diff --git a/.gitignore b/.gitignore index c66be002b..177b8904a 100644 --- a/.gitignore +++ b/.gitignore @@ -62,10 +62,10 @@ dist/ .claude/ .codex/ -AGENTS.md -CLAUDE.md _bmad/ _bmad-output/ +# Investigation and editorial working files belong outside this repository. +docs/investigations/ docs/security/ docs/rebranding.md .release-sign/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..9bc9aff6d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,84 @@ +# SILO Repository Guide + +## Project map + +- Server: `pgsty/silo` (this checkout, default branch `main`) +- Console: `pgsty/silo-console` (usual sibling checkout `../silo-console`) +- Client: `pgsty/mc`, shipped as `mcli` (usual sibling checkout `../mc`) +- Shared packages: `pgsty/silo-pkg` (usual sibling checkout `../silo-pkg`) +- Documentation: `pgsty/silo.pgsty.com` (usual sibling checkout `../silo.pgsty.com`), + published at + +The server executable, package, systemd service, and container image use `silo`; +the public Docker image is `docker.io/pgsty/silo`. The server module remains +`github.com/minio/minio`. Consult the current `go.mod` and release configuration +for selected component versions and compatibility replacements. + +## Supported stack and compatibility policy + +The maintained, release-gating product graph is the coordinated PGSTY stack: +`silo` + `silo-console` + `mc` + `silo-pkg`. + +Keep compatibility with upstream MinIO/MC on a best-effort basis. Preserve +inexpensive wire, configuration, CLI, migration, and import compatibility when +it helps users, and document known differences. Do not infer from source +lineage, MinIO-compatible protocols, retained `MINIO_*`/`MC_*` names, or an +inherited upstream test that unmodified upstream MinIO is a supported release +target. + +Upstream-only compatibility checks may remain as advisory evidence, but they +must not force a downgrade of a maintained SILO component, a fork-only API +workaround, or a release block. Make upstream compatibility a hard gate only +when the user explicitly requests that scope. + +## Dependency selection + +- When PGSTY maintains a component, import and require it directly under its own + module path. In particular, prefer `github.com/pgsty/silo-pkg/v3` over + `github.com/minio/pkg/v3` in maintained SILO source. +- Do not use an upstream package merely to make unmodified upstream MinIO or MC + compile. Unavoidable transitive upstream modules should be documented and + kept separate from the maintained product dependency. +- `github.com/minio/minio-go/v7` is the explicit exception: use the verified + upstream module/commit while it contains the required fixes; do not recreate + a SILO fork without a concrete functional divergence. +- Check the current `go.mod` before changing versions. Coordinate breaking + import-path changes across package, client, Console, and server releases. + +## Documentation and delivery + +The companion site owns product, operations, migration, security-advisory, +design, and release documentation. Maintain English and Chinese content in +that repository and link to its canonical URLs from this one. The site's +`content/docs/` is the documentation entry point; detailed pages live under +`content/operations/`, `administration/`, `reference/`, `compatibility/`, +`about/`, and `blog/`, following the existing structure. + +Keep repository entry points and contributor instructions here. Retained +upstream documents, examples, and test fixtures under `docs/` may be updated +when a code or tooling change requires it; do not build a second documentation +site in this tree. + +Investigation plans, prompts, AI session transcripts, execution logs, temporary +reports, and private security material belong outside the checkout, for example +in a task-specific directory under `~/tmp/`. Do not recreate +`docs/investigations/`, `docs/security/`, or `docs/rebranding.md`, or move their +working material to another repository directory. Extract reusable, verified +knowledge into the companion site; keep private evidence outside public Git. + +Compatibility pages may continue to describe similarities with upstream, but +must label that compatibility as best effort. The supported and tested server +for Console and mcli administration is `pgsty/silo`. + +Before removing or moving documentation, check repository links and scripts, +the companion site's links and anchors, and references from this guide. Use +fixed commit URLs for historical evidence that should survive deletion from +the current tree. Run the site's `make check` for site changes and this +repository's `make rebrand-guard` for documentation cleanup that changes the +identifier inventory. When new public URLs are involved, publish the site +before publishing source changes that depend on those URLs. + +Keep changes in the repository that owns the affected surface. Treat every +repository commit, tag, release, image, documentation update, and deployment as +a separate deliverable. Follow `CONTRIBUTING.md`, including DCO sign-off +(`git commit -s`). `CLAUDE.md` imports this guide so both agents use one policy. diff --git a/CHANGELOG.md b/CHANGELOG.md index 404dfd479..317b3dc64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ and [complete commit range](https://github.com/pgsty/silo/compare/RELEASE.2026-0 TLS handshake reads. The wrapper's strict header mode is not applied to HTTP/2. - Reject unsigned `x-amz-*` request headers that could turn a signed PUT into a copy of another object accessible to the signer (SN-2026-011). The latest - public Server is affected; the fix is on main. See [the advisory ledger](docs/security/advisories.md). + public Server is affected; the fix is on main. See [the advisory ledger](https://silo.pgsty.com/about/security-advisories/). - Align signed request fields with policy conditions and enforce header-only presigned payload checksums. See [the signed-header review](https://silo.pgsty.com/blog/design/signed-header-coverage/). - **Breaking policy semantics:** separate self-service `admin:ChangeMyPassword` @@ -97,7 +97,7 @@ and [complete commit range](https://github.com/pgsty/silo/compare/RELEASE.2026-0 its tracker, mover, scanner hooks, configuration, XML actions and metrics. Accept and ignore retired configuration/XML and preserve ordinary statistics when reading v9 caches. See [migration notes](docs/bucket/lifecycle/access-tiering-removal.md). - The [decision record](docs/investigations/access-tiering-revert.md) preserves + The [decision record](https://silo.pgsty.com/compatibility/access-tiering-removal/) preserves the feature's introduction, subsequent fixes, rollback scope and review history. - Preserve the independent multi-pool write, metadata, healing and conditional deletion fixes from PR #178, including shared remote-tier reference protection. @@ -111,7 +111,7 @@ and [complete commit range](https://github.com/pgsty/silo/compare/RELEASE.2026-0 reflect actual work, and report bounded MRF drops. - Converge bucket metadata with deterministic source state, deletion tombstones, creation time recovery and diagnostics. The mixed-version export gate requires - coordinated upgrades before tombstones are exported. See [the #77 record](docs/investigations/issue-77-current.md). + coordinated upgrades before tombstones are exported. See [the #77 record](https://silo.pgsty.com/blog/design/bucket-metadata-convergence/). - Include per-bucket CORS in metadata export/import, close metadata publication and logger races, and report effective bucket quotas in metrics. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..43c994c2d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index cf732b4e5..343ff60ca 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -97,7 +97,7 @@ for their reports as well; the avatar wall and community total still count each | [@davinkevin](https://github.com/davinkevin) | [pgsty/silo#20](https://github.com/pgsty/silo/issues/20) Proposal: Enable Renovate for automated dependency updates | | [@cbornet](https://github.com/cbornet) | [pgsty/silo#31](https://github.com/pgsty/silo/issues/31) Multipart uploads with FULL_OBJECT CRC32 not working
[pgsty/silo#32](https://github.com/pgsty/silo/issues/32) `listObjects` should return `NoSuchBucket` when the bucket doesn't exist and prefix is passed
[pgsty/silo#107](https://github.com/pgsty/silo/issues/107) PutObject fails with chunked encoding and checksumType | | [@vampywiz17](https://github.com/vampywiz17) | [pgsty/silo#15](https://github.com/pgsty/silo/issues/15) LDAP TLS regression in RELEASE.2026-03-21T00-00-00Z breaks built-in Console and external Console LDAP login on Kubernetes Tenant
[pgsty/silo#108](https://github.com/pgsty/silo/issues/108) Web Console login regression in RELEASE.2026-09-03T13-18-01Z (local and LDAP users fail) | -| [@orenyomtov](https://github.com/orenyomtov) | Private security disclosure: a presigned or signed PUT could be turned into a server-side CopyObject read of any object the signing key can reach via an unsigned `x-amz-copy-source` header. Fixed as [`SN-2026-011`](https://github.com/pgsty/silo/blob/main/docs/security/advisories.md) ([pgsty/silo#173](https://github.com/pgsty/silo/pull/173)) | +| [@orenyomtov](https://github.com/orenyomtov) | Private security disclosure: a presigned or signed PUT could be turned into a server-side CopyObject read of any object the signing key can reach via an unsigned `x-amz-copy-source` header. Fixed as [`SN-2026-011`](https://silo.pgsty.com/about/security-advisories/#sn-2026-011) ([pgsty/silo#173](https://github.com/pgsty/silo/pull/173)) | | [@mumu-lab](https://github.com/mumu-lab) | [pgsty/silo#106](https://github.com/pgsty/silo/issues/106) 监控指标读取已弃用的 BucketQuota.Quota 字段导致 Quota 指标无值 | | [@jvasile](https://github.com/jvasile) | [pgsty/silo#33](https://github.com/pgsty/silo/issues/33) .deb doesn't create user/group/default files | | [@pmezhuev](https://github.com/pmezhuev) | [pgsty/silo#43](https://github.com/pgsty/silo/issues/43) RPM package for RELEASE.2026-06-18T00-00-00Z is missing GPG signature | diff --git a/README.md b/README.md index d3fe0a391..94b6edbfb 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ ## Current release and main branch The latest published Server is [20260903](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-09-03T13-18-01Z). -As of 2026-09-13, the main branch has newer security, storage, Console and +As of 2026-09-16, the main branch has newer security, storage, Console and shared-package changes that have not shipped in a Server release. See [CHANGELOG.md](CHANGELOG.md) and the [component version matrix](https://silo.pgsty.com/compatibility/versions/) for the exact release/source boundary, including SN-2026-011 and password-policy migration. @@ -93,13 +93,16 @@ Every release ships checksums, SPDX SBOMs, Sigstore-signed manifests, and GitHub ## Compatibility -The S3 API, `MINIO_*` variables, `minio_*` metrics, `x-minio-*` headers, `/minio/*` routes, the `github.com/minio/*` import paths, and the on-disk format (including `.minio.sys`) are preserved and held in place by a CI compatibility check. Only Silo-owned delivery surfaces change: the `silo` executable, package, service, Helm chart, and container image — no `minio` binary alias is installed. +Silo preserves S3 and storage-format compatibility, including existing `MINIO_*` variables, `minio_*` metrics, `x-minio-*` headers, `/minio/*` routes, and `.minio.sys` data. CI guards selected compatibility identifiers; release notes document intentional security and behavior changes. Silo-owned delivery surfaces use the `silo` executable, package, service, Helm chart, and container image; no `minio` server binary alias is installed. + +The supported release stack is `pgsty/silo` + `pgsty/silo-console` + `pgsty/mc` + `pgsty/silo-pkg`; compatibility with unmodified upstream MinIO/MC is best effort. The Server, Console, and client retain their historical module paths where needed, while maintained code imports `github.com/pgsty/silo-pkg/v3` directly. The SDK `github.com/minio/minio-go/v7` is an explicit upstream dependency. See the current [go.mod](go.mod) for versions and replacements. Every divergence from upstream is listed in the code-verified [compatibility audit](https://silo.pgsty.com/compatibility/server/). Treat each release as a downstream upgrade: pin versions, read the [release notes](https://silo.pgsty.com/tags/silo/), and keep a rollback path. ### TLS and Go upgrades -TLS key exchange follows Go's defaults across the S3 listener, node links, +The following TLS repair is on main and is not included in Server 20260903. +With that repair, TLS key exchange follows Go's defaults across the S3 listener, node links, replication, identity providers, etcd, and external HTTP services. If an endpoint cannot accept ML-KEM, `GODEBUG=tlsmlkem=0` disables the default hybrid exchanges for the process; certificate verification remains enabled. This option does not @@ -115,7 +118,16 @@ values to restore Keychain trust. Explicit certificates in the configured `CAs` directory remain additive to the selected root pool. Go 1.27 binaries require macOS 13 or later. See the [Go release notes](https://go.dev/doc/go1.27) and the -[SILO stack investigation](docs/investigations/go127-stack.md). +[Go 1.27 TLS and OIDC discovery guide](https://silo.pgsty.com/blog/design/go127-tls-oidc-discovery/). + +## Documentation ownership + +User documentation is maintained at [silo.pgsty.com](https://silo.pgsty.com/docs/), +with source in [pgsty/silo.pgsty.com](https://github.com/pgsty/silo.pgsty.com). +The remaining `docs/` tree contains inherited references, examples, and tooling +fixtures. Investigation logs, AI work records, and temporary reports are kept +outside this repository; reusable findings belong in the companion site. +See [AGENTS.md](AGENTS.md) for repository ownership and maintenance rules. ## Security & Contributing diff --git a/README_ZH.md b/README_ZH.md index a8db844d6..b30cd3304 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -38,7 +38,7 @@ ## 当前发行版与主分支 最新已发布的 Server 仍为 [20260903](https://github.com/pgsty/silo/releases/tag/RELEASE.2026-09-03T13-18-01Z)。 -截至 2026-09-13,主分支已合入更新的安全、存储、Console 与共享包改动,但尚未发布新 Server。 +截至 2026-09-16,主分支已合入更新的安全、存储、Console 与共享包改动,但尚未发布新 Server。 准确的已发布/源码边界见 [CHANGELOG.md](CHANGELOG.md) 与[组件版本矩阵](https://silo.pgsty.com/zh/compatibility/versions/), 其中包括 SN-2026-011 修复状态与密码权限迁移要求。 @@ -92,10 +92,25 @@ docker exec silo mcli mb local/demo && docker exec silo mcli ls local ## 兼容性 -S3 API、`MINIO_*` 环境变量、`minio_*` 指标、`x-minio-*` 头、`/minio/*` 路由、`github.com/minio/*` 导入路径与磁盘格式(含 `.minio.sys`)原样保留,并由 CI 兼容性门禁冻结。只有 Silo 自有交付面改名:`silo` 可执行文件、软件包、服务、Helm Chart 与容器镜像 —— 原生交付物不会安装 `minio` 二进制别名。 +Silo 保留 S3 与存储格式兼容性,包括既有 `MINIO_*` 环境变量、`minio_*` 指标、`x-minio-*` 头、`/minio/*` 路由与 `.minio.sys` 数据。CI 守卫检查选定的兼容性标识,有意的安全与行为变化在发布说明中记录。Silo 自有交付面使用 `silo` 可执行文件、软件包、服务、Helm Chart 与容器镜像;原生交付物不会安装 `minio` 服务端二进制别名。 + +正式支持和发布验收的组合为 `pgsty/silo` + `pgsty/silo-console` + `pgsty/mc` + `pgsty/silo-pkg`,对未修改的上游 MinIO/MC 尽最大努力保持兼容。Server、Console 与客户端按需保留历史模块路径,维护源码直接导入 `github.com/pgsty/silo-pkg/v3`;SDK `github.com/minio/minio-go/v7` 是明确保留的上游依赖。具体版本与 replace 以当前 [go.mod](go.mod) 为准。 与上游的全部分歧,以逐项核验代码的[兼容性审计](https://silo.pgsty.com/zh/compatibility/server/)形式维护。每个版本仍应视为下游升级:锁定版本,阅读[版本说明](https://silo.pgsty.com/zh/tags/silo/),并保留回滚路径。 +### TLS 与 Go 升级 + +Server 恢复 Go 默认密钥交换策略的修复已在 main,尚未包含在 Server 20260903。 +`GODEBUG=tlsmlkem=0`、`tlssecpmlkem=0` 的适用范围、macOS 根证书来源变化, +以及 OIDC discovery 的诊断方法见 [Go 1.27 TLS 与 OIDC 指南](https://silo.pgsty.com/zh/blog/design/go127-tls-oidc-discovery/)。 + +## 文档归属 + +用户文档统一维护在 [silo.pgsty.com](https://silo.pgsty.com/zh/docs/),源码位于 +[pgsty/silo.pgsty.com](https://github.com/pgsty/silo.pgsty.com)。本仓库保留的 `docs/` +主要是继承的参考材料、示例和工具测试夹具。调查日志、AI 工作记录与临时报告放在仓库外; +可复用的结论应整理进伴生文档站。仓库职责与维护规则见 [AGENTS.md](AGENTS.md)。 + ## 安全与贡献 请按照 [`SECURITY.md`](SECURITY.md) 私密报告漏洞;每项修复都会发布公开[安全公告](https://silo.pgsty.com/zh/blog/security/)。本项目不要求签署 CLA:贡献按 AGPL-3.0-or-later(inbound=outbound)接收,只需 DCO 签署(`git commit -s`),详见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。 diff --git a/SECURITY.md b/SECURITY.md index dcfd6e4ea..84608c7c1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,7 +7,7 @@ Silo-specific fixes or release notes. ## Supported Versions Security fixes are tracked on the active development branch and summarized in -[docs/security/advisories.md](docs/security/advisories.md). Only the current +[the security advisory ledger](https://silo.pgsty.com/about/security-advisories/). Only the current Silo release line is supported unless an advisory says otherwise. ## Inherited Fix Evidence @@ -26,7 +26,7 @@ separately even when the fork preserves the original commit object and SHA. The inherited [service-account](https://github.com/pgsty/silo/blob/c1a49490c78e9c3ebcad86ba0662319138ace190/cmd/admin-handlers-users_test.go#L211-L212) and [STS](https://github.com/pgsty/silo/blob/c1a49490c78e9c3ebcad86ba0662319138ace190/cmd/sts-handlers_test.go#L45-L46) regression groups remain part of `go test ./cmd`; see the - [canonical ledger](docs/security/advisories.md#inherited-upstream-advisory-baseline) + [canonical ledger](https://silo.pgsty.com/about/security-advisories/#inherited) for the operator-facing record. ## Reporting a Vulnerability @@ -42,4 +42,4 @@ For vulnerabilities in this fork: ## Disclosure Process -Fork-specific fixes and user-visible upgrade notes are published in [docs/security/advisories.md](docs/security/advisories.md). The fork-specific triage and remediation process is described in [VULNERABILITY_REPORT.md](VULNERABILITY_REPORT.md). +Fork-specific fixes and user-visible upgrade notes are published in [the security advisory ledger](https://silo.pgsty.com/about/security-advisories/). The fork-specific triage and remediation process is described in [VULNERABILITY_REPORT.md](VULNERABILITY_REPORT.md). diff --git a/VULNERABILITY_REPORT.md b/VULNERABILITY_REPORT.md index 6c5ea45dc..b3b36d7d0 100644 --- a/VULNERABILITY_REPORT.md +++ b/VULNERABILITY_REPORT.md @@ -34,4 +34,4 @@ Based on the report, the Silo maintainers investigate: If the vulnerability exists in this fork itself, the maintainers will, when feasible, fix the issue or implement reasonable countermeasures such that the vulnerability can no longer be exploited. Fork-specific upgrade notes and -security advisories are published in `docs/security/advisories.md`. +security advisories are published in [the security advisory ledger](https://silo.pgsty.com/about/security-advisories/). diff --git a/buildscripts/rebrand-guard/compat-baseline.json b/buildscripts/rebrand-guard/compat-baseline.json index e158977f3..29f1d16b2 100644 --- a/buildscripts/rebrand-guard/compat-baseline.json +++ b/buildscripts/rebrand-guard/compat-baseline.json @@ -132,7 +132,6 @@ "MINIO_API_DELETE_CLEANUP_INTERVAL", "MINIO_API_DISABLE_ODIRECT", "MINIO_API_GZIP_OBJECTS", - "MINIO_API_LEGACY_BUCKET_RESOURCE_MATCH", "MINIO_API_LIST_QUORUM", "MINIO_API_OBJECT_MAX_VERSIONS", "MINIO_API_ODIRECT", @@ -627,7 +626,6 @@ "x-minio-origin-endpoint", "x-minio-prefixes-total", "x-minio-read-quorum", - "x-minio-replication", "x-minio-replication-actual-object-size", "x-minio-replication-delete-status", "x-minio-replication-deletemarker-status", diff --git a/buildscripts/rebrand-guard/main.go b/buildscripts/rebrand-guard/main.go index b625cadff..2060ee50d 100644 --- a/buildscripts/rebrand-guard/main.go +++ b/buildscripts/rebrand-guard/main.go @@ -115,9 +115,8 @@ func collect(repo string) (manifest, error) { fset := token.NewFileSet() for _, rel := range files { - // Investigation artifacts contain synthetic routes and archived configurations. + // Migration notes and guard fixtures contain archived identifiers. if rel == "SILO_REBRANDING_MIGRATION.md" || - strings.HasPrefix(rel, "docs/investigations/") || strings.HasPrefix(rel, "buildscripts/rebrand-guard/") || strings.HasPrefix(rel, "buildscripts/helm-migration-guard/") { continue diff --git a/docs/bucket/lifecycle/access-tiering-removal.md b/docs/bucket/lifecycle/access-tiering-removal.md index 876aecdfc..36083b127 100644 --- a/docs/bucket/lifecycle/access-tiering-removal.md +++ b/docs/bucket/lifecycle/access-tiering-removal.md @@ -2,7 +2,7 @@ PR #60 introduced an opt-in scheduler that moved objects between local server pools according to GET frequency. It and its feature-specific fixes have been removed. This does not remove ordinary lifecycle expiration, transitions to remote tiers, rebalance, decommission, or the general multi-pool correctness fixes from PR #178. -The [introduction and rollback record](../../investigations/access-tiering-revert.md) documents the commit history, scope decision, review corrections and unresolved validation findings. +The [introduction and rollback record](https://silo.pgsty.com/compatibility/access-tiering-removal/) documents the commit history, scope decision, review corrections and unresolved validation findings. The published Server 20260903 predates this feature. These instructions concern main/snapshot deployments that included #60; upgrading from the published version does not require access-tier configuration cleanup. diff --git a/docs/investigations/access-tiering-revert.md b/docs/investigations/access-tiering-revert.md deleted file mode 100644 index 0c76b7240..000000000 --- a/docs/investigations/access-tiering-revert.md +++ /dev/null @@ -1,193 +0,0 @@ -# 访问频率分层:引入、修复与回退记录 - -记录日期:2026-09-15。本记录说明 PR #60 的设计、合入后的取舍,以及此次回退为什么同时保留并补齐通用多池正确性修复。操作步骤见[退役迁移说明](../bucket/lifecycle/access-tiering-removal.md)。合并、正式发布和生产部署是不同状态;本记录随回退变更交付,不代表已经发布。 - -初稿审查时(2026-09-15 03:20 UTC),最终候选尚未移植到远端基线、尚未冻结 PR head,合并前全量检查与三次有效 Linux 运行尚未执行,本变更尚未合并。后续执行状态以承载本记录的 PR 及其绑定提交的验收记录为准;下文的历史实验不替代这些检查。 - -**当前验收状态:** 回退、普通版本 DELETE 调和、扫描复用及文档由 [PR #188](https://github.com/pgsty/silo/pull/188) 交付。本地与 CI 检查通过;首次 Linux 验收因 DELETE204 后的 HEAD/GET503 停止。随后完成可控机制实验、匹配写入负载对照,并修正准备检查;独立的新轮次 R-Upgrade-2 三次完整升级验收均通过。旧失败没有改判,原单次请求的逐盘状态仍不可追溯。最终合并状态以 PR 为准,正式发布与部署另行验收。详情见第 9 至 11 节。 - -## 1. 引入的目标和实际范围 - -[@mrjavadseydi](https://github.com/mrjavadseydi) 在 [PR #60](https://github.com/pgsty/silo/pull/60) 提出了基于 GET 频率的本地池间分层。成功 GET 更新有界滚动计数,后台调度器把热对象提升到配置中的首个池,把已经迁移且变冷的对象降到末个池。功能默认关闭,至少需要两个池;它与普通生命周期过期、远端对象存储 transition、rebalance 和 decommission 是不同机制。 - -实现不只是一个后台任务:它增加了 GET 计数入口、leader 调度、跨池版本栈复制、来源清理及失败恢复、热池配额、生命周期 XML 扩展、配置项、指标和扫描统计。访问热度统计使 data-usage cache 从 v8 升到 v9。对象本体的存储格式没有因此改变。 - -搬移要保持完整版本历史、删除标记、null version、时间戳、ETag、校验和和加密元数据;同时需要处理并发写入、目的端已有版本、来源部分删除和远端 tier 引用。合入前的修补和测试针对并覆盖了这些边界,不应把回退解释成贡献无效。贡献者署名继续保留,独立的其他贡献也不回退。 - -## 2. 可追溯时间线 - -下表的 PR/Issue 时间使用 UTC;提交链接对应具体代码,不把“报告时间”当作“缺陷首次出现时间”。 - -| 时间 | 事件 | 本次处理 | -| --- | --- | --- | -| 2026-08-15 10:15 | #60 创建;原始实现 [`7a060cab1`](https://github.com/pgsty/silo/commit/7a060cab1edd5bbc17da7f703bbd1ab7415b6f7c) | 随特性撤销 | -| 2026-09-06 00:09 | [#133](https://github.com/pgsty/silo/issues/133) 报告多池副本写不能权威调和 Object Lock 状态 | 保留解决它的通用修复 | -| 2026-09-06 15:12 | [#144](https://github.com/pgsty/silo/issues/144) 报告条件 DELETE 原子性仅限单个纠删码集合 | 保留解决它的通用修复 | -| 2026-09-08 05:18 | [`9a6e1477f`](https://github.com/pgsty/silo/commit/9a6e1477f45067559def8423d431ee177795134f) 补访问分层兼容标识清单 | 删除功能专属标识,保留有依据的退役兼容 | -| 2026-09-08 07:08 | [`374de0fa3`](https://github.com/pgsty/silo/commit/374de0fa32aa1d6eda57dbba6ac522ebf793b6be) 修复搬移的版本保全、写隔离和删除范围 | 随专属搬移器撤销 | -| 2026-09-08 07:28 | [`5ac33e158`](https://github.com/pgsty/silo/commit/5ac33e1583e838ade3f56c7d60e80e7a854f9a88) 确定性覆盖搬移失败恢复 | 随已删除搬移器的专属测试撤销 | -| 2026-09-08 07:42 | #60 以 [`a3df317ae`](https://github.com/pgsty/silo/commit/a3df317ae0725eb650e4d3e21551154f69be6229) 合入 | 以该 merge 的第一父差异确定功能边界 | -| 2026-09-11 12:34 | [#178](https://github.com/pgsty/silo/pull/178) 合入通用多池写入、元数据与条件删除调和 | 保留,解除测试对访问搬移器的依赖 | -| 2026-09-13 | [`2dd1e00da`](https://github.com/pgsty/silo/commit/2dd1e00da49faf995f2db29807fc6211b8376d7d) 的 CHANGELOG 同时汇总访问分层和通用多池修复 | 拆开表述,不整条删除独立修复历史 | -| 2026-09-15 | 维护者决定收缩访问分层;完成来源分析、三种候选反证、退役兼容、普通版本 DELETE 修补与外部评审 | 形成此次选择性回退 | - -#178 中的 [`e59a3d938`](https://github.com/pgsty/silo/commit/e59a3d938ed25c1bcd51efbb4ad6955073d195f7) 提供池级串行化、字段调和和条件删除;[`ccb676e60`](https://github.com/pgsty/silo/commit/ccb676e60cb7441ee65ff7c35f3b7828979101fd) 保护仍被其他副本使用的远端 tier 引用;[`51d41345f`](https://github.com/pgsty/silo/commit/51d41345f7ac532f9c6fea2b7dba5f29da930b8f) 保存 Linux 重启及 OIDC 验收记录。这三项不属于仅为访问频率调度而存在的代码。 - -截至回退评估时,公开 Server `RELEASE.2026-09-03T13-18-01Z` 早于 #60 合入。退役迁移主要针对运行过后续 main、自行构建或快照版本的实例;不能据此声称正式 release 用户普遍启用过此特性。 - -## 3. 为什么回退,为什么不能整批撤销后续修复 - -维护者的取舍是:默认关闭的可选调度能力,对配置、生命周期、缓存、统计和核心多池写入路径带来了过大的维护面。此次移除的是该能力及专属实现,没有测量并宣称吞吐量提升、延迟降低或固定减少一次分布式锁往返。 - -“后来改过同一文件”不等于“由 #60 引发”。#133/#144 的报告早于 #60 合入;更关键的是,原有 rebalance、decommission 和复制写入也会使同一版本暂时存在于多个池。访问分层消失后,这些状态仍然合法存在。移除调和与锁纪律会重新允许旧副本遮蔽新元数据、条件删除选错版本、清理错误被吞掉等问题。 - -评估在隔离工作树中实际比较了三条路线: - -| 候选 | 通用多池回归 | 普通指定版本 DELETE | -| --- | --- | --- | -| A:撤销 #60,保留 #178 | 原有 13 组通过 | 仍能成功返回后留下可读副本 | -| B:同时撤销 #60 和 #178 的存储修补 | 相同 13 组中 10 组失败 | 问题仍在 | -| C:A 加普通版本 DELETE 调和 | 13 组原有及当时新增的 8 组通过 | 同一复现通过 | - -这些是 2026-09-15 的历史对照结果,不是最终 PR head 的发布验收。后续补上目录标记、真实 rebalance 中断等覆盖后,通用多池测试达到 23 组。测试通过不能替代来源分析,来源分析也不能替代最终候选的运行验证。 - -## 4. 最终保留与删除的边界 - -- 删除访问 tracker、调度/搬移器、GET 和 scanner 钩子、热池配额、专属配置帮助、生命周期动作、指标及专属测试。 -- 保留普通过期、远端 transition、rebalance/decommission、复制写入,以及 #178 的 Object Lock、标签、条件删除、元数据调和与远端引用保护。 -- 原有 data-usage 生成解码器恢复到 #60 前的实现;允许读取 v8/v9,利用字段编码跳过已退役热度字段,继续写 v8。普通字段保真由历史真实 v9 样本测试覆盖。 -- 仅容忍准确的十个退役 ILM 键;读取生命周期时丢弃退役扩展。纯访问动作的规则需先清理才能再次编辑;混合规则保留普通动作。 -- 已经搬移的对象留在当前池;没有全量搬回、自动删除所有重复版本、后台清理服务或对象元数据重写。 - -曾对本地审查提交 `d06f1c614` 做过声明来源复核:消失的 357 个声明均不在 #60 之前,删除的十个文件均由 #60 引入;#60 原先删换的 41 行旧文本按忽略空白比较有 40 行恢复,剩下一行保留 #178 在已持有池锁时调用 `getWritePoolIdx(..., true)` 的修正,避免对同一对象再次取锁。生成缓存解码器与功能前逐字节一致,原有 13 组通用测试没有删除。这是该审查版本的保全证据,不能把数字脱离 SHA 当作未来所有版本的保证。 - -## 5. 普通版本 DELETE 是独立补洞 - -功能移除不会自动消除历史重复副本。普通单对象指定版本 DELETE 因而复用既有调和路径:在池级对象锁内读取每个池的目标版本,计算一次条件及回调,先删除非权威副本,再处理权威副本;任何不可读池或清理错误都不能当作成功。 - -范围包括 UUID、null version、delete marker,以及原先就被解析为 null version 的未指定版本目录标记 DELETE。入站复制、搬移内部调用、生命周期过期和 free-version 清理保留各自语义;批量 `DeleteObjects` 原本就会向池并发扇出,不是此次遗漏。 - -删除标记需要向 retention/metadata 回调传入与 set 层相同的 `MethodNotAllowed` 或 `ObjectNotFound` 语义。直接复用拒绝 marker 的元数据更新入口会错误地拒绝合法版本删除。回调从所有副本合并独立更新的 Object Lock 和标签,不能随意只采用一个池的状态。 - -存在两项明确的成功/失败边界: - -1. 读法定多数不足时返回 `503 SlowDownRead`,即使另一个池有可读副本。旧路径的结果会受池遍历顺序影响;新路径把失败语义统一。它是正确性与可用性的取舍,需要恢复后重试。 -2. 出站删除复制尚未完成时,成功响应可以表示各副本进入 `VersionPurgePending`,由既有 worker 完成清理。原有每池 quorum 规则也继续适用;不能把成功响应等同于每一块盘立即物理删除。 - -## 6. 审查如何改变了方案 - -本地先形成三个线性审查提交:`8fdfdabd9` 移除特性,`d06f1c614` 补普通 DELETE,`6e3fdca97` 去除重复扫描。它们记录审查演进,最终 PR 在独立远端基线上重放,提交 ID 会改变;不应把线性演进误认为三份同时维护的实现。 - -Claude Code Opus 5 / max 的五轮实现评审要求补齐退役兼容、说明协调停机及环境一致性、验证真实池故障,并纠正运行证据措辞。随后 Claude 与 ZCode 的独立复核再次确认了回退边界和普通 DELETE 语义;最终两轮计划商榷收束了交付流程。 - -| 意见 | 裁定与处理 | -| --- | --- | -| 指定版本 DELETE 重复扫描所有池 | 接受。第一次扫描已在同一锁内得到目标副本,直接合并其结果。16 盘池在回调前的读取计数从 32 降为 16;这不等于总 I/O 或延迟减半。 | -| N 个副本产生 N 条 DELETE 审计 | 反驳。底层调用只追加上下文标签,HTTP 层向每个配置审计目标发送一次请求事件。成功完成调和时池标签最终指向 primary;不增加额外 NoAuditLog 修改。 | -| retention/metadata 顺序与 set 层不同 | 差异存在,但已有明确注释。保留 retention 优先,拒绝后不删除、不调度删除复制、不 Sweep;不宣称任意自定义回调都能交换。 | -| 把优化 amend 进旧提交并直接丢弃 main 脏修改 | 不 amend 已审历史。先保存完整文件、补丁、哈希及可达恢复引用,核对覆盖后受控恢复。八组新增通用测试承接,访问搬移专用测试由真实 rebalance 中断覆盖替代。 | -| 从当前本地分支直接开 PR | 调整。其祖先含另一个任务的 IAM/超时提交 `ebc9937d9`;从远端 `89637554d` 仅移植本次变更,不夹带或删除独立工作。 | -| 合并之后再跑全量与 Linux 验收 | 不接受。先完成文档和移植,冻结 PR head,再验收;不能把旧 SHA 的测试直接提升为新基线的通过记录。 | -| 不同基线 diff 必须逐字节一致 | 改为每提交 stable patch-id、路径与完整树等价性核验。blob hash 和行号随基线变化,不应成为错误的拒绝依据。 | - -`objectPoolInfos` 的并行查询作为独立性能跟进,本次不增加并发实现。Contributor 署名、#132 配额指标、#77 桶元数据、federated COPY、IAM、超时及其他独立修复不因本次取舍被整体撤销。 - -## 7. 历史运行证据与未定案事项 - -以下是移植前 `d06f1c614` 的历史实验,不是最终 PR head 的验收替身。 - -| 运行 | 实际观察 | 不应推导的结论 | -| --- | --- | --- | -| `f590538f`,四节点双池 | 旧版真实 rebalance 中断留下 9 个重复 UUID;协调停机换新后对象四节点可读,旧配置与普通规则可编辑,真实缓存头 v9→v8;删除一个 addressed UUID 后四节点 HEAD/GET 404,另一个版本仍可读 | 没有验证全部 9 个不同 UUID 收敛;物理元数据仅每池抽查一盘;没有整份运行时统计守恒测量 | -| `fd93cd37`,三节点双池 | 停掉一个池所在节点,保留 namespace 锁的 2/3 法定多数;DELETE 返回 503 SlowDownRead,源全部四盘保留目标版本;恢复后 DELETE204,各节点 HEAD/GET404 | 不能推广为跨主机网络、持久盘及压力验收 | -| 被弃用的四节点停池拓扑 | 同时丢失 namespace 锁法定多数,发生客户端超时,记录脚本还遇到 NoneType 错误 | 不是“池读取返回503”的证明 | -| `83676ca2` | 升级后配置/生命周期编辑之后一次 HeadObject 返回503;artifact 没有记录 DELETE 自身响应 | “DELETE之后”仅来自脚本顺序,不能写成已证实 DELETE204 后异常,也不能归类为已修复、既有问题或暂态 | - -**开放项:`83676ca2` 的 HEAD503 仍未定案。** 可直接比较的目标阶段历史运行是一失败、一成功;一次未复现不足以关闭问题。仅凭 `SlowDownWrite` 等错误名字也不能给其他失败确定容量或环境根因。 - -最终候选的合并前复核采用有界规则:要求三次有效升级后 DELETE/HEAD 运行,最多五次总尝试;每次记录 DELETE 码/耗时、失败 HEAD 的节点与版本、GET 错误码、两池全盘元数据、固定间隔重试时序和旧版同拓扑对照。旧版可能保留副本返回200,不要求它满足新增跨池删除契约。 - -有证据证明在目标操作前失败的 harness 尝试才能不计入有效运行,但仍计入总尝试。任何目标阶段的新503或数据不变量失败都不能通过补跑抹掉,必须暂停合并并定位。三次通过也只满足这项工程检查,不证明历史异常已消失;开放项在合并和正式发布评估时仍须可见。 - -## 8. 交付与恢复纪律 - -最终候选使用独立分支;main 的五个旧修改先保存完整内容、二进制补丁、SHA-256 和具名 Git 恢复引用,再核对原 HEAD/哈希及测试覆盖,只恢复这五个文件。禁止用整树 reset 或 clean 代替受控归一;若用户已有新增编辑,应保留并重新核对。 - -全量 cmd/internal、相关 race、构建、vet、lint、生成文件和兼容检查,以及上述 Linux 验收,绑定最终候选的实际 SHA。若纳入新的远端提交,重新记录基线与 head,复核变更并重跑受影响验收。文档与原始测试记录各自保留其对应版本,不篡改旧失败、不用新通过覆盖旧记录。 - -逐节点滚动升级未通过既有二进制校验检查,采用[协调停机方案](../bucket/lifecycle/access-tiering-removal.md#before-upgrading-a-build-with-access-tiering)。实验使用单个 Docker Linux VM 和 tmpfs;正式 tag、包、镜像、跨主机及生产部署仍是独立交付。未验证全部重复 UUID 或运行时统计守恒应如实披露,不能反向引入自动搬回/清理需求或无关重构。 - -## 9. 执行后记:最终基线、恢复窗口与验收 - -本次实际交付由 [PR #188](https://github.com/pgsty/silo/pull/188) 承载。选择的远端基线为 `89637554d60c27cfc51d2281d0a4fe15e415f06d`,移植没有包含本地独立 IAM/超时提交 `ebc9937d9`。前三项实现和历史文档逐提交通过 stable patch-id 对照;虚拟补回独立 IAM 差异后,完整树与原审查分支一致。 - -首次本地 lint 发现新增回调选择分支触发 `gocritic/ifElseChain`,因此追加等价的无表达式 `switch` 改写,保持 marker、指定版本和普通元数据查找的条件顺序及分支体。重新固定的代码候选为 [`41aa84609`](https://github.com/pgsty/silo/commit/41aa84609754769cfb1861d7fd060c2e84182b98)。这一提交上,全量 cmd/internal 得到 6,428 个测试及子测试通过、166 个跳过,50 个有测试的包通过;相关 race 得到 283 个测试及子测试通过。`make build`、全包构建、vet、lint、生成文件及 rebrand/compat 检查均通过。[Go CI](https://github.com/pgsty/silo/actions/runs/34925534139)、[DCO](https://github.com/pgsty/silo/actions/runs/34925534110)、[VulnCheck](https://github.com/pgsty/silo/actions/runs/34925534142) 和[发布流水线的测试运行](https://github.com/pgsty/silo/actions/runs/34925534198)共 11 项检查通过;后者没有发布正式制品。 - -第一次最终候选停池实验 `dcd5c2e5` 在源版本保全断言后失败:停掉另一池返回 `503 SlowDownRead`,源四盘版本保留;恢复后 DELETE 成功,节点 0/2 的 HEAD/GET 返回 404,节点 1 返回 503。DELETE 成功由脚本已通过的 204 断言确定,原输出没有单独保存该次 DELETE 响应。此次失败如实保留,不能把后来的成功写回原记录。 - -复核发现,`ListBuckets` 以及位于源池的现存版本 GET,不能证明每个协调节点对另一池的读取连接已经恢复。随后进行了旧基线与候选的同拓扑对照,探测的是从未写入过的随机 UUID,并且在这些探测之前没有执行任何 DELETE: - -| 高时间分辨率对照 | 桶列表和现存版本 | 从未写入版本的 HEAD/GET | 观察到的恢复窗口 | -| --- | --- | --- | --- | -| `20502a2f`,旧基线 `89637554d` | 三节点均为 200 | 节点 0/2 为 404,节点 1 为 503 SlowDownRead,连续 16 组 | 从重启后的观察循环起算约 1.60–2.50 秒 | -| `246aaf77`,候选 `41aa84609` | 三节点均为 200 | 同样是节点 1 的 503,连续 11 组 | 约 1.67–2.30 秒 | - -两边在缺失版本全节点连续三轮返回 404 后执行 DELETE,均得到 204、全节点 HEAD/GET 404、目标 UUID 在八盘均不存在且其他版本可读。另有两次较低时间分辨率诊断未捕捉到窗口,同样保留;这四次诊断不计入三次升级验收。 - -这给出了基线在零 DELETE 下的正向复现,证明原恢复条件不足。`getLatestObjectInfoWithIdx` 的读选择函数与基线文本相同:现存副本可以遮蔽另一池的读错误;缺失版本则必须确认所有池,不可读时返回 503。Claude 复核后同意修正实验准备条件并继续验收,明确反对把这一路径的 503 改成 404。最初失败没有瞬时 RPC 全貌,不能逐请求追溯每条连接;这些对照也不能给 `83676ca2` 归因。 - -修订后的验收在升级/恢复后逐节点探测从未写入的 UUID,记录首次全 404 时刻,要求连续三轮全 404;并列保存各节点 `admin info` 的全盘状态。最多等待 30 秒,超时仍失败,DELETE 之后仍严格要求 204/404,不把 503 纳入通过条件。后续失败保留实验资源供即时取证,再受控清理。 - -修订准备条件后的独立停池验收 `40a59b3b` 通过:离线 DELETE `503 SlowDownRead`,源四盘版本保留;恢复门约 1.48 秒完成,DELETE `204`,三节点 HEAD/GET `404`,目标在八盘均不存在,另一版本仍可读。恢复早期 `admin info` 中也记录到了各节点不同的离线盘视图,最后恢复为全盘正常。 - -完整升级验收随后实际进行了三次尝试: - -| 尝试 | 运行 | 结果与证据边界 | -| --- | --- | --- | -| 1 | `83f88c59` | 准备失败,未启动候选。旧版 rebalance 报 Completed、搬移版本数为 0;源池占用约 6.5%,到平均空闲目标的差值约 3.13%,落入代码既有 5% 容差。增加造数从 64 到 192 个 2 MiB 版本后再试;本次仍计入五次总尝试上限。 | -| 2,第一轮有效运行 | `ea58d0c5` | 通过。实际 rebalance 中断产生跨八盘的重复版本;停机复制同一数据供旧版对照。旧版 DELETE204 后仍可读;候选 DELETE204 后四节点立即及后续固定间隔 HEAD/GET 均404,八盘目标清除、另一版本四节点可读;旧 ILM/生命周期编辑和真实缓存 v9→v8 通过。 | -| 3,第二轮有效运行 | `2059bc6b` | **候选失败,阻断合并。** 两阶段逐节点缺失版本连续三轮404、admin info全盘ok之后,DELETE明确204(约12.98ms);节点2随后的HEAD503/GET503 SlowDownRead,另外三节点404;八盘快照均无目标,首次重试及后续采样全404,其他版本四节点可读。首次失败保留,不因重试恢复改判。 | - -第三次尝试发生后停止剩余验收,保留原容器、卷、元数据与响应时序进行诊断。不能把四节点顺序探测中的“节点2异常”直接解释为永久节点故障:它也可能与采样时间有关。随后在保留环境中,对三个额外重复版本做并发、不同顺序的“刚删 UUID / 从未写入 UUID”对照,候选稳定期均为204/404,未复现;旧版克隆数据的双次 DELETE 对照则遇到 `SlowDownWrite`,没有完成其全部断言。这些是诊断结果,不补入有效升级通过计数,也不证明第三次尝试已解释。 - -为观察逐盘返回,另在独立临时工作树编译仅增加日志的诊断二进制,**没有进入 PR**。它证明了第二层准备检查盲点:`getObjectFileInfo` 的四个响应信号中,可以只有两个实际 `file version not found`,其余两个是被跳过的盘所保留的 `errDiskOngoingReq`;`objectQuorumFromMeta` 的预期读 quorum 为2,因而仍可返回404。同期 `admin info` 汇集各服务器本地盘态为ok,不能证明请求节点到各盘的路径都可用。一次诊断 DELETE 的逐盘返回为 `[nil, nil, drive not found, drive not found]`,达不到写 quorum 3,故返回 `SlowDownWrite`。 - -Claude 撤回了此前“逐节点三轮404已是最强全池准备条件”的表述,同意这只能证明读 quorum,不能证明全盘可达或写 quorum。诊断给出了候选机制,但**第三次尝试失败瞬间没有逐请求逐盘日志,仍不足以确定其具体原因**;不能把后来稳定期的成功、诊断中的配额不足,或对错误名的解释当成该次故障的直接证据。 - -## 10. 首次执行的停止位置与恢复资料 - -首次执行停止时,代码为 `41aa84609`;后续提交只记录执行,不改变已测试的生产代码。当时有效升级运行是一通过、一失败,未满足三次有效运行全部通过的约定;总尝试3次,没有通过继续补跑消耗剩余次数来冲淡失败。`2059bc6b` 和先前的 `83676ca2` 均保持 **OPEN**。Claude 与 Codex 当时的裁定是 **NO-GO for merge**,没有把证据不足升级为“已修复”“既有问题”或“暂态”。后续收尾见第 11 节;实际合并状态以 [#188](https://github.com/pgsty/silo/pull/188) 为准。 - -主工作区原五文件的完整内容、二进制补丁和 SHA-256 已归档;另有可达 Git 引用 `refs/archive/access-tiering-main-five-files-20260915`,指向快照 `c7fbfc6ada0f0f2abcbe8c0a9681f07e12dafe52`。逐文件核验快照与原已审内容一致。首次停止时因验收未通过,没有执行五文件恢复,也没有移动或改写独立 IAM 提交 `ebc9937d9`。当时唯一待交付候选在 PR 分支,旧变体冻结等待验收裁定。 - -本机执行资料归档在 `~/.codex/outputs/silo-access-revert-assessment-20260915/final-execution/`:保存了每次尝试、旧/新基线对照、二进制 SHA-256、准备条件、源/目的全盘元数据、诊断补丁、独立评审意见,以及受控清理记录。原始失败记录不覆写;保存的诊断卷内容用于继续调查,不是生产数据或发布制品。 - -继续推进需要一次能区分机制的取证:在条件可控的升级实验中,于失败请求当时记录每池每盘的真实应答和错误类型,并同时读刚删版本与从未写入版本;明确区分盘面不同步、请求节点的不可用路径与其他原因。若四盘均真实应答仍返回503,应沿错误归约/元数据路径定位;若盘不可用,应查明连接或初始化状态,并验证准备条件。后续成功本身不能关闭本次失败,更不能通过把不可判定的503改成404来满足验收。正式发布、制品和生产部署继续作为独立交付。 - -## 11. 收尾复核:准备检查、可控机制与独立新轮次 - -后续收尾没有继续修改生产代码。旧基线 `89637554d` 与候选 `41aa84609` 使用各自的独立诊断构建,仅对测试桶记录逐盘应答;这些日志补丁没有进入 PR。第一轮完整诊断 `53ebe161` 通过但未复现503,仅作为一个样本保留。第二轮 `c951f762` 得到了不同的、可直接解释的失败:两个池的删除调用均记录 `quorum=3 errs=[nil,nil,nil,drive not found]`,DELETE204、所有读样本404,但八盘快照的盘3、7仍保留目标版本。它符合既有写 quorum 契约,并正向证明旧准备门会放行尚未完成挂盘的协调节点;它没有复现或解释 `2059bc6b` 那一次请求。 - -审查还纠正了两项推断:后续诊断进程在04:19的重连日志不能用于解释03:56的原失败;错误归约按具体错误值计数,不能把“最高同值计数不足”简单等同于“实际应答盘数不足”。原失败之后的全盘快照也不是失败瞬间的原子快照。这些界限继续保留。 - -准备检查改用服务器已有的 storage trace:从每个 S3 节点,对各自唯一、从未写入的对象执行 `GetObjectTagging`,该路径等待所有盘;按唯一对象名和后端节点、盘路径匹配真实 `storage.ReadVersion` 应答。四节点双池需要每轮32条实际缺失应答,连续三轮完成才满足准备条件;单纯404或 `admin info` 的本地盘状态不够。探针不创建对象,不改变服务器读写语义。trace 输出是多行 JSON 对象流,按流解析;缺失 trace 证据会使检查失败,不能据此假定对应盘健康。 - -为了比较相同的跨池写入负载,停止真实 rebalance 夹具后复制两份相同数据。旧基线使用 #178 已有的 `If-Match` 条件 DELETE,候选使用普通指定版本 DELETE,二者都处理同一目标的两个池副本。两臂分别完成96次“已删版本/从未写入版本”的 HEAD/GET 对照,均404,目标八盘均清除。准备检查分别耗时约0.60秒和17.08秒。这是一对匹配样本,没有观察到候选专属差异,不是吞吐比较,也不能排除所有可能的故障机制。 - -`f55967b7` 另用明确制造的重复副本夹具完成因果实验,而非冒充真实 rebalance:pool0 的四盘由一个节点承载并保留 namespace 锁;pool1 的四盘分布到另外四个节点。先停止 pool1 一盘,DELETE204 后直接确认只有该盘残留目标版本。重新接入这份副本,再停止 pool1 两块已删除该版本的盘,保留“一盘返回版本、一盘返回缺失”的读视图。三个被测协调节点的已删版本均返回 `503 SlowDownRead`,从未写入版本均返回404;同一失败请求的逐盘记录明确为 `[drive not found, drive not found, file version not found, nil]`,没有足够的同值应答达到读 quorum。整个实验没有丢失 pool0 的 namespace 锁,也没有将不确定状态改为404。 - -该因果实验的部分节点重启未在35秒内恢复全部40条访问路径,原实验因此仍记 FAIL;正向机制观察与这个恢复失败分开记录。随后协调重启全部五节点,独立恢复检查通过,五节点 HEAD/GET 均404。这不是滚动恢复已通过的声明,也不追溯原 `2059bc6b` 的逐盘状态。可控复现确定的是故障机制类别,原单次实例继续 **OPEN**。 - -Claude 复核上述证据后同意显式开启 **R-Upgrade-2**:原轮次的一通过、一失败和总尝试3次保持原样,不合并计数,不静默重置。新轮使用未经诊断修改的 `41aa84609` 二进制,要求三次有效运行全部通过、最多五次总尝试;任何新503或数据不变量失败仍须停止。每次 DELETE 前后均检查32条路径,响应后的即时 HEAD/GET 先于后置准备检查执行,避免等待掩盖短暂错误。 - -| 新轮次运行 | 完整运行耗时 | 升级后32路径准备耗时 | 验收结果 | -| --- | --- | --- | --- | -| `8eb016db` | 90.20秒 | 17.04秒 | PASS | -| `2a2b7b64` | 80.83秒 | 0.62秒 | PASS | -| `371e7b9f` | 96.28秒 | 0.60秒 | PASS | - -三次均实际走到候选阶段:DELETE204,所有即时及后续 HEAD/GET 样本404,目标在八盘均不存在,其他版本从四节点读回;旧配置、普通生命周期规则编辑及真实缓存 v9→v8 均通过。删除前后各三轮32路径检查也全部通过。准备时间有明显波动,应验证访问路径,不能用固定等待秒数代替检查。这些结果满足修正准备条件后的有界验收;不把有限样本写成“历史503已消失”,不把不同阶段的诊断通过计入新轮次。 - -新轮仍绑定生产代码 `41aa84609`(Linux 二进制 SHA-256 `28e1339d630a22fa5a0e4659b6182224e81f6cd7cd4079856534390e40a697b1`),后续仅更新文档。合并前须核对源码等价性和最终 CI,按第8节的恢复纪律处理旧五文件,保留独立 IAM 提交。原 `2059bc6b`/`83676ca2`、部分重启恢复边界、单 VM/tmpfs、未验证全部不同重复 UUID 和整份运行时统计守恒继续可见;不为此新增自动搬回、清理服务或读错误降级。 - -本轮详细资料位于原归档的 `final-execution/closure-20260915/`,包括匹配对照、同请求逐盘日志、`qualification-summary.json`、独立 R-Upgrade-2 账本、诊断补丁和完整卷归档。原失败现场、后续对照及恢复后的卷分别标注时点。临时实验资源在归档验证后清理;这些资料与正式发布制品区分管理。 diff --git a/docs/investigations/go127-stack-evidence.json b/docs/investigations/go127-stack-evidence.json deleted file mode 100644 index 7113ef476..000000000 --- a/docs/investigations/go127-stack-evidence.json +++ /dev/null @@ -1,2754 +0,0 @@ -{ - "recorded_utc": "2026-09-09T10:06:09.493712+00:00", - "scope": "Local PGSTY stack Go 1.27 audit and fixes; no external deployment or publication", - "repositories": { - "silo": { - "path": "/Users/vonng/.codex/worktrees/4224/silo", - "base_commit": "d1105bbb3d4a0afa33b3a4ac11b821235038ed0e", - "branch": "codex/investigate-oidc-154", - "tracked_changes": [ - "README.md", - "cmd/grid.go", - "cmd/utils.go", - "internal/config/etcd/etcd.go", - "internal/crypto/crypto.go" - ], - "untracked": [ - "cmd/tls_defaults_test.go", - "docs/investigations/go127-stack-evidence.json", - "docs/investigations/go127-stack.md", - "docs/investigations/issue-154.md", - "docs/investigations/issue-154/admin-check.go", - "docs/investigations/issue-154/cert-roots.go", - "docs/investigations/issue-154/evidence.json", - "docs/investigations/issue-154/fixture.go", - "docs/investigations/issue-154/linux-evidence.json", - "docs/investigations/issue-154/openid-default-curves.patch", - "docs/investigations/issue-154/probe.go", - "docs/investigations/issue-154/run-linux.py" - ], - "go_mod_sha256": "f8182f00d130b89bd794041cb804ac3255ce7b035c0b73f8e6ca7094d6ea4035", - "go_sum_sha256": "6913c1a5d63af9f1cc4b21205bf45a98bd3f53855e4aa49dd3eccac06cd6ef19", - "dependencies_changed": false, - "modified_go_files": { - "cmd/grid.go": "570c10a301e4ed3c842c1811711df99021cbeafa7142a97e287df0f78175aae9", - "cmd/utils.go": "c84727b88ac0b7e1442bce1c81c719518dabdbd8fca5002144329070ea98e34e", - "internal/config/etcd/etcd.go": "0c29792e070d09f05b83effb639b324148ea56cc709077dcb6892a3a37c16d42", - "internal/crypto/crypto.go": "c3bf12d2fe9d998697a9a3aa6084b83d50d84d8af06c2af0e8598326e7ddbaf9", - "cmd/tls_defaults_test.go": "1a1ae148abd218881e841b62f9cc38ff637618e3df172b107ac9e59e0912b5bd" - } - }, - "silo-pkg": { - "path": "/Users/vonng/.codex/worktrees/4224/silo-pkg", - "base_commit": "a92c54d7ad910060dd6053c2fc38d41f3229bcfe", - "branch": "codex/go127-compat-20260909", - "tracked_changes": [ - "README.md" - ], - "untracked": [ - "env/web_env_tls_test.go" - ], - "go_mod_sha256": "0371c50986702312701ab3850dabcd2442b53e7e073d42168dfea5b38a5baff3", - "go_sum_sha256": "a73d65b976a5b5ac99d0f13bcba694662fee735fdad8a15107ef3a1be9ab7870", - "dependencies_changed": false, - "modified_go_files": { - "env/web_env_tls_test.go": "98d30a93d3ca50cf8fb30a6eb72cb6bd95ad6db90e398643af093cf0be134693" - } - }, - "mc": { - "path": "/Users/vonng/.codex/worktrees/4224/mc", - "base_commit": "fcd5cad8247ffac68c075adec43f02a5f15c16c6", - "branch": "codex/go127-compat-20260909", - "tracked_changes": [ - "README.md" - ], - "untracked": [ - "cmd/tls_defaults_test.go" - ], - "go_mod_sha256": "288ebd823a98033d5c63445e77d274f7407afede82ad240368ba3f06bfbada58", - "go_sum_sha256": "3dee1265485779625830593b17af6fa954eba7394f874db664d3938f5b66971f", - "dependencies_changed": false, - "modified_go_files": { - "cmd/tls_defaults_test.go": "8caedb0be3ac94b900ccaf94c2039569fb6d6d859a4c79a7ad272c8e446927f0" - } - }, - "silo-console": { - "path": "/Users/vonng/.codex/worktrees/4224/silo-console", - "base_commit": "c103d08ec36aab8e08ba091d77b639158ce9f18f", - "branch": "codex/go127-compat-20260909", - "tracked_changes": [ - "README.md" - ], - "untracked": [ - "api/tls_defaults_test.go" - ], - "go_mod_sha256": "1665c8c4ab2c63ce8c36e1391f254952ba460de46e79195e5c5b7acbcdfe02e4", - "go_sum_sha256": "7211f69c6c5d25ed923649dfa19eb571b2a16a71ab3d0935be50b2e57f9028e9", - "dependencies_changed": false, - "modified_go_files": { - "api/tls_defaults_test.go": "1f270ebe7cce540ec56348d4c5dbcf93be73beea60861907ca4425dc35986b27" - } - } - }, - "linux_server_build": { - "go": "go1.27.1", - "sha256": "7892ce222c61a6e362291a303c0d9e9a361f866cc398f7ded382d2e61bd21d49", - "settings": [ - "build\t-buildmode=exe", - "build\t-compiler=gc", - "build\t-tags=kqueue", - "build\tCGO_ENABLED=0", - "build\tGOARCH=arm64", - "build\tGOOS=linux", - "build\tGOARM64=v8.0", - "build\tvcs=git", - "build\tvcs.revision=d1105bbb3d4a0afa33b3a4ac11b821235038ed0e", - "build\tvcs.time=2026-09-09T06:55:35Z", - "build\tvcs.modified=true" - ], - "dependencies": [ - "dep\taead.dev/mem\tv0.2.0\th1:ufgkESS9+lHV/GUjxgc2ObF43FLZGSemh+W+y27QFMI=", - "dep\taead.dev/minisign\tv0.3.0\th1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=", - "dep\taead.dev/mtls\tv0.3.0\th1:a+C0t15Y9SRX6qP1EqmQFZ4ZSMm88TPvNDymasu4ahQ=", - "dep\tcel.dev/expr\tv0.25.2\th1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=", - "dep\tcloud.google.com/go\tv0.123.0\th1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=", - "dep\tcloud.google.com/go/auth\tv0.20.0\th1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=", - "dep\tcloud.google.com/go/auth/oauth2adapt\tv0.2.8\th1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=", - "dep\tcloud.google.com/go/compute/metadata\tv0.9.0\th1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=", - "dep\tcloud.google.com/go/iam\tv1.5.3\th1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=", - "dep\tcloud.google.com/go/monitoring\tv1.24.3\th1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=", - "dep\tcloud.google.com/go/storage\tv1.61.3\th1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=", - "dep\tfilippo.io/edwards25519\tv1.2.0\th1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore\tv1.22.0\th1:aokoqcHvaGjiM3VpjKDfMMnF/8epJ+Q1HLJ7CudztqE=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity\tv1.14.0\th1:CU4+EJeJi3TKYWEcYuSdWsjzw0nVsK/H0MSQOiPcymU=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/internal\tv1.12.0\th1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob\tv1.6.4\th1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM=", - "dep\tgithub.com/Azure/go-ntlmssp\tv0.1.1\th1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=", - "dep\tgithub.com/AzureAD/microsoft-authentication-library-for-go\tv1.7.2\th1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp\tv1.33.0\th1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric\tv0.55.0\th1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping\tv0.55.0\th1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=", - "dep\tgithub.com/IBM/sarama\tv1.45.1\th1:nY30XqYpqyXOXSNoe2XCgjj9jklGM1Ye94ierUb1jQ0=", - "dep\tgithub.com/VividCortex/ewma\tv1.2.0\th1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=", - "dep\tgithub.com/acarl005/stripansi\tv0.0.0-20180116102854-5a71ef0e047d\th1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=", - "dep\tgithub.com/alecthomas/participle\tv0.7.1\th1:2bN7reTw//5f0cugJcTOnY/NYZcWQOaajW+BwZB5xWs=", - "dep\tgithub.com/apache/thrift\tv0.24.0\th1:zy31L1a49QTNB2bG1BBfMXol3yJrTH975G3pPubQVLQ=", - "dep\tgithub.com/aymanbagabas/go-osc52/v2\tv2.0.1\th1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=", - "dep\tgithub.com/beevik/ntp\tv1.5.0\th1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=", - "dep\tgithub.com/beorn7/perks\tv1.0.1\th1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=", - "dep\tgithub.com/buger/jsonparser\tv1.1.2\th1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=", - "dep\tgithub.com/cespare/xxhash/v2\tv2.3.0\th1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=", - "dep\tgithub.com/charmbracelet/bubbles\tv1.0.0\th1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=", - "dep\tgithub.com/charmbracelet/bubbletea\tv1.3.10\th1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=", - "dep\tgithub.com/charmbracelet/colorprofile\tv0.4.3\th1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=", - "dep\tgithub.com/charmbracelet/harmonica\tv0.2.0\th1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=", - "dep\tgithub.com/charmbracelet/lipgloss\tv1.1.0\th1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=", - "dep\tgithub.com/charmbracelet/x/ansi\tv0.11.8\th1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=", - "dep\tgithub.com/charmbracelet/x/cellbuf\tv0.0.15\th1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=", - "dep\tgithub.com/charmbracelet/x/term\tv0.2.2\th1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=", - "dep\tgithub.com/cheggaaa/pb\tv1.0.30\th1:NylhgqJfXx3JVBGx6ywsXuhpz8caSMPmLArXyAv1bwU=", - "dep\tgithub.com/clipperhouse/displaywidth\tv0.11.0\th1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=", - "dep\tgithub.com/clipperhouse/uax29/v2\tv2.7.0\th1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=", - "dep\tgithub.com/cncf/xds/go\tv0.0.0-20260202195803-dba9d589def2\th1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=", - "dep\tgithub.com/coreos/go-oidc/v3\tv3.21.0\th1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM=", - "dep\tgithub.com/coreos/go-semver\tv0.3.1\th1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=", - "dep\tgithub.com/coreos/go-systemd/v22\tv22.7.0", - "=>\tgithub.com/coreos/go-systemd/v22\tv22.6.0\th1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=", - "dep\tgithub.com/cosnicolaou/pbzip2\tv1.0.6\th1:FYF6b2j4X4q3hZezd2AoUN/emLCtH/MbDGwJjiOacak=", - "dep\tgithub.com/davecgh/go-spew\tv1.1.2-0.20180830191138-d8f796af33cc\th1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=", - "dep\tgithub.com/dchest/siphash\tv1.2.3\th1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=", - "dep\tgithub.com/docker/go-units\tv0.5.0\th1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=", - "dep\tgithub.com/dustin/go-humanize\tv1.0.1\th1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=", - "dep\tgithub.com/eapache/go-resiliency\tv1.7.0\th1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=", - "dep\tgithub.com/eapache/go-xerial-snappy\tv0.0.0-20230731223053-c322873962e3\th1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=", - "dep\tgithub.com/eapache/queue\tv1.1.0\th1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=", - "dep\tgithub.com/eclipse/paho.mqtt.golang\tv1.5.1\th1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=", - "dep\tgithub.com/elastic/go-elasticsearch/v7\tv7.17.10\th1:TCQ8i4PmIJuBunvBS6bwT2ybzVFxxUhhltAs3Gyu1yo=", - "dep\tgithub.com/envoyproxy/go-control-plane/envoy\tv1.37.0\th1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=", - "dep\tgithub.com/envoyproxy/protoc-gen-validate\tv1.3.3\th1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=", - "dep\tgithub.com/fatih/color\tv1.19.0\th1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=", - "dep\tgithub.com/fatih/structs\tv1.1.0\th1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=", - "dep\tgithub.com/felixge/fgprof\tv0.9.5\th1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=", - "dep\tgithub.com/felixge/httpsnoop\tv1.1.0\th1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=", - "dep\tgithub.com/fraugster/parquet-go\tv0.12.0\th1:1slnC5y2VWEOUSlzbeXatM0BvSWcLUDsR/EcZsXXCZc=", - "dep\tgithub.com/go-asn1-ber/asn1-ber\tv1.5.8\th1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=", - "dep\tgithub.com/go-jose/go-jose/v4\tv4.1.4\th1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=", - "dep\tgithub.com/go-ldap/ldap/v3\tv3.4.14\th1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=", - "dep\tgithub.com/go-logr/logr\tv1.4.4\th1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=", - "dep\tgithub.com/go-logr/stdr\tv1.2.2\th1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=", - "dep\tgithub.com/go-openapi/analysis\tv0.26.2\th1:Q6wOwXW8mcVAkpDFMshj/F4PlK2Fx86tmLJjZW4vyEs=", - "dep\tgithub.com/go-openapi/errors\tv0.22.8\th1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I=", - "dep\tgithub.com/go-openapi/jsonpointer\tv1.0.0\th1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=", - "dep\tgithub.com/go-openapi/jsonreference\tv1.0.1\th1:4zJ7AmYDKNmD3aSpfPnFNCFA5E80/xMHUNKgydaLh38=", - "dep\tgithub.com/go-openapi/loads\tv0.25.2\th1:+uNsDlRQfYtZTrh+3pdwampcAqZVPuBJW0IA82aZHII=", - "dep\tgithub.com/go-openapi/runtime\tv0.33.1\th1:jCvhI+wAdsn29byy+RgcPcg+j39YT6E304QOE/WqIVk=", - "dep\tgithub.com/go-openapi/runtime/server-middleware\tv0.33.1\th1:IAeKbwWnBnpsYTpuPVS8t73ZrPpKvRZnK2iJ2KJGUV0=", - "dep\tgithub.com/go-openapi/spec\tv1.0.0\th1:JtB/GHOj+eetjse6YvxqLze88oEekl/4uPBethvzRrA=", - "dep\tgithub.com/go-openapi/strfmt\tv0.27.0\th1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM=", - "dep\tgithub.com/go-openapi/swag\tv0.29.1\th1:C6EeWzUwQtcWEhE9eqBdUubGXxhWY4PlzHMLD7kLaiQ=", - "dep\tgithub.com/go-openapi/swag/cmdutils\tv0.29.1\th1:3DorPGfUdE80BogKY22EzoHBcHMrkVomZMoV7kS4ANY=", - "dep\tgithub.com/go-openapi/swag/conv\tv0.29.1\th1:AC4Eh/5c/eUDOUCzzsRC9ghmFgOSBHeRMGIngY0ZUGA=", - "dep\tgithub.com/go-openapi/swag/fileutils\tv0.29.1\th1:ZcPzMceVhU1WPbK6N1G6sNQKdd1CWJlf3cA08UHuoM0=", - "dep\tgithub.com/go-openapi/swag/jsonutils\tv0.29.1\th1:AFCxs0eQZ24/QyfhVHM2t49rMz7Vv3XCsZQI6yrNy+c=", - "dep\tgithub.com/go-openapi/swag/loading\tv0.29.1\th1:FCv5fG8UhTdDJa2R7w+5O9Ekpcbw7tt0nFWvmDKGBjc=", - "dep\tgithub.com/go-openapi/swag/mangling\tv0.29.1\th1:lHALtvYCdxVnRl4GrHmFPwfBTZYIObqdGNSKyu/8D6I=", - "dep\tgithub.com/go-openapi/swag/netutils\tv0.29.1\th1:IjIvdEP5duKcghFqJEPSUraRnkKYHoM65kTluTu+Jb4=", - "dep\tgithub.com/go-openapi/swag/pools\tv0.29.1\th1:NRogYxdEW9SjRM4mkAOji9iefO4MRXq3p/ZJcoQbUKg=", - "dep\tgithub.com/go-openapi/swag/stringutils\tv0.29.1\th1:1ykunK7iJQk1uOO7+oUH1ukbsK85fFCOiCFMOVSY+F0=", - "dep\tgithub.com/go-openapi/swag/typeutils\tv0.29.1\th1:Nzv9nhnlLCRBPQqfOX+7lB6Guju370or8StT+lIOf6M=", - "dep\tgithub.com/go-openapi/swag/yamlutils\tv0.29.1\th1:69w3tsBajm7MR/fejLy7HD/3J68Ys1SeeZMEzZ3w2sk=", - "dep\tgithub.com/go-openapi/validate\tv0.26.5\th1:Vm02dSmhevDx/4v4m8KAtMwffHGfq9wRLqICeebE/D4=", - "dep\tgithub.com/go-sql-driver/mysql\tv1.9.3\th1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=", - "dep\tgithub.com/go-viper/mapstructure/v2\tv2.5.0\th1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=", - "dep\tgithub.com/gobwas/httphead\tv0.1.0\th1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=", - "dep\tgithub.com/gobwas/pool\tv0.2.1\th1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=", - "dep\tgithub.com/gobwas/ws\tv1.4.0\th1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=", - "dep\tgithub.com/gogo/protobuf\tv1.3.2\th1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=", - "dep\tgithub.com/golang-jwt/jwt/v4\tv4.5.2\th1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=", - "dep\tgithub.com/golang-jwt/jwt/v5\tv5.3.1\th1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=", - "dep\tgithub.com/golang/protobuf\tv1.5.4\th1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=", - "dep\tgithub.com/golang/snappy\tv1.0.0\th1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=", - "dep\tgithub.com/gomodule/redigo\tv1.9.3\th1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=", - "dep\tgithub.com/google/pprof\tv0.0.0-20260709232956-b9395ee17fa0\th1:du0WGc8xSKq/++e0cglxhS/mXVqsR7+c7jLEi5Vqduw=", - "dep\tgithub.com/google/s2a-go\tv0.1.9\th1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=", - "dep\tgithub.com/google/shlex\tv0.0.0-20191202100458-e7afc7fbc510\th1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=", - "dep\tgithub.com/google/uuid\tv1.6.0\th1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=", - "dep\tgithub.com/googleapis/enterprise-certificate-proxy\tv0.3.18\th1:hvVi34VucdrV1IIsiWuqYM8kutw/92MxNEFxCJZEh0k=", - "dep\tgithub.com/googleapis/gax-go/v2\tv2.23.0\th1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE=", - "dep\tgithub.com/gorilla/websocket\tv1.5.4-0.20250319132907-e064f32e3674\th1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=", - "dep\tgithub.com/grafana/regexp\tv0.0.0-20250905093917-f7b3be9d1853\th1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM=", - "dep\tgithub.com/grpc-ecosystem/grpc-gateway/v2\tv2.30.0\th1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=", - "dep\tgithub.com/hashicorp/errwrap\tv1.1.0\th1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=", - "dep\tgithub.com/hashicorp/go-multierror\tv1.1.1\th1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=", - "dep\tgithub.com/hashicorp/go-uuid\tv1.0.3\th1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=", - "dep\tgithub.com/inconshreveable/mousetrap\tv1.1.0\th1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=", - "dep\tgithub.com/jcmturner/aescts/v2\tv2.0.0\th1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=", - "dep\tgithub.com/jcmturner/dnsutils/v2\tv2.0.0\th1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=", - "dep\tgithub.com/jcmturner/gofork\tv1.7.6\th1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=", - "dep\tgithub.com/jcmturner/gokrb5/v8\tv8.4.4\th1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=", - "dep\tgithub.com/jcmturner/rpc/v2\tv2.0.3\th1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=", - "dep\tgithub.com/jedib0t/go-pretty/v6\tv6.8.3\th1:yVSk5aemoYHCvcrtqyXklwqcgHQIQzmy/oUzFlmffSQ=", - "dep\tgithub.com/jessevdk/go-flags\tv1.6.1\th1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=", - "dep\tgithub.com/json-iterator/go\tv1.1.12\th1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=", - "dep\tgithub.com/juju/ratelimit\tv1.0.2\th1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=", - "dep\tgithub.com/klauspost/compress\tv1.20.0\th1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=", - "dep\tgithub.com/klauspost/cpuid/v2\tv2.4.0\th1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=", - "dep\tgithub.com/klauspost/crc32\tv1.3.0\th1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=", - "dep\tgithub.com/klauspost/filepathx\tv1.1.1\th1:201zvAsL1PhZvmXTP+QLer3AavWrO3U1NILWpniHK4w=", - "dep\tgithub.com/klauspost/pgzip\tv1.2.6\th1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=", - "dep\tgithub.com/klauspost/readahead\tv1.4.0\th1:w4hQ3BpdLjBnRQkZyNi+nwdHU7eGP9buTexWK9lU7gY=", - "dep\tgithub.com/klauspost/reedsolomon\tv1.13.3\th1:01GwnO2xoCSaM0ShP4qwl+FsHg3csFShC6Tu/RS1ji0=", - "dep\tgithub.com/kr/fs\tv0.1.0\th1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=", - "dep\tgithub.com/kylelemons/godebug\tv1.1.0\th1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=", - "dep\tgithub.com/lestrrat-go/blackmagic\tv1.0.4\th1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=", - "dep\tgithub.com/lestrrat-go/dsig\tv1.4.0\th1:g7LUjK8cT74A5DzBXJI5HzsJuLhoYN0Wzj4nuOMIrH8=", - "dep\tgithub.com/lestrrat-go/httpcc\tv1.0.1\th1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=", - "dep\tgithub.com/lestrrat-go/httprc/v3\tv3.0.6\th1:4FpLQ18KK/ypPbVU3NLWJNRvH3kcYiqKqWfKGqNWxxI=", - "dep\tgithub.com/lestrrat-go/jwx/v3\tv3.2.0\th1:Jb3zBASTSZXz7gzzSAfYqxXF8KejvKC4xWoePLQqXCA=", - "dep\tgithub.com/lestrrat-go/option/v2\tv2.0.0\th1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=", - "dep\tgithub.com/lib/pq\tv1.10.9\th1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=", - "dep\tgithub.com/lithammer/shortuuid/v4\tv4.2.0\th1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=", - "dep\tgithub.com/lucasb-eyer/go-colorful\tv1.4.1\th1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=", - "dep\tgithub.com/mattn/go-colorable\tv0.1.15\th1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=", - "dep\tgithub.com/mattn/go-ieproxy\tv0.0.12\th1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M=", - "dep\tgithub.com/mattn/go-isatty\tv0.0.24\th1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=", - "dep\tgithub.com/mattn/go-runewidth\tv0.0.29\th1:3oGF3R/S2N9DQ3ptftzVIvg2eicmojCzlwBEmqEPDfQ=", - "dep\tgithub.com/matttproud/golang_protobuf_extensions\tv1.0.4\th1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=", - "dep\tgithub.com/miekg/dns\tv1.1.73\th1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=", - "dep\tgithub.com/minio/cli\tv1.24.2\th1:J+fCUh9mhPLjN3Lj/YhklXvxj8mnyE/D6FpFduXJ2jg=", - "dep\tgithub.com/minio/colorjson\tv1.0.8\th1:AS6gEQ1dTRYHmC4xuoodPDRILHP/9Wz5wYUGDQfPLpg=", - "dep\tgithub.com/minio/console\tv1.7.6", - "=>\tgithub.com/pgsty/silo-console\tv0.0.0-20260908142700-c103d08ec36a\th1:aHLqQ7INozqGLEOB1tr+n/eKgrBhlQ20fHyLmeNu+ao=", - "dep\tgithub.com/minio/crc64nvme\tv1.1.1\th1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=", - "dep\tgithub.com/minio/csvparser\tv1.0.0\th1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=", - "dep\tgithub.com/minio/dnscache\tv0.1.1\th1:AMYLqomzskpORiUA1ciN9k7bZT1oB3YZN4cEIi88W5o=", - "dep\tgithub.com/minio/dperf\tv0.7.1\th1:eBwtaBBjuANwgUy1waWoS+wP+0i5fkXJOdGU2RXuDxo=", - "dep\tgithub.com/minio/filepath\tv1.0.0\th1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY=", - "dep\tgithub.com/minio/highwayhash\tv1.0.4\th1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4=", - "dep\tgithub.com/minio/kms-go/kes\tv0.3.1\th1:K3sPFAvFbJx33XlCTUBnQo8JRmSZyDvT6T2/MQ2iC3A=", - "dep\tgithub.com/minio/kms-go/kms\tv0.6.0\th1:oGdGUyjfCZwRIi7em0aj4wk+oOm7+4a0lzSZny7ZIDU=", - "dep\tgithub.com/minio/madmin-go/v3\tv3.0.110\th1:FIYekj7YPc430ffpXFWiUtyut3qBt/unIAcDzJn9H5M=", - "dep\tgithub.com/minio/mc\tv0.0.0-20251106162529-77f82e18b540", - "=>\tgithub.com/pgsty/mc\tv0.0.0-20260909015522-fcd5cad8247f\th1:JiL/FcsGMsAhA+Iv+0Jzk9VnEAVVv4HUNbR0cGf+/CE=", - "dep\tgithub.com/minio/md5-simd\tv1.1.2\th1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=", - "dep\tgithub.com/minio/minio-go/v7\tv7.3.1-0.20260828014306-0e78d3f18efe\th1:By2FKNSOUGLOeb0x4D7xJMHr8x/X1ZW8PG780SpKUwQ=", - "dep\tgithub.com/minio/mux\tv1.10.1\th1:grrK8SwRKbkNFE6qG7WAvFGH09bB46d5teOOtKfQ14s=", - "dep\tgithub.com/minio/pkg/v3\tv3.6.1\th1:gaNT80BS/iuIany5ylTkVmfN4s6UYY30OtImFv4GQA8=", - "dep\tgithub.com/minio/selfupdate\tv0.6.0\th1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=", - "dep\tgithub.com/minio/simdjson-go\tv0.4.5\th1:r4IQwjRGmWCQ2VeMc7fGiilu1z5du0gJ/I/FsKwgo5A=", - "dep\tgithub.com/minio/sio\tv0.4.3\th1:JqyID1XM86KwBZox5RAdLD4MLPIDoCY2cke2CXCJCkg=", - "dep\tgithub.com/minio/websocket\tv1.6.0\th1:CPvnQvNvlVaQmvw5gtJNyYQhg4+xRmrPNhBbv8BdpAE=", - "dep\tgithub.com/minio/xxml\tv0.0.3\th1:ZIpPQpfyG5uZQnqqC0LZuWtPk/WT8G/qkxvO6jb7zMU=", - "dep\tgithub.com/minio/zipindex\tv0.5.0\th1:QydEWJW+uAFMd5xmQa580bm7JtC5krpuAtARXIQr72U=", - "dep\tgithub.com/mitchellh/go-homedir\tv1.1.0\th1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=", - "dep\tgithub.com/modern-go/concurrent\tv0.0.0-20180306012644-bacd9c7ef1dd\th1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=", - "dep\tgithub.com/modern-go/reflect2\tv1.0.3-0.20250322232337-35a7c28c31ee\th1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=", - "dep\tgithub.com/muesli/ansi\tv0.0.0-20230316100256-276c6243b2f6\th1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=", - "dep\tgithub.com/muesli/cancelreader\tv0.2.2\th1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=", - "dep\tgithub.com/muesli/reflow\tv0.3.0\th1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=", - "dep\tgithub.com/muesli/termenv\tv0.16.0\th1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=", - "dep\tgithub.com/munnerz/goautoneg\tv0.0.0-20191010083416-a7dc8b61c822\th1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=", - "dep\tgithub.com/nats-io/nats.go\tv1.49.0\th1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=", - "dep\tgithub.com/nats-io/nkeys\tv0.4.15\th1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=", - "dep\tgithub.com/nats-io/nuid\tv1.0.1\th1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=", - "dep\tgithub.com/nats-io/stan.go\tv0.10.4\th1:19GS/eD1SeQJaVkeM9EkvEYattnvnWrZ3wkSWSw4uXw=", - "dep\tgithub.com/ncw/directio\tv1.0.5\th1:JSUBhdjEvVaJvOoyPAbcW0fnd0tvRXD76wEfZ1KcQz4=", - "dep\tgithub.com/nsqio/go-nsq\tv1.1.0\th1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=", - "dep\tgithub.com/oklog/ulid/v2\tv2.1.2\th1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec=", - "dep\tgithub.com/olekukonko/tablewriter\tv0.0.5\th1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=", - "dep\tgithub.com/pgsty/silo-pkg/v3\tv3.13.3\th1:d2xYTn4LXoWIAIjBlW/17wtA/Ut1ap29t+1ww4TFa8o=", - "dep\tgithub.com/philhofer/fwd\tv1.2.0\th1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=", - "dep\tgithub.com/pierrec/lz4/v4\tv4.1.29\th1:CDQY6qZOLI4DW0Nx6R1vRrifrCeQHnNXkMb0hZWXFjg=", - "dep\tgithub.com/pkg/browser\tv0.0.0-20240102092130-5ac0b6a4141c\th1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=", - "dep\tgithub.com/pkg/errors\tv0.9.1\th1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=", - "dep\tgithub.com/pkg/sftp\tv1.13.11\th1:0N92SLTB8JqASJB14ZLHHzFnBV8mG9zw4K7jghEFWuE=", - "dep\tgithub.com/pkg/xattr\tv0.4.12\th1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM=", - "dep\tgithub.com/posener/complete\tv1.2.3\th1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=", - "dep\tgithub.com/prometheus/client_golang\tv1.24.1\th1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=", - "dep\tgithub.com/prometheus/client_model\tv0.6.3\th1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo=", - "dep\tgithub.com/prometheus/common\tv0.71.0\th1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8=", - "dep\tgithub.com/prometheus/procfs\tv0.22.0\th1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics=", - "dep\tgithub.com/prometheus/prom2json\tv1.5.0\th1:WIcAOjLE1x476W3dUlmTL6E/e98CgVGuwwYusl6MPP8=", - "dep\tgithub.com/prometheus/prometheus\tv0.314.0\th1:YjsimqsIi6/mOtzZcrPEYUALO6zpfaht9O5sXqDz2vg=", - "dep\tgithub.com/puzpuzpuz/xsync/v3\tv3.5.1\th1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=", - "dep\tgithub.com/rabbitmq/amqp091-go\tv1.10.0\th1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=", - "dep\tgithub.com/rcrowley/go-metrics\tv0.0.0-20250401214520-65e299d6c5c9\th1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=", - "dep\tgithub.com/rivo/uniseg\tv0.4.7\th1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=", - "dep\tgithub.com/rjeczalik/notify\tv0.9.3\th1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=", - "dep\tgithub.com/rs/cors\tv1.11.1\th1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=", - "dep\tgithub.com/rs/xid\tv1.6.0\th1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=", - "dep\tgithub.com/safchain/ethtool\tv0.7.0\th1:rlJzfDetsVvT61uz8x1YIcFn12akMfuPulHtZjtb7Is=", - "dep\tgithub.com/secure-io/sio-go\tv0.3.1\th1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=", - "dep\tgithub.com/shirou/gopsutil/v3\tv3.24.5\th1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=", - "dep\tgithub.com/spiffe/go-spiffe/v2\tv2.7.0\th1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=", - "dep\tgithub.com/tidwall/gjson\tv1.19.0\th1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=", - "dep\tgithub.com/tidwall/match\tv1.2.0\th1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=", - "dep\tgithub.com/tidwall/pretty\tv1.2.1\th1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=", - "dep\tgithub.com/tinylib/msgp\tv1.6.4\th1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=", - "dep\tgithub.com/tklauser/go-sysconf\tv0.4.0\th1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=", - "dep\tgithub.com/tklauser/numcpus\tv0.12.0\th1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=", - "dep\tgithub.com/unrolled/secure\tv1.17.0\th1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=", - "dep\tgithub.com/valyala/bytebufferpool\tv1.0.0\th1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=", - "dep\tgithub.com/valyala/fastjson\tv1.6.10\th1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=", - "dep\tgithub.com/vbauerster/cupwriter\tv0.0.4\th1:9sBPe0uXWLZuWQU5lqVbhyFlxX6c09asST/YfatFAys=", - "dep\tgithub.com/vbauerster/mpb/v8\tv8.16.1\th1:gNYmwMip9xRWNGAiblZOgUNXWeU2P0NIGd5x0f8ffbc=", - "dep\tgithub.com/xdg/scram\tv1.0.5\th1:TuS0RFmt5Is5qm9Tm2SoD89OPqe4IRiFtyFY4iwWXsw=", - "dep\tgithub.com/xdg/stringprep\tv1.0.3\th1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4=", - "dep\tgithub.com/xo/terminfo\tv1.0.0\th1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY=", - "dep\tgithub.com/zeebo/xxh3\tv1.1.0\th1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=", - "dep\tgo.etcd.io/etcd/api/v3\tv3.7.1\th1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E=", - "dep\tgo.etcd.io/etcd/client/pkg/v3\tv3.7.1\th1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mfFv4=", - "dep\tgo.etcd.io/etcd/client/v3\tv3.7.1\th1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg=", - "dep\tgo.opentelemetry.io/auto/sdk\tv1.2.1\th1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=", - "dep\tgo.opentelemetry.io/contrib/detectors/gcp\tv1.44.0\th1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=", - "dep\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\tv0.70.0\th1:oECp5f+hN7nkwjU/8BxQ/q23bGPb8FIrD839owX222E=", - "dep\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\tv0.70.0\th1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI=", - "dep\tgo.opentelemetry.io/otel\tv1.45.0\th1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=", - "dep\tgo.opentelemetry.io/otel/metric\tv1.45.0\th1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=", - "dep\tgo.opentelemetry.io/otel/sdk\tv1.45.0\th1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=", - "dep\tgo.opentelemetry.io/otel/sdk/metric\tv1.45.0\th1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=", - "dep\tgo.opentelemetry.io/otel/trace\tv1.45.0\th1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=", - "dep\tgo.uber.org/atomic\tv1.11.0\th1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=", - "dep\tgo.uber.org/multierr\tv1.11.0\th1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=", - "dep\tgo.uber.org/zap\tv1.28.0\th1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=", - "dep\tgo.yaml.in/yaml/v3\tv3.0.5\th1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=", - "dep\tgoftp.io/server/v2\tv2.0.3\th1:iz6Gxj7f2SFQVxrj0s1is+gueE6O9yTc+Ab0vtQ6Zn4=", - "dep\tgolang.org/x/crypto\tv0.56.0\th1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=", - "dep\tgolang.org/x/net\tv0.58.0\th1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=", - "dep\tgolang.org/x/oauth2\tv0.36.0\th1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=", - "dep\tgolang.org/x/sync\tv0.22.0\th1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=", - "dep\tgolang.org/x/sys\tv0.47.0\th1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=", - "dep\tgolang.org/x/term\tv0.45.0\th1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=", - "dep\tgolang.org/x/text\tv0.41.0\th1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=", - "dep\tgolang.org/x/time\tv0.15.0\th1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=", - "dep\tgoogle.golang.org/api\tv0.290.0\th1:eMw0Xo+IfbbMlKmW7aHvpyQRv9RCXuWx/vs8AD+0x9A=", - "dep\tgoogle.golang.org/genproto\tv0.0.0-20260319201613-d00831a3d3e7\th1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=", - "dep\tgoogle.golang.org/genproto/googleapis/api\tv0.0.0-20260831171406-18b4a7587f8a\th1:i3TAXhpKc7TUP1VAPiBBrv45kamjoizCC3rOC0cAbOs=", - "dep\tgoogle.golang.org/genproto/googleapis/rpc\tv0.0.0-20260831171406-18b4a7587f8a\th1:3Dnd1cDaZlB68lziofO+bJXpjOy8UfRv8Unt+yH8tQ4=", - "dep\tgoogle.golang.org/grpc\tv1.83.2\th1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=", - "dep\tgoogle.golang.org/protobuf\tv1.36.12\th1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=", - "dep\tgopkg.in/ini.v1\tv1.67.3\th1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw=", - "dep\tgopkg.in/yaml.v2\tv2.4.0\th1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=" - ] - }, - "linux_container": { - "image_id": "sha256:307af7711e2e04ab75759cb42a1eef45c43c4404894c0e30dd19f742b107b922", - "kind": "pre-existing generic Debian 12 base", - "network": "none", - "published_ports": [], - "server_or_console_image": false - }, - "linux_integration": [ - { - "case": "allpaths-add", - "binary": "candidate-go127", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "add_ok": true, - "add_reset": false, - "events": [ - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - }, - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Silo" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Silo" - } - ] - }, - { - "case": "allpaths-compat-login", - "binary": "candidate-go127", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "oauth": { - "callback_status": 200, - "login_status": 204, - "buckets_status": 200, - "session_cookie": true - }, - "bad-signature": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "bad-audience": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "events": [ - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Silo" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Silo" - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - }, - { - "alpn": null, - "bytes_read": 269, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-signature", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Silo" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-audience", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - } - ] - }, - { - "case": "allpaths-tls13-login", - "binary": "candidate-go127", - "mode": "normal", - "godebug": null, - "tls13": true, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "oauth": { - "callback_status": 200, - "login_status": 204, - "buckets_status": 200, - "session_cookie": true - }, - "bad-signature": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "bad-audience": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "events": [ - { - "alpn": null, - "bytes_read": 1513, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Silo" - }, - { - "cipher": 4865, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Silo" - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 772, - "user_agent_family": "curl/7.88.1" - }, - { - "alpn": null, - "bytes_read": 1495, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-signature", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Silo" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-audience", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - } - ] - } - ], - "ca_default_comparison": [ - { - "explicit_ca": false, - "go": "go1.26.5", - "os": "darwin", - "trusted": false, - "binary": "cert-roots-126", - "module_go": "1.26.0", - "mode": "environment" - }, - { - "explicit_ca": true, - "go": "go1.26.5", - "os": "darwin", - "trusted": true, - "binary": "cert-roots-126", - "module_go": "1.26.0", - "mode": "explicit" - }, - { - "explicit_ca": false, - "go": "go1.26.5", - "os": "darwin", - "trusted": false, - "binary": "cert-roots-126", - "module_go": "1.26.0", - "mode": "legacy-platform" - }, - { - "explicit_ca": false, - "go": "go1.26.5", - "os": "darwin", - "trusted": false, - "binary": "cert-roots-126", - "module_go": "1.26.0", - "mode": "force-environment" - }, - { - "explicit_ca": false, - "go": "go1.27.1", - "os": "darwin", - "trusted": false, - "binary": "cert-roots-127", - "module_go": "1.26.0", - "mode": "environment" - }, - { - "explicit_ca": true, - "go": "go1.27.1", - "os": "darwin", - "trusted": true, - "binary": "cert-roots-127", - "module_go": "1.26.0", - "mode": "explicit" - }, - { - "explicit_ca": false, - "go": "go1.27.1", - "os": "darwin", - "trusted": false, - "binary": "cert-roots-127", - "module_go": "1.26.0", - "mode": "legacy-platform" - }, - { - "explicit_ca": false, - "go": "go1.27.1", - "os": "darwin", - "trusted": true, - "binary": "cert-roots-127", - "module_go": "1.26.0", - "mode": "force-environment" - }, - { - "explicit_ca": false, - "go": "go1.27.1", - "os": "darwin", - "trusted": true, - "binary": "cert-roots-app127", - "module_go": "1.27.1", - "mode": "environment" - }, - { - "explicit_ca": true, - "go": "go1.27.1", - "os": "darwin", - "trusted": true, - "binary": "cert-roots-app127", - "module_go": "1.27.1", - "mode": "explicit" - }, - { - "explicit_ca": false, - "go": "go1.27.1", - "os": "darwin", - "trusted": false, - "binary": "cert-roots-app127", - "module_go": "1.27.1", - "mode": "legacy-platform" - }, - { - "explicit_ca": false, - "go": "go1.27.1", - "os": "darwin", - "trusted": true, - "binary": "cert-roots-app127", - "module_go": "1.27.1", - "mode": "force-environment" - } - ], - "commands": [ - { - "repo": "silo", - "command": "CGO_ENABLED=0 GOWORK=off GOTOOLCHAIN=local go test -mod=readonly -tags kqueue -count=1 ./cmd -run ^Test(Outbound|Server)TLSKeyExchangeDefaults$", - "platform": "darwin/arm64", - "phase": "before fix", - "exit": 1, - "result": "All five outbound constructor cases incorrectly offered ML-KEM with tlsmlkem=0, against both TLS 1.2 and TLS 1.3 peers; the listener incorrectly accepted a PQ-only client." - }, - { - "repo": "silo", - "command": "same focused command", - "platform": "darwin/arm64", - "phase": "after fix", - "exit": 0, - "result": "ok 1.322s" - }, - { - "repo": "silo", - "command": "GOWORK=off GOTOOLCHAIN=local go test -mod=readonly -race -tags kqueue -count=1 ./cmd -run ^Test(Outbound|Server)TLSKeyExchangeDefaults$", - "platform": "darwin/arm64", - "exit": 0, - "result": "ok 2.533s" - }, - { - "repo": "silo", - "command": "CGO_ENABLED=0 GOWORK=off GOTOOLCHAIN=local go test -mod=readonly -tags kqueue -count=1 ./internal/crypto ./internal/config/... ./internal/http ./internal/grid", - "platform": "darwin/arm64", - "exit": 0 - }, - { - "repo": "silo", - "command": "locally cross-compiled server-cmd.test -test.run ^Test(Outbound|Server)TLSKeyExchangeDefaults$ -test.v -test.timeout 45s", - "platform": "linux/arm64", - "exit": 0, - "result": "20 outbound combinations and 4 inbound handshake attempts pass" - }, - { - "repo": "silo-pkg", - "command": "GOWORK=off GOTOOLCHAIN=local make test", - "go": "go1.27.1", - "platform": "darwin/arm64", - "exit": 0, - "result": "lint 0 issues; full race suite passes" - }, - { - "repo": "silo-pkg", - "command": "CGO_ENABLED=0 GOWORK=off GOTOOLCHAIN=local /path/to/go1.26.5/bin/go test -mod=readonly -tags kqueue -count=1 ./...", - "go": "go1.26.5", - "platform": "darwin/arm64", - "exit": 0 - }, - { - "repo": "mc", - "command": "CGO_ENABLED=0 GOWORK=off GOTOOLCHAIN=local go test -mod=readonly -tags kqueue -count=1 ./cmd", - "go": "go1.27.1", - "platform": "darwin/arm64", - "exit": 0, - "result": "ok 19.430s; includes S3 Select early-close cancellation" - }, - { - "repo": "mc", - "command": "golangci-lint v2.13.1 run --allow-serial-runners --build-tags kqueue --timeout=10m --config ./.golangci.yml", - "exit": 0, - "result": "0 issues; rerun after initial concurrent linter lock conflict" - }, - { - "repo": "silo-console", - "command": "CGO_ENABLED=0 GOWORK=off GOTOOLCHAIN=local go test -mod=readonly -tags kqueue -count=1 ./api/... ./pkg/...", - "go": "go1.27.1", - "platform": "darwin/arm64", - "exit": 0 - }, - { - "repo": "silo-console", - "command": "golangci-lint v2.13.1 run --build-tags kqueue --timeout=5m --config ./.golangci.yml", - "exit": 0, - "result": "0 issues" - }, - { - "repo": "silo", - "command": "GOWORK=off GOTOOLCHAIN=local make lint GOLANGCI=/Users/vonng/go/bin/golangci-lint", - "exit": 0, - "result": "Go lint 0 issues; typos absent, optional spelling check skipped. Used installed matching v2.13.1 after terminating only the task-owned redundant installer." - } - ], - "limits": [ - "Synthetic loopback fixtures, not the customer IdP or production network.", - "No full distributed TLS cluster or actual etcd, cloud, LDAP or Keycloak deployment.", - "No rendered browser, logout or production data migration test.", - "No Windows, old macOS or PowerPC runtime test.", - "No claim that all possible Go 1.27 regressions have been excluded." - ], - "post_review_validation": { - "recorded_utc": "2026-09-09T10:45:18.855578+00:00", - "phase": "After first Fable 5.1 Max review corrections, before final review and commit", - "first_review_verdict": "CHANGES REQUIRED: synthetic fixture routes broke rebrand CI after staging", - "corrections": [ - "Exclude docs/investigations from rebrand guard without changing its baseline", - "Use non-blocking ClientHello capture in all four test files", - "Remove unused internal HTTP curve override field", - "Clarify Keychain replacement and the SecP-only compatibility option", - "Label historical patch and worktree evidence as superseded or captured history" - ], - "rebrand_guard": { - "positive": { - "exit_code": 0, - "output": "compatibility manifest: imports=119 env=437 metrics=19 headers=86 routes=223 roots=1 grid=3 storage=15 policy=58 brand=180 sha256=373c66a6b05a19e2c2173034aa184b9237b249875fd33371cbda3414be7f6c34\nSilo rebrand compatibility baseline is unchanged\n" - }, - "negative_control": { - "exit_code": 1, - "output": "routes compatibility set changed\n + /codex-review-product-route\nexit status 1\n" - }, - "baseline_diff": "" - }, - "linux_server_build": { - "go": "go1.27.1", - "sha256": "e904a7df2fda47391a3a0725589332a6fc1b456411ffacca3e2dad326d744dcc", - "settings": [ - "build\t-buildmode=exe", - "build\t-compiler=gc", - "build\t-tags=kqueue", - "build\tCGO_ENABLED=0", - "build\tGOARCH=arm64", - "build\tGOOS=linux", - "build\tGOARM64=v8.0", - "build\tvcs=git", - "build\tvcs.revision=d1105bbb3d4a0afa33b3a4ac11b821235038ed0e", - "build\tvcs.time=2026-09-09T06:55:35Z", - "build\tvcs.modified=true" - ], - "runtime_source_sha256": { - "cmd/grid.go": "570c10a301e4ed3c842c1811711df99021cbeafa7142a97e287df0f78175aae9", - "cmd/utils.go": "c84727b88ac0b7e1442bce1c81c719518dabdbd8fca5002144329070ea98e34e", - "internal/config/etcd/etcd.go": "0c29792e070d09f05b83effb639b324148ea56cc709077dcb6892a3a37c16d42", - "internal/crypto/crypto.go": "c3bf12d2fe9d998697a9a3aa6084b83d50d84d8af06c2af0e8598326e7ddbaf9", - "internal/http/transports.go": "12e22830e98dd7391329b852eee8481981c74450b1cf7601c1d6623ee46b3981" - } - }, - "linux_integration": [ - { - "case": "allpaths-add", - "binary": "candidate-go127", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "add_ok": true, - "add_reset": false, - "events": [ - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "ua": "curl/7.88.1" - }, - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (linux; arm64; mode-server-xl-single; docker; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:16; vendor:Apple; family:; model:0x000; stepping:0; model_name:))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (linux; arm64; mode-server-xl-single; docker; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:16; vendor:Apple; family:; model:0x000; stepping:0; model_name:))" - } - ] - }, - { - "case": "allpaths-compat-login", - "binary": "candidate-go127", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "oauth": { - "callback_status": 200, - "login_status": 204, - "buckets_status": 200, - "session_cookie": true - }, - "bad-signature": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "bad-audience": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "events": [ - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (linux; arm64; mode-server-xl-single; docker; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:16; vendor:Apple; family:; model:0x000; stepping:0; model_name:))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (linux; arm64; mode-server-xl-single; docker; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:16; vendor:Apple; family:; model:0x000; stepping:0; model_name:))" - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "ua": "curl/7.88.1" - }, - { - "alpn": null, - "bytes_read": 269, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Python-urllib/3.11" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-signature", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Python-urllib/3.11" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (linux; arm64; mode-server-xl-single; docker; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:16; vendor:Apple; family:; model:0x000; stepping:0; model_name:))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-audience", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Python-urllib/3.11" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Go-http-client/1.1" - } - ] - }, - { - "case": "allpaths-tls13-login", - "binary": "candidate-go127", - "mode": "normal", - "godebug": null, - "tls13": true, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "oauth": { - "callback_status": 200, - "login_status": 204, - "buckets_status": 200, - "session_cookie": true - }, - "bad-signature": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "bad-audience": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "events": [ - { - "alpn": null, - "bytes_read": 1513, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Silo (linux; arm64; mode-server-xl-single; docker; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:16; vendor:Apple; family:; model:0x000; stepping:0; model_name:))" - }, - { - "cipher": 4865, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Silo (linux; arm64; mode-server-xl-single; docker; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:16; vendor:Apple; family:; model:0x000; stepping:0; model_name:))" - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 772, - "ua": "curl/7.88.1" - }, - { - "alpn": null, - "bytes_read": 1495, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Python-urllib/3.11" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-signature", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Python-urllib/3.11" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Silo (linux; arm64; mode-server-xl-single; docker; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:16; vendor:Apple; family:; model:0x000; stepping:0; model_name:))" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-audience", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Python-urllib/3.11" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "ua": "Go-http-client/1.1" - } - ] - } - ], - "tests": { - "mc": [ - { - "command": [ - "go", - "test", - "-mod=readonly", - "-race", - "-tags", - "kqueue", - "-count=1", - "-run", - "^TestClientTLSKeyExchangeDefaults$", - "./cmd" - ], - "exit_code": 0, - "seconds": 10.066, - "log": "/private/tmp/silo-go127-fable51-review.7ega9azp/after-review-mc-0.log" - } - ], - "silo-console": [ - { - "command": [ - "go", - "test", - "-mod=readonly", - "-race", - "-tags", - "kqueue", - "-count=1", - "-run", - "^TestOutboundTLSKeyExchangeDefaults$", - "./api" - ], - "exit_code": 0, - "seconds": 10.603, - "log": "/private/tmp/silo-go127-fable51-review.7ega9azp/after-review-silo-console-0.log" - } - ], - "silo-pkg": [ - { - "command": [ - "make", - "test" - ], - "exit_code": 0, - "seconds": 16.946, - "log": "/private/tmp/silo-go127-fable51-review.7ega9azp/after-review-silo-pkg-0.log" - }, - { - "command": [ - "/Users/vonng/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.5.darwin-arm64/bin/go", - "test", - "-mod=readonly", - "-count=1", - "-run", - "^TestWebEnvTLSKeyExchangeDefaults$", - "./env" - ], - "exit_code": 0, - "seconds": 1.457, - "log": "/private/tmp/silo-go127-fable51-review.7ega9azp/after-review-silo-pkg-1.log" - } - ], - "silo": [ - { - "command": [ - "go", - "test", - "-mod=readonly", - "-race", - "-tags", - "kqueue", - "-count=1", - "-run", - "^Test(Outbound|Server)TLSKeyExchangeDefaults$", - "./cmd" - ], - "exit_code": 0, - "seconds": 33.047, - "log": "/private/tmp/silo-go127-fable51-review.7ega9azp/after-review-silo-0.log" - }, - { - "command": [ - "go", - "test", - "-mod=readonly", - "-race", - "-tags", - "kqueue", - "-count=1", - "./internal/http" - ], - "exit_code": 0, - "seconds": 2.379, - "log": "/private/tmp/silo-go127-fable51-review.7ega9azp/after-review-silo-1.log" - } - ] - }, - "lint": [ - { - "repo": "silo", - "command": [ - "make", - "lint", - "GOLANGCI=/Users/vonng/go/bin/golangci-lint" - ], - "exit_code": 0, - "seconds": 63.943, - "log": "/private/tmp/silo-go127-fable51-review.7ega9azp/after-review-silo-lint.log" - }, - { - "repo": "mc", - "command": [ - "/Users/vonng/go/bin/golangci-lint", - "run", - "--build-tags", - "kqueue", - "--timeout=10m", - "--config", - "./.golangci.yml" - ], - "exit_code": 0, - "seconds": 15.794, - "log": "/private/tmp/silo-go127-fable51-review.7ega9azp/after-review-mc-lint.log" - }, - { - "repo": "silo-console", - "command": [ - "/Users/vonng/go/bin/golangci-lint", - "run", - "--build-tags", - "kqueue", - "--timeout=5m", - "--config", - "./.golangci.yml" - ], - "exit_code": 0, - "seconds": 29.207, - "log": "/private/tmp/silo-go127-fable51-review.7ega9azp/after-review-silo-console-lint.log" - } - ], - "independent_test_note": "The first review observed one shuffled certs.TestValidPairAfterWrite timing failure in an untouched separate test binary; three plain reruns, the same seed, and the full certs rerun passed. The post-correction make test full race suite also passes." - } -} diff --git a/docs/investigations/go127-stack.md b/docs/investigations/go127-stack.md deleted file mode 100644 index 27bd35f15..000000000 --- a/docs/investigations/go127-stack.md +++ /dev/null @@ -1,190 +0,0 @@ -# SILO stack: Go 1.27 compatibility audit - -> This is a dated investigation, with the source and runtime boundaries recorded -> below. It does not establish the current dependency pins or a later release. -> See [the current changelog](../../CHANGELOG.md) and -> [component matrix](https://silo.pgsty.com/compatibility/versions/). - - -2026-09-09. Scope: the maintained Server, silo-pkg, mcli, and Console. This extends -the [OIDC #154 investigation](issue-154.md) to other paths using the same TLS -configuration and to adjacent standard-library changes. It records local -validation performed before commit. No pushes, issue comments, releases, or -production changes were made during the audit. - -## Findings and changes - -| Component | Finding | Change | -| --- | --- | --- | -| Server, baseline `d1105bbb3d4a0afa33b3a4ac11b821235038ed0e` | Eight TLS configuration sites explicitly use the same ML-KEM-containing curve list, overriding `tlsmlkem=0` in Go 1.27. | Remove the eight assignments and obsolete `TLSCurveIDs` helper. Retain Go defaults in HTTP clients, replication, cloud client-certificate transport, both grid links, etcd, and the S3 listener. Add wire-level regression tests. | -| silo-pkg, baseline `a92c54d` | No built-in explicit PQ curve list. Web environment transport uses defaults; LDAP/OIDC accept caller configuration. | Add a real web-environment TLS regression test and document runtime-default selection. Keep the Go 1.26 library floor. | -| mcli, baseline `fcd5cad8` | S3/Admin transport and alias/TOFU dialer use default curves. No instance of the Server's override bug found. | Add real S3/Admin transport and alias-dialer handshake tests; update Go/TLS upgrade notes. | -| Console, baseline `c103d08ec` | IdP, SILO/STS, Prometheus, and webhook clients share a transport using defaults. The HTTPS listener explicitly uses only P-256. | Add real IdP/SILO-client handshake tests; update Go/TLS upgrade notes. Retain the existing listener policy. | - -The Server's general external HTTP transport is also used by OpenID discovery -and JWKS, identity plugins, notification/lambda checks, audit/log webhooks, and -S3 cloud backends. Fixing only the two OIDC callers would leave those other paths -affected. The broader correction supersedes the earlier OIDC-only candidate. - -The retained defaults follow Go's implementation rather than duplicating its -`GODEBUG` parser. Go 1.27 explicitly changed the interaction between manually -selected curves and the `tlsmlkem`/`tlssecpmlkem` default controls. See the -[Go TLS release notes](https://go.dev/doc/go1.27#crypto/tls). - -With no opt-out, default curves additionally include SecP256r1MLKEM768 and -SecP384r1MLKEM1024. With `GODEBUG=tlsmlkem=0`, hybrid exchanges are disabled for -default-configured TLS throughout the process. This is an intentional change -from the old fixed subset. `GODEBUG=tlssecpmlkem=0` disables just the SecP -hybrids while retaining X25519MLKEM768. TLS versions, cipher-suite policy, -certificate and hostname checks, client certificates, proxies, and HTTP/2 choices are not -relaxed by this patch. No automatic fallback after a TLS error is introduced. - -## Reproduction and regression evidence - -Before changing product code, the new Server test failed for all five tested -outbound constructors with `tlsmlkem=0`: general external HTTP, internode HTTP, -replication, cloud client certificates, and etcd. The server observed -`[X25519MLKEM768 X25519 P256 P384 P521]` in every case. Both TLS 1.2 and TLS 1.3 -peers reproduced the problem. The inbound Server also accepted a PQ-only client -despite the same opt-out. - -After the change, those tests pass on darwin/arm64 and linux/arm64. They exercise -20 outbound combinations (five constructors, two peer TLS versions, two debug -settings), plus inbound classical/PQ-only peers with the opt-out enabled and -disabled. The inbound opt-out case rejects a PQ-only peer while continuing to -accept P-256. The etcd test exercises its actual TLS configuration, not an etcd -cluster; the client-certificate test exercises construction/loading, not a full -mutual-authentication service. - -The mc, Console, and silo-pkg handshake tests pass without product code changes. -All use a trusted synthetic certificate and inspect a real ClientHello. They -check both disabling and retaining ML-KEM. Console also retains its existing -unknown-CA, hostname, and endpoint-scoping regression checks. - -A freshly source-built complete Linux Server then passed three isolated -integration scenarios using the existing synthetic IdP fixture: - -| Scenario | Result | -| --- | --- | -| ML-KEM-intolerant IdP + `tlsmlkem=0` | Discovery/JWKS, IAM, Console, and authenticated Admin calls succeed; login 204 and bucket list 200. | -| Normal TLS 1.3 IdP, no opt-out | Same successful login, token exchange, STS, session, and bucket-list chain. | -| Add OIDC to a running Server | Actual Admin API accepts the provider under the compatibility setting. | - -Both login scenarios reject modified JWT signatures and a wrong audience: no -session cookie is issued and authenticated bucket access returns 403. Login -currently reports these authentication failures as 500; that existing error -mapping is outside this TLS change. Curl in the same namespace returns HTTP/2 -200. The containers use a pre-existing generic Debian image, `--network none`, -and no published ports; no Server or Console image was downloaded or used. - -The fixture drives real Console HTTP APIs, not rendered browser interaction. -This is a conditional interoperability reproduction, not proof of the actual -customer ingress behavior. Grid's existing tests pass, but a production-style -distributed TLS cluster, external cloud providers, and real etcd/LDAP/Keycloak -deployments were not exercised. Disabling ML-KEM does not disable new ML-DSA -signature offers and does not repair an ingress that rejects those offers. - -## Other Go changes checked - -### macOS root certificates: a confirmed upgrade-visible change - -Using the same public synthetic CA and `certs.GetRootCAs`, fresh-process probes -produce the following results when `SSL_CERT_FILE` points at that CA and -`SSL_CERT_DIR` points at an empty directory: - -| Compiler / main module | CA from environment trusted? | Explicit CA argument trusted? | -| --- | --- | --- | -| Go 1.26.5 / `go 1.26.0` | No | Yes | -| Go 1.27.1 / `go 1.26.0` | No | Yes | -| Go 1.27.1 / `go 1.27.1` | Yes | Yes | - -The Go 1.26 module built by Go 1.27 carries -`DefaultGODEBUG=...x509sslcertoverrideplatform=0`; explicitly setting that option -to `1` enables the new behavior. A Go 1.27 application can explicitly set it to -`0` to recover the previous platform behavior. The diagnostic source is -[cert-roots.go](issue-154/cert-roots.go). The consuming application's defaults -apply to library calls as well, so silo-pkg's older `go` directive does not -prevent the behavior in Server, mc, or Console. - -This is expected standard-library behavior, not a reason to silently discard -configured CA variables or skip verification. Setting either variable replaces -Keychain trust with on-disk roots and Go's verifier; stale or incomplete paths -can break previously trusted connections. Unset inherited variables to restore -Keychain trust; explicit additional CAs still work. The three application -READMEs and the package README now document this. The package's Windows loader enumerates -the native ROOT store directly and does not call `SystemCertPool`; it does not -inherit this particular new setting. Windows behavior was reviewed in source, -not runtime-tested. - -### JSON, HTTP, timers, and other compatibility controls - -- **JSON:** checked owned JSON error handling and exercised policy/condition, - config, and authentication tests. `quick` uses typed `SyntaxError` and - `UnmarshalTypeError`; no owned decision depending on changed standard JSON - error text was found. silo-pkg's full suite passes with both Go compilers; - mc's command and Console's API/auth suites pass under Go 1.27. No broad - `nojsonv2` opt-out or serialization rewrite is justified by these results. -- **HTTP response closing:** Go 1.27's standard-library drain is bounded at - 256 KiB and 50 ms. mc's actual early-close/cancel regression passes, including - the compressed S3 Select stream. Existing owned drain helpers can still have - independent timeout concerns; those are not newly caused by this Go change. -- **ALPN and custom connections:** mc's TLS dialer returns a real `*tls.Conn`; - its deadline wrapper is on the TCP dial path. No new accidental HTTP/2 opt-in - from the expanded `ConnectionState` interface support was identified in - these transports. Console keeps its existing transport policy. -- **Removed switches:** no maintained runtime/config reliance on `tlsrsakex`, - `tls3des`, `tls10server`, `x509keypairleaf`, or `asynctimerchan` was found. - Existing explicit cipher policies continue to be explicit. Timer uses in - package certificate reload and license refresh do not depend on buffered - timer-channel length/capacity. Unix EOF error changes do not expose a matching - owned error-type assumption on the reviewed Unix-socket paths. -- **Platform floor:** application READMEs now identify macOS 13 as the minimum - for Go 1.27 binaries. No Windows, old macOS, or PowerPC runtime claim is made. - -## Validation and delivery - -- Server: focused TLS tests on macOS and Linux; macOS race run; crypto, HTTP, - grid, and all `internal/config/...` tests; full Linux Server build and the - three integration scenarios above. -- silo-pkg: `make test` (lint and full race suite) on Go 1.27.1; full suite on - Go 1.26.5, preserving the library floor. -- mc: complete `./cmd` suite, including the new TLS test and existing S3 Select - early-cancel test. -- Console: complete `./api/... ./pkg/...` suites, including identity and STS - validation and the new handshake tests. - -Go lint reports zero issues in all four repositories. Server's optional spelling -check is skipped because `typos` is not installed. Its lint target used the -already-installed matching golangci-lint v2.13.1 after a redundant download was -stopped; mc's lint was rerun serially after the global linter lock prevented the -first concurrent attempt. These tooling retries did not require source changes. - -An independent Fable 5.1 Max review reproduced the old-code failures and ran the -new TLS tests with the race detector. Its shuffled mc and Console suites passed. -One shuffled silo-pkg run failed the untouched `certs.TestValidPairAfterWrite`; -that test passed three plain reruns, the same shuffle seed, and the full certs -package rerun. The certs package runs in a separate test binary from the changed -env tests; this was classified as an existing timing flake. - -The review also found that committing the diagnostic fixture made rebrand CI -count synthetic IdP paths as product routes. The guard now excludes -`docs/investigations/`; the product compatibility baseline is unchanged. -The full proposed file set passes the guard, and a negative control adding a -product route still fails. After the review corrections, a fresh Linux Server -build passed all three integration scenarios again; the new binary identity and -rerun results are recorded in the evidence file's `post_review_validation`. - -All source and dependency choices remain those of the maintained PGSTY stack. -`go.mod` and `go.sum` are unchanged in every repository. The Server uses its -existing pinned Console/pkg/mc modules; the companion changes are tests and -documentation, so no replacement graph or unpublished dependency version is -needed to build the runtime fix. - -Validation used separate worktrees for Server, silo-pkg, mc, and Console, leaving -the original repositories and their `main` branches untouched. Build identities, -fixture results, and command outcomes are recorded in -[go127-stack-evidence.json](go127-stack-evidence.json). Absolute paths and branch -names in the captured evidence identify the environment at recording time. -The recorded module graph identifies the dependencies selected for those -builds; `go.sum` also retains checksums for unselected versions. Local image IDs -and temporary paths are historical evidence, not portable setup instructions. diff --git a/docs/investigations/issue-116/evidence-20260911.json b/docs/investigations/issue-116/evidence-20260911.json deleted file mode 100644 index ab2c005a1..000000000 --- a/docs/investigations/issue-116/evidence-20260911.json +++ /dev/null @@ -1,534 +0,0 @@ -{ - "date": "2026-09-11", - "runs": [ - { - "runid": "silo-v1-0806-9c53f14c", - "version": "0806", - "image": "sha256:29a498b24669cae1fed11c1a2fb2b3d73c68829a0a9c0b14e71b386671d38fac", - "nodes": 4, - "drives": 4, - "filesystem": "Linux tmpfs named volumes, held mounted across server restarts", - "drive_bytes": 268435456, - "status": "PASS", - "phases": [ - { - "phase": "startup-admin", - "seconds_from_start": 4.157, - "first_online_by_coordinator": [ - 4.037, - 4.078, - 4.118, - 3.737 - ] - }, - { - "phase": "startup-canary", - "attempts": 1, - "puts": 4, - "gets": 16, - "hard_deadline_seconds": 60, - "gate_seconds": 0.239, - "seconds_from_start": 4.396, - "first_put_seconds_after_admin_by_coordinator": [ - 0.096, - 0.116, - 0.127, - 0.138 - ], - "first_four_reads_seconds_after_admin_by_coordinator": [ - 0.162, - 0.194, - 0.217, - 0.239 - ], - "transient_error_count": 0, - "first_transient_errors": [] - }, - { - "phase": "full-restart-admin", - "seconds_from_start": 2.084, - "first_online_by_coordinator": [ - 1.937, - 1.976, - 2.016, - 1.643 - ] - }, - { - "phase": "full-restart-canary", - "attempts": 45, - "puts": 4, - "gets": 16, - "hard_deadline_seconds": 60, - "gate_seconds": 14.449, - "seconds_from_start": 16.534, - "first_put_seconds_after_admin_by_coordinator": [ - 14.315, - 13.573, - 13.953, - 0.05 - ], - "first_four_reads_seconds_after_admin_by_coordinator": [ - 14.373, - 14.396, - 14.426, - 14.449 - ], - "transient_error_count": 129, - "first_transient_errors": [ - { - "attempt": 1, - "operation": "put", - "node": 0, - "error": "An error occurred (SlowDownWrite) when calling the PutObject operation (reached max retries: 0): Resource requested is unwritable, please reduce your request rate" - }, - { - "attempt": 1, - "operation": "put", - "node": 1, - "error": "An error occurred (SlowDownWrite) when calling the PutObject operation (reached max retries: 0): Resource requested is unwritable, please reduce your request rate" - }, - { - "attempt": 1, - "operation": "put", - "node": 2, - "error": "An error occurred (SlowDownWrite) when calling the PutObject operation (reached max retries: 0): Resource requested is unwritable, please reduce your request rate" - }, - { - "attempt": 2, - "operation": "put", - "node": 0, - "error": "An error occurred (SlowDownWrite) when calling the PutObject operation (reached max retries: 0): Resource requested is unwritable, please reduce your request rate" - } - ] - }, - { - "phase": "readback-15s", - "seconds_after_canary": 17.596, - "objects": 55, - "reads": 220, - "errors": [] - }, - { - "phase": "readback-30s", - "seconds_after_canary": 31.357, - "objects": 55, - "reads": 220, - "errors": [] - }, - { - "phase": "readback-60s", - "seconds_after_canary": 61.566, - "objects": 55, - "reads": 220, - "errors": [] - }, - { - "phase": "one-node-outage", - "existing_read": true, - "put": true, - "readers": 3 - }, - { - "phase": "rejoin-admin", - "seconds_from_start": 1.382, - "first_online_by_coordinator": [ - 1.27, - 1.307, - 1.343, - 1.382 - ] - }, - { - "phase": "rejoin-canary", - "attempts": 1, - "puts": 4, - "gets": 16, - "hard_deadline_seconds": 60, - "gate_seconds": 0.158, - "seconds_from_start": 1.541, - "first_put_seconds_after_admin_by_coordinator": [ - 0.013, - 0.029, - 0.039, - 0.05 - ], - "first_four_reads_seconds_after_admin_by_coordinator": [ - 0.077, - 0.102, - 0.132, - 0.158 - ], - "transient_error_count": 0, - "first_transient_errors": [] - }, - { - "phase": "final-readback", - "seconds_after_canary": 1.71, - "objects": 60, - "reads": 240, - "errors": [] - } - ], - "acknowledged_objects": 60, - "version_ids_recorded": true, - "sha256_recorded": true - }, - { - "runid": "silo-v1-0903-a457573c", - "version": "0903", - "image": "sha256:b616a0cf8cb281e7e6bb3c9b1fb53875b4016a2878223925541c18f82d6c5ca3", - "nodes": 4, - "drives": 4, - "filesystem": "Linux tmpfs named volumes, held mounted across server restarts", - "drive_bytes": 268435456, - "status": "PASS", - "phases": [ - { - "phase": "startup-admin", - "seconds_from_start": 4.271, - "first_online_by_coordinator": [ - 4.158, - 4.194, - 3.827, - 3.867 - ] - }, - { - "phase": "startup-canary", - "attempts": 1, - "puts": 4, - "gets": 16, - "hard_deadline_seconds": 60, - "gate_seconds": 0.2, - "seconds_from_start": 4.471, - "first_put_seconds_after_admin_by_coordinator": [ - 0.054, - 0.068, - 0.081, - 0.092 - ], - "first_four_reads_seconds_after_admin_by_coordinator": [ - 0.118, - 0.146, - 0.167, - 0.2 - ], - "transient_error_count": 0, - "first_transient_errors": [] - }, - { - "phase": "full-restart-admin", - "seconds_from_start": 2.284, - "first_online_by_coordinator": [ - 2.143, - 2.193, - 0.849, - 0.902 - ] - }, - { - "phase": "full-restart-canary", - "attempts": 1, - "puts": 4, - "gets": 16, - "hard_deadline_seconds": 60, - "gate_seconds": 0.191, - "seconds_from_start": 2.476, - "first_put_seconds_after_admin_by_coordinator": [ - 0.016, - 0.029, - 0.044, - 0.057 - ], - "first_four_reads_seconds_after_admin_by_coordinator": [ - 0.093, - 0.124, - 0.158, - 0.192 - ], - "transient_error_count": 0, - "first_transient_errors": [] - }, - { - "phase": "readback-15s", - "seconds_after_canary": 15.235, - "objects": 8, - "reads": 32, - "errors": [] - }, - { - "phase": "readback-30s", - "seconds_after_canary": 30.356, - "objects": 8, - "reads": 32, - "errors": [] - }, - { - "phase": "readback-60s", - "seconds_after_canary": 60.192, - "objects": 8, - "reads": 32, - "errors": [] - }, - { - "phase": "one-node-outage", - "existing_read": true, - "put": true, - "readers": 3 - }, - { - "phase": "rejoin-admin", - "seconds_from_start": 0.903, - "first_online_by_coordinator": [ - 0.791, - 0.829, - 0.865, - 0.903 - ] - }, - { - "phase": "rejoin-canary", - "attempts": 1, - "puts": 4, - "gets": 16, - "hard_deadline_seconds": 60, - "gate_seconds": 0.141, - "seconds_from_start": 1.044, - "first_put_seconds_after_admin_by_coordinator": [ - 0.012, - 0.022, - 0.035, - 0.045 - ], - "first_four_reads_seconds_after_admin_by_coordinator": [ - 0.069, - 0.096, - 0.12, - 0.141 - ], - "transient_error_count": 0, - "first_transient_errors": [] - }, - { - "phase": "final-readback", - "seconds_after_canary": 0.301, - "objects": 13, - "reads": 52, - "errors": [] - } - ], - "acknowledged_objects": 13, - "version_ids_recorded": true, - "sha256_recorded": true - }, - { - "runid": "silo-v1-current-6a14eb18", - "version": "current", - "image": "pgsty/d12a:build", - "image_id": "sha256:307af7711e2e04ab75759cb42a1eef45c43c4404894c0e30dd19f742b107b922", - "platform": "linux/arm64", - "nodes": 4, - "drives": 4, - "filesystem": "Linux tmpfs named volumes, held mounted across server restarts", - "drive_bytes": 268435456, - "status": "PASS", - "binary_sha256": "1e4cd7b78ecfa1b0cf28f1961d48a220a712c6be1fd3a01b60ee99aec62f04a4", - "source_revision": "b32f2d9dd01a383a9991d34ffe248063463a031d+pool-consistency", - "phases": [ - { - "phase": "startup-admin", - "seconds_from_start": 7.374, - "first_online_by_coordinator": [ - 7.207, - 6.677, - 6.74, - 6.8 - ] - }, - { - "phase": "startup-canary", - "attempts": 1, - "puts": 4, - "gets": 16, - "hard_deadline_seconds": 60, - "gate_seconds": 0.479, - "seconds_from_start": 7.854, - "first_put_seconds_after_admin_by_coordinator": [ - 0.266, - 0.279, - 0.292, - 0.305 - ], - "first_four_reads_seconds_after_admin_by_coordinator": [ - 0.336, - 0.378, - 0.431, - 0.479 - ], - "transient_error_count": 0, - "first_transient_errors": [] - }, - { - "phase": "full-restart-admin", - "seconds_from_start": 2.461, - "first_online_by_coordinator": [ - 2.264, - 1.414, - 2.396, - 2.461 - ] - }, - { - "phase": "full-restart-canary", - "attempts": 36, - "puts": 4, - "gets": 16, - "hard_deadline_seconds": 60, - "gate_seconds": 14.489, - "seconds_from_start": 16.951, - "first_put_seconds_after_admin_by_coordinator": [ - 0.013, - 0.025, - 14.301, - 0.046 - ], - "first_four_reads_seconds_after_admin_by_coordinator": [ - 14.358, - 14.394, - 14.435, - 14.489 - ], - "transient_error_count": 104, - "first_transient_errors": [ - { - "attempt": 1, - "operation": "put", - "node": 2, - "error": "An error occurred (SlowDownWrite) when calling the PutObject operation (reached max retries: 0): Resource requested is unwritable, please reduce your request rate" - }, - { - "attempt": 1, - "operation": "get", - "node": 2, - "key": "full-restart-attempt-1-node-0", - "error": "An error occurred (SlowDownRead) when calling the GetObject operation (reached max retries: 0): Resource requested is unreadable, please reduce your request rate" - }, - { - "attempt": 1, - "operation": "get", - "node": 2, - "key": "full-restart-attempt-1-node-3", - "error": "An error occurred (SlowDownRead) when calling the GetObject operation (reached max retries: 0): Resource requested is unreadable, please reduce your request rate" - }, - { - "attempt": 2, - "operation": "put", - "node": 2, - "error": "An error occurred (SlowDownWrite) when calling the PutObject operation (reached max retries: 0): Resource requested is unwritable, please reduce your request rate" - } - ] - }, - { - "phase": "readback-15s", - "started_seconds_after_canary": 15.0, - "seconds_after_canary": 22.006, - "objects": 113, - "reads": 452, - "errors": [] - }, - { - "phase": "readback-30s", - "started_seconds_after_canary": 30.003, - "seconds_after_canary": 36.571, - "objects": 113, - "reads": 452, - "errors": [] - }, - { - "phase": "readback-60s", - "started_seconds_after_canary": 60.0, - "seconds_after_canary": 65.743, - "objects": 113, - "reads": 452, - "errors": [] - }, - { - "phase": "one-node-outage", - "existing_read": true, - "put": true, - "readers": 3 - }, - { - "phase": "rejoin-admin", - "seconds_from_start": 2.49, - "first_online_by_coordinator": [ - 1.165, - 2.417, - 1.646, - 2.087 - ] - }, - { - "phase": "rejoin-canary", - "attempts": 37, - "puts": 4, - "gets": 16, - "hard_deadline_seconds": 60, - "gate_seconds": 13.977, - "seconds_from_start": 16.468, - "first_put_seconds_after_admin_by_coordinator": [ - 0.013, - 0.024, - 0.035, - 13.889 - ], - "first_four_reads_seconds_after_admin_by_coordinator": [ - 13.909, - 13.927, - 13.953, - 13.977 - ], - "transient_error_count": 36, - "first_transient_errors": [ - { - "attempt": 1, - "operation": "put", - "node": 3, - "error": "An error occurred (SlowDownWrite) when calling the PutObject operation (reached max retries: 0): Resource requested is unwritable, please reduce your request rate" - }, - { - "attempt": 2, - "operation": "put", - "node": 3, - "error": "An error occurred (SlowDownWrite) when calling the PutObject operation (reached max retries: 0): Resource requested is unwritable, please reduce your request rate" - }, - { - "attempt": 3, - "operation": "put", - "node": 3, - "error": "An error occurred (SlowDownWrite) when calling the PutObject operation (reached max retries: 0): Resource requested is unwritable, please reduce your request rate" - }, - { - "attempt": 4, - "operation": "put", - "node": 3, - "error": "An error occurred (SlowDownWrite) when calling the PutObject operation (reached max retries: 0): Resource requested is unwritable, please reduce your request rate" - } - ] - }, - { - "phase": "final-readback", - "started_seconds_after_canary": 0.0, - "seconds_after_canary": 6.507, - "objects": 226, - "reads": 904, - "errors": [] - } - ], - "acknowledged_objects": 226, - "version_ids_recorded": true, - "sha256_recorded": true - } - ] -} diff --git a/docs/investigations/issue-116/run-linux.py b/docs/investigations/issue-116/run-linux.py deleted file mode 100644 index f34e12be6..000000000 --- a/docs/investigations/issue-116/run-linux.py +++ /dev/null @@ -1,327 +0,0 @@ -#!/usr/bin/env python3 -"""Bounded four-container restart/readback acceptance for pgsty/silo#116.""" -import argparse -import hashlib -import json -import os -from pathlib import Path -import secrets -import shutil -import signal -import socket -import subprocess -import time -import uuid -from concurrent.futures import ThreadPoolExecutor - -import boto3 -from botocore.config import Config - -ROOT = None -MCLI = None -CURRENT_BINARY = None -SOURCE_REVISION = None -IMAGES = { - '0806': 'pgsty/silo:RELEASE.2026-08-06T00-00-00Z', - '0903': 'pgsty/silo:RELEASE.2026-09-03T13-18-01Z', - 'current': 'pgsty/d12a:build', -} - - -def docker(*args, timeout=40, check=True): - return subprocess.run(['docker', *args], check=check, capture_output=True, text=True, timeout=timeout) - - -class Deadline(BaseException): - pass - - -def bounded(seconds, operation): - def expired(*_): - raise Deadline(f'hard deadline of {seconds}s exceeded') - old = signal.signal(signal.SIGALRM, expired) - signal.setitimer(signal.ITIMER_REAL, seconds) - try: - return operation(time.monotonic() + seconds) - finally: - signal.setitimer(signal.ITIMER_REAL, 0) - signal.signal(signal.SIGALRM, old) - - -def remaining(deadline): - value = deadline - time.monotonic() - if value <= 0: - raise Deadline('absolute deadline exceeded') - return value - - -def run(version): - image_info = json.loads(docker('image', 'inspect', IMAGES[version]).stdout)[0] - runid = f'silo-v1-{version}-{uuid.uuid4().hex[:8]}' - out = ROOT / runid - out.mkdir(mode=0o700) - user, password = 'local116', secrets.token_urlsafe(24) - envfile = out / 'credentials.env' - envfile.write_text(f'MINIO_ROOT_USER={user}\nMINIO_ROOT_PASSWORD={password}\nMINIO_CI_CD=1\nMINIO_BROWSER=off\nGOMAXPROCS=2\n') - envfile.chmod(0o600) - env = {k: v for k, v in os.environ.items() if not k.startswith(('MINIO_', 'SILO_', 'MC_')) - and k.lower() not in {'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy'}} - nodes = [f'{runid}-n{i}' for i in range(4)] - sockets = [socket.socket() for _ in nodes] - for sock in sockets: - sock.bind(('127.0.0.1', 0)) - ports = [sock.getsockname()[1] for sock in sockets] - for sock in sockets: - sock.close() - volumes = [n + '-data' for n in nodes] - endpoints, ledger = [], [] - result = {'runid': runid, 'version': version, 'image': IMAGES[version], - 'image_id': image_info['Id'], 'platform': image_info['Os']+'/'+image_info['Architecture'], - 'nodes': 4, - 'drives': 4, 'filesystem': 'Linux tmpfs named volumes, held mounted across server restarts', - 'drive_bytes': 268435456, 'phases': [], 'status': 'RUNNING'} - if version == 'current': - result['binary_sha256'] = hashlib.sha256(CURRENT_BINARY.read_bytes()).hexdigest() - result['source_revision'] = SOURCE_REVISION - bucket = 'canary-' + uuid.uuid4().hex[:10] - - def save(event=None): - if event is not None: - result['phases'].append(event) - compact = {k: (len(v) if k in ('transient_errors', 'errors') else v) for k, v in event.items()} - print(json.dumps({'version': version, **compact}), flush=True) - (out / 'result.json').write_text(json.dumps(result, indent=2) + '\n') - (out / 'acknowledged.json').write_text(json.dumps(ledger, indent=2) + '\n') - - def parallel(fn, values): - with ThreadPoolExecutor(max_workers=4) as pool: - return list(pool.map(fn, values)) - - def client(i, deadline): - timeout = remaining(deadline) - return boto3.client('s3', endpoint_url=endpoints[i], aws_access_key_id=user, - aws_secret_access_key=password, region_name='us-east-1', - config=Config(proxies={}, signature_version='s3v4', s3={'addressing_style': 'path'}, - retries={'total_max_attempts': 1}, connect_timeout=timeout, - read_timeout=timeout, request_checksum_calculation='when_required', - response_checksum_validation='when_required')) - - def admin_gate(origin, phase): - def check(deadline): - first = [None] * 4 - while True: - states = [] - for i in range(4): - try: - p = subprocess.run([str(MCLI), '--config-dir', str(out / 'mcli'), '--json', - 'admin', 'info', f'n{i}'], env=env, capture_output=True, - text=True, timeout=min(5, remaining(deadline))) - info = json.loads(p.stdout)['info'] - servers = info['servers'] - ok = len(servers) == 4 and all(s.get('state') == 'online' and s.get('drives') - and all(d.get('state') == 'ok' for d in s['drives']) - and all(v == 'online' for v in s.get('network', {}).values()) for s in servers) - if ok: - (out / f'{phase}-admin-{i}.json').write_text(json.dumps(info, indent=2) + '\n') - if first[i] is None: - first[i] = round(time.monotonic() - origin, 3) - states.append(ok) - except (Exception,): - states.append(False) - if all(states): - event = {'phase': phase + '-admin', 'seconds_from_start': round(time.monotonic()-origin, 3), - 'first_online_by_coordinator': first} - save(event) - return time.monotonic() - time.sleep(min(.25, remaining(deadline))) - return bounded(90, check) - - def canary(phase, origin, admin_time, setup=False): - def check(deadline): - started = time.monotonic() - first_put, first_reads = [None] * 4, [None] * 4 - errors, attempt, setup_done = [], 0, not setup - result['active_canary'] = {'phase': phase, 'errors': errors} - while True: - attempt += 1 - remaining(deadline) - if not setup_done: - try: - try: - client(0, deadline).create_bucket(Bucket=bucket) - except Exception as e: - if 'BucketAlreadyOwnedByYou' not in str(e): - raise - client(0, deadline).put_bucket_versioning(Bucket=bucket, VersioningConfiguration={'Status': 'Enabled'}) - setup_done = True - except Exception as e: - errors.append({'attempt': attempt, 'operation': 'setup', 'error': str(e)[:250]}) - time.sleep(min(.25, remaining(deadline))) - continue - acked = [] - for i in range(4): - key = f'{phase}-attempt-{attempt}-node-{i}' - payload = (key + '\n').encode() * 16384 - try: - vid = client(i, deadline).put_object(Bucket=bucket, Key=key, Body=payload)['VersionId'] - entry = {'phase': phase, 'key': key, 'version': vid, 'bytes': len(payload), - 'sha256': hashlib.sha256(payload).hexdigest(), 'writer': i, - 'ack_seconds_from_start': round(time.monotonic()-origin, 3)} - ledger.append(entry) - save() # Persist every acknowledged write, including failed rounds. - acked.append(entry) - if first_put[i] is None: - first_put[i] = round(time.monotonic()-admin_time, 3) - except Exception as e: - errors.append({'attempt': attempt, 'operation': 'put', 'node': i, 'error': str(e)[:250]}) - reads = 0 - for i in range(4): - own_reads = 0 - for entry in acked: - try: - verify(i, entry, deadline) - reads += 1 - own_reads += 1 - except Exception as e: - errors.append({'attempt': attempt, 'operation': 'get', 'node': i, - 'key': entry['key'], 'error': str(e)[:250]}) - if own_reads == 4 and first_reads[i] is None: - first_reads[i] = round(time.monotonic()-admin_time, 3) - remaining(deadline) - if len(acked) == 4 and reads == 16: - result.pop('active_canary', None) - save({'phase': phase + '-canary', 'attempts': attempt, 'puts': 4, 'gets': 16, - 'hard_deadline_seconds': 60, 'gate_seconds': round(time.monotonic()-started, 3), - 'seconds_from_start': round(time.monotonic()-origin, 3), - 'first_put_seconds_after_admin_by_coordinator': first_put, - 'first_four_reads_seconds_after_admin_by_coordinator': first_reads, 'transient_errors': errors}) - return time.monotonic() - (out / f'{phase}-canary-errors.json').write_text(json.dumps(errors, indent=2) + '\n') - if attempt == 1: - print(json.dumps({'version': version, 'phase': phase, 'first_attempt_errors': errors}), flush=True) - time.sleep(min(.25, remaining(deadline))) - return bounded(60, check) - - def verify(i, entry, deadline): - got = client(i, deadline).get_object(Bucket=bucket, Key=entry['key'], VersionId=entry['version']) - try: - data = got['Body'].read() - finally: - got['Body'].close() - assert len(data) == entry['bytes'] and hashlib.sha256(data).hexdigest() == entry['sha256'], entry['key'] - assert got.get('VersionId') == entry['version'], entry['key'] - remaining(deadline) - - def readback(label, origin): - def check(deadline): - started = time.monotonic() - errors = [] - for entry in ledger: - for i in range(4): - try: - verify(i, entry, deadline) - except Exception as e: - errors.append({'key': entry['key'], 'node': i, 'error': str(e)[:250]}) - save({'phase': label, 'started_seconds_after_canary': round(started-origin, 3), - 'seconds_after_canary': round(time.monotonic()-origin, 3), - 'objects': len(ledger), 'reads': len(ledger)*4, 'errors': errors}) - return errors - return bounded(60, check) - - try: - docker('network', 'create', runid) - for volume in volumes: - docker('volume', 'create', '--driver', 'local', '--opt', 'type=tmpfs', - '--opt', 'device=tmpfs', '--opt', 'o=size=256m', volume) - mounts = [arg for i, v in enumerate(volumes) for arg in ('--mount', f'type=volume,source={v},target=/keep/{i}')] - docker('run', '-d', '--pull=never', '--network', 'none', '--name', runid + '-keeper', - *mounts, '--entrypoint', 'sleep', 'alpine:3.23', '1800') - urls = [f'http://{n}:9000/data' for n in nodes] - def create(i): - extra = [] - if version == 'current': - extra = ['--mount', f'type=bind,source={CURRENT_BINARY},target=/lab/silo,readonly', - '--entrypoint', '/lab/silo'] - docker('create', '--pull=never', '--name', nodes[i], '--network', runid, - '--hostname', nodes[i], '--cpus', '2', '--memory', '3g', '--env-file', str(envfile), - '--mount', f'type=volume,source={volumes[i]},target=/data', - '-p', f'127.0.0.1:{ports[i]}:9000', *extra, image_info['Id'], 'server', '--address', ':9000', - '--console-address', ':9001', *urls) - parallel(create, range(4)) - start = time.monotonic() - parallel(lambda n: docker('start', n), nodes) - for i, n in enumerate(nodes): - endpoint = 'http://' + docker('port', n, '9000/tcp').stdout.strip() - endpoints.append(endpoint) - env[f'MC_HOST_n{i}'] = endpoint.replace('http://', f'http://{user}:{password}@') - admin = admin_gate(start, 'startup') - canary('startup', start, admin, setup=True) - parallel(lambda n: docker('stop', '-t', '10', n), nodes) - start = time.monotonic() - parallel(lambda n: docker('start', n), nodes) - admin = admin_gate(start, 'full-restart') - gate = canary('full-restart', start, admin) - read_errors = [] - for after in (15, 30, 60): - time.sleep(max(0, gate + after - time.monotonic())) - read_errors.extend(readback(f'readback-{after}s', gate)) - assert not read_errors, 'acknowledged object readback failure; see result.json' - docker('stop', '-t', '10', nodes[3]) - def outage(deadline): - verify(0, ledger[0], deadline) - payload = b'acknowledged with one Linux node offline' * 16384 - key = 'one-node-outage' - vid = client(0, deadline).put_object(Bucket=bucket, Key=key, Body=payload)['VersionId'] - entry = {'phase': 'outage', 'key': key, 'version': vid, 'bytes': len(payload), - 'sha256': hashlib.sha256(payload).hexdigest(), 'writer': 0} - ledger.append(entry) - save() - for i in range(3): - verify(i, entry, deadline) - save({'phase': 'one-node-outage', 'existing_read': True, 'put': True, 'readers': 3}) - bounded(60, outage) - start = time.monotonic() - docker('start', nodes[3]) - admin = admin_gate(start, 'rejoin') - gate = canary('rejoin', start, admin) - assert not readback('final-readback', gate) - result['status'] = 'PASS' - except BaseException as e: - result['status'] = 'FAIL' - result['error'] = f'{type(e).__name__}: {e}' - raise - finally: - save() - for n in nodes: - log = docker('logs', n, check=False) - (out / (n + '.log')).write_text(log.stdout + log.stderr) - docker('rm', '-f', n, check=False) - docker('rm', '-f', runid + '-keeper', check=False) - for v in volumes: - docker('volume', 'rm', v, check=False) - docker('network', 'rm', runid, check=False) - envfile.unlink(missing_ok=True) - print(json.dumps({'version': version, 'status': result['status'], 'evidence': str(out)}), flush=True) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('versions', nargs='+', choices=list(IMAGES)) - parser.add_argument('--output', type=Path, required=True, help='directory for retained evidence') - parser.add_argument('--mcli', default=shutil.which('mcli'), help='native mcli executable') - parser.add_argument('--current-binary', type=Path, help='Linux binary matching the Docker architecture') - parser.add_argument('--source-revision', help='Git revision of --current-binary') - parser.add_argument('--current-image', default=IMAGES['current'], help='cached Linux base image for current binary') - args = parser.parse_args() - if not args.mcli or not Path(args.mcli).is_file(): - parser.error('--mcli must point to an executable file') - if 'current' in args.versions and (not args.current_binary or not args.current_binary.is_file()): - parser.error('current requires --current-binary') - ROOT = args.output.resolve() - ROOT.mkdir(mode=0o700, parents=True, exist_ok=True) - MCLI = Path(args.mcli).resolve() - CURRENT_BINARY = args.current_binary.resolve() if args.current_binary else None - SOURCE_REVISION = args.source_revision - IMAGES['current'] = args.current_image - for version in args.versions: - run(version) diff --git a/docs/investigations/issue-154.md b/docs/investigations/issue-154.md deleted file mode 100644 index 538af6daa..000000000 --- a/docs/investigations/issue-154.md +++ /dev/null @@ -1,409 +0,0 @@ -# SILO #154:OIDC discovery 连接重置调查 - -> This is a dated investigation, with the source and runtime boundaries recorded -> below. It does not establish the current dependency pins or a later release. -> See [the current changelog](../../CHANGELOG.md) and -> [component matrix](https://silo.pgsty.com/compatibility/versions/). - - -前两轮调查时间:2026-09-09;公开 issue 最后核对于 07:53 UTC。第二轮补充同源码、同依赖、不同 Go 工具链的 Linux 完整 Server 对照和候选补丁认证链路验证。 - -**后续更新:用户已授权扩展至整个 SILO 技术栈并修复。现已确认 Server 其他 TLS 路径也存在同类覆盖问题,并在产品工作区完成统一使用 Go 默认曲线的修复。当前实现、验证和交付状态见 [全栈调查](go127-stack.md)。下文保留前两轮的诊断与当时的 OIDC 局部候选;“未修改产品”和“不要扩大范围”等表述仅适用于当时的调查阶段,局部候选已被后续全路径修复取代。** - -## 判断与处理顺序 - -**目前可以确认是 Server 发出的 discovery GET 失败,随后 IAM 初始化等待,Console 初始化也被延后;还不能确认真实连接由谁、在哪个协议阶段重置。优先调查 SILO/Go 客户端与 IdP 前置 TLS 终止器、代理或 WAF 的互操作。** 现有证据不支持将其定性为证书错误、Keycloak 配置错误、JWT 校验错误或 `coreos/go-oidc` 回归。 - -有两项与版本相关、可在本地验证的 TLS 差异: - -1. **Go 1.27 改变了 `GODEBUG=tlsmlkem=0` 与显式 `CurvePreferences` 的关系。** SILO 两个版本都显式包含 X25519MLKEM768。旧版 Go 1.26.5 会根据该环境选项移除它;Go 1.27.1 保留显式配置。在模拟拒绝 ML-KEM 的入口上,能重现“相同选项下旧 Server 启动成功,新 Server 持续 reset”。**客户是否设置过这个选项尚未知,不能把条件性复现当成客户根因。** -2. **Go 1.27 的 ClientHello 新增 ML-DSA 签名算法。** 没有上述环境选项时,新旧版本也会发送不同的握手。人为拒绝新算法编号的入口同样能产生旧成功、新失败。真实入口是否存在这种行为尚未知。 - -另外,HTTP User-Agent 从 `MinIO` 变成了 `Silo`;若连接在 TLS 完成、GET 发出之后被重置,应优先查 WAF、User-Agent 规则和 HTTP 路由,而不是继续调整 TLS。 - -最短路径:**先在故障进程所在环境拿到实际 Go 版本、是否设置 `tlsmlkem=0`、目标 IP 和 TLS 完成与否;再针对已证实的分支处理。** 优先修正入口兼容性或错误路由。若确认是上述环境选项失效,只为 OpenID 出站请求恢复 Go 默认曲线选择,是目前最小的代码候选。现阶段不宜做全局 TLS 改动或整体依赖回退。 - -## 第二轮结论:已隔离 Go 因素,局部候选修复通过 Linux 验证 - -**可以复现一种由 Go 1.26.5 → 1.27.1 单独触发的兼容性回归。建议保留 Go、针对已确认分支修复;整体回退只用于临时恢复服务。** 这里的“已确认”指本地实验机制,仍不等于已经确认客户入口的根因。 - -四个完整 Server 都从本地源码编译为 `linux/arm64`,在现成通用 Debian 12 基础镜像的 `--network none` 容器中运行;Server、合成 IdP、Console 和 curl 共用同一 loopback 网络,没有暴露端口。没有下载或运行 Server/Console 镜像。旧源码为 `d88f46ccee345a9c2fabe2d221d9a9e56bc11aec`,当前源码为 `d1105bbb3d4a0afa33b3a4ac11b821235038ed0e`。 - -旧源码两次构建使用完全相同的 `go.mod`、`go.sum` 和实际链接模块版本,仅替换编译器;当前源码与候选补丁构建的依赖图也完全相同。实际二进制 SHA-256、`go version -m` 依赖和构建选项保存在 [Linux 证据](issue-154/linux-evidence.json)。 - -下表的入口**人为设置为见到 X25519MLKEM768 就发 TCP RST**,Server 都设置 `GODEBUG=tlsmlkem=0`: - -| 源码与编译器 | Server 初始 ClientHello | 完整 Server 结果 | 同容器 curl | -| --- | --- | --- | --- | -| 同一份旧源码 + Go 1.26.5 | 275 字节,无 ML-KEM | discovery、JWKS、IAM、Console 正常;cluster 200 | HTTP/2 200 | -| 同一份旧源码 + Go 1.27.1 | 1509 字节,仍包含 ML-KEM | `connection reset by peer`;IAM 等待;cluster 503;Console 未启动 | HTTP/2 200 | -| 当前源码 + Go 1.27.1 | 1509 字节,仍包含 ML-KEM | 同样失败 | HTTP/2 200 | -| 当前源码 + 局部候选补丁 + Go 1.27.1 | 287 字节,无 ML-KEM | 完整启动及合成 OIDC 登录成功 | HTTP/2 200 | - -这将该条件下的回归定位到工具链行为,而非 Console、pkg、mc 或 `coreos/go-oidc` 升级。Go 1.27 的发布说明明确将此作为有意改变:`tlsmlkem` / `tlssecpmlkem` 只控制默认曲线集合,显式指定的集合可以继续启用这些算法。SILO 现有曲线列表显式包含该算法,因而原来的兼容开关在这条路径失效。[Go 1.27 crypto/tls 说明](https://go.dev/doc/go1.27#crypto/tls)。 - -共完成 **12 个 Linux 场景**,其中失败用例按预期失败: - -- 正常 TLS 1.2 / P-256 / RSA / AES-256-GCM 的 IdP,旧源码用两个 Go 版本编译均正常,证明不是 Go 1.27 普遍无法连接这套 TLS。 -- 上表四个对照均符合预期;候选补丁如果不设置 `tlsmlkem=0`,仍被 ML-KEM 拒绝规则拦截。补丁恢复显式兼容选项的作用,不会自行关闭后量子算法。 -- 候选补丁在上述 TLS 1.2 兼容场景、以及无 GODEBUG 的正常 TLS 1.3 场景,都完成 Console 登录信息获取 → IdP authorization redirect → callback → token exchange → STS 凭据 → Console 会话 → 桶列表读取。登录 API 为 204,桶列表为 200。 -- 两个登录场景分别提交错误签名与错误 audience 的 JWT,登录返回 500、桶列表返回 403,没有生成会话 cookie。这里只确认认证未放行,没有将现有 500 状态码行为改为另一项修复。 -- 不信任 CA 时,候选 Server 仍因 `x509: certificate signed by unknown authority` 停在 IAM 初始化。没有关闭证书校验。 -- 不配置 OIDC 启动后,经实际 Admin API 添加同一合成 provider:当前源码失败且返回 reset;候选补丁成功。 -- 入口改为拒绝 ML-DSA 签名编号,候选补丁加 `tlsmlkem=0` 仍失败。这是另一条机制,当前补丁没有解决它。 - -认证流程由 Python 驱动真实 Console HTTP API;IdP 使用一次性合成授权码和自行签发的实验 JWT,没有客户账户。没有执行浏览器页面交互、登出、真实 Keycloak 或生产数据升级测试。TLS 1.3 对照实际协商 TLS 1.3/P-256,不是所有新增后量子曲线的互操作覆盖。 - -### 修复与回退的取舍 - -[候选补丁](issue-154/openid-default-curves.patch) 只有三个文件:增加 OpenID 专用 transport helper,将其 `TLSClientConfig.CurvePreferences` 设为 `nil`,再替换 IAM 初始化和 OpenID 配置校验两个调用点。测试时仅应用于隔离源码副本,当前产品工作区未应用;Go 版本与所有依赖保持不变。它让 Go 默认策略和现有兼容开关接管外部 IdP 的密钥交换,其他 TLS 参数继续来自现有构造函数。 - -如果客户证据确认此分支,建议采用该局部修复,并在客户实际入口复验。如果客户没有设置 `tlsmlkem=0`,它不能单独解释旧版成功:旧版默认也发送 ML-KEM。此时应继续区分 ML-DSA、其他握手变化、HTTP/WAF 规则与网络路径;不把此补丁直接宣称为 #154 的完整修复。 - -**不建议将当前产品直接改回 Go 1.26。** 实测以 Go 1.26.7 和 `GOTOOLCHAIN=local` 读取当前源码即被 `go.mod requires go >= 1.27.1` 拒绝;所固定的 Server、Console、mc 均声明 Go 1.27.1。回退需要进一步调整这些模块及可能的传递依赖,不是只换一个编译器版本。这个报错证明当前依赖图不能原样回编,并不证明经过额外适配后绝对无法回编。报告中已恢复服务的旧版可作为临时运行状态,不能把这种处置等同于完成兼容修复。 - -第二轮的运行脚本是 [run-linux.py](issue-154/run-linux.py),结果是 [linux-evidence.json](issue-154/linux-evidence.json),具体构建和运行步骤见文末。没有 issue 评论、提交、推送、合并或发布。 - -候选副本另通过 Go 1.27.1、darwin/arm64、CGO 关闭的 `go test -mod=readonly -count=1 ./internal/http ./internal/config/identity/openid`;补丁可以干净应用到调查基线。这些包级测试与上述 Linux 集成验证分别记录,不将其当成 Linux 单元测试结果。 - -## 范围、版本与公开证据 - -- 初始工作区干净,处于 detached HEAD;调查分支为 `codex/investigate-oidc-154`,基线与远端 main 均为 `d1105bbb3d4a0afa33b3a4ac11b821235038ed0e`。 -- 已读取 `/Users/vonng/pgsty/silo/AGENTS.md` 及工作区适用说明。维护范围为 PGSTY 的 Server、Console、mc、silo-pkg;上游 MinIO 仅作参考。 -- 第一轮 Server 与 transport 探针为本地源码构建的 darwin/arm64 程序,第二轮完整 Server 交叉构建为 linux/arm64;均使用 `CGO_ENABLED=0 GOWORK=off`。fixture 与 Admin 辅助程序也由本机 Go 构建。历史源码使用 `git archive` 导入隔离临时目录。没有下载或运行 Server/Console Docker 镜像,没有访问客户端点,没有改动真实服务或数据,没有评论 issue、推送、合并或发布。 -- 产品源码、`go.mod`、`go.sum` 未修改。本目录中的 Go 文件是显式运行的调查工具,带 `//go:build ignore`,不进入正常构建。 - -| 项目 | 旧版:2026-08-04 | 报告故障版:2026-09-03 | 调查时 main | -| --- | --- | --- | --- | -| 完整 tag | `RELEASE.2026-08-04T00-00-00Z` | `RELEASE.2026-09-03T13-18-01Z` | 无新 release 声明 | -| 源码 commit | `d88f46ccee345a9c2fabe2d221d9a9e56bc11aec` | `9b11dc9469e650815b775cb47b039610644f5da4` | `d1105bbb3d4a0afa33b3a4ac11b821235038ed0e` | -| go.mod / Docker 构建定义 | Go 1.26.5 | Go 1.27.1 | Go 1.27.1 | -| 本地 Server / 探针实际编译器 | Go 1.26.5 | Go 1.27.1 | Go 1.27.1 | -| Console replacement | `v0.0.0-20260804042150-b952a1202869` | `v0.0.0-20260903111932-464a59d73ada` | `v0.0.0-20260908142700-c103d08ec36a` | -| mc replacement | `v0.0.0-20260801042411-ad10a2a10b76` | `v0.0.0-20260903063637-a2ef95c035d9` | `v0.0.0-20260909015522-fcd5cad8247f` | -| PGSTY silo-pkg | v3.11.0,替换历史 minio/pkg 路径 | v3.13.2,直接依赖 | v3.13.3,直接依赖 | -| coreos/go-oidc/v3 | v3.17.0 | v3.21.0 | v3.21.0 | -| x/crypto | v0.54.0 | v0.56.0 | v0.56.0 | -| x/net | v0.57.0 | v0.58.0 | v0.58.0 | -| x/oauth2 | v0.36.0 | v0.36.0 | v0.36.0 | - -版本依据:[旧版 go.mod](https://github.com/pgsty/silo/blob/d88f46ccee345a9c2fabe2d221d9a9e56bc11aec/go.mod)、[故障版 go.mod](https://github.com/pgsty/silo/blob/9b11dc9469e650815b775cb47b039610644f5da4/go.mod)、[本次 main go.mod](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/go.mod)。还核对了各 tag 的 `Dockerfile.goreleaser` 和 release workflow。它们声明相应 Go 构建版本、禁用 CGO;历史归档、本次探针和 Server 均用 `go version -m` 核实实际编译器与 replacement。**这些不是客户实际镜像 digest 或其内二进制的取证,后者仍需 `--version` / build info 确认。** - -[Issue #154](https://github.com/pgsty/silo/issues/154) 当前 OPEN,最后更新时间 `2026-09-08T05:30:42Z`,评论数为 0。报告包含升级后 Server 初始化失败、旧版回退恢复、测试实例添加 OIDC 失败,以及 curl 成功的输出。 - -curl 那次连接验证了所收到的证书链,并选择 TLS 1.2、`ECDHE-RSA-AES256-GCM-SHA384`、P-256 和 HTTP/2;解析得到两个 IPv4,记录中实际访问了其中一个。**公开命令使用 `docker run --rm` 新建容器,并非 `docker exec` 进入原故障容器;同镜像不能证明同网络命名空间、环境变量、CA 挂载、DNS 结果或出口。** 域名、realm、IP 已脱敏,本次不推测其真实值。 - -## 实际请求链 - -### IAM 与配置校验 - -```text -Server startup - IAMSys.Init - openid.LookupConfig - parseDiscoveryDoc: GET .well-known/openid-configuration - PopulatePublicKey: GET discovery 中的 jwks_uri - IAM store 初始化 - Console 初始化 - -Console 添加 OIDC - AdminClient.AddOrUpdateIDPConfig - Server addOrUpdateIDPHandler - validateConfig(identity_openid) - 同一个 openid.LookupConfig / NewHTTPTransport -``` - -`parseDiscoveryDoc` 是 SILO 自己的实现,用标准库 `http.Client` 发 GET,收到成功响应后才解码 JSON;这次日志中的 `Get ... read tcp ... reset` 发生在该调用返回响应之前,不能进一步区分 TLS 与 HTTP。请求本身不需要客户 client secret、token 或私钥。[IAM 调用点](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/iam.go#L278-L288)、[discovery 实现](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/internal/config/identity/openid/jwt.go#L261-L285)、[JWKS 实现](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/internal/config/identity/openid/jwt.go#L89-L110)。 - -Console 的添加表单通过 Admin API 触发 Server 配置校验。校验成功才写入配置;本地已重现该 API 返回同样的 reset,恢复 IdP 后再次创建成功并要求重启。[Console 调用](https://github.com/pgsty/silo-console/blob/c103d08ec36a/api/admin_idp.go#L79-L114)、[Admin 客户端](https://github.com/pgsty/silo-console/blob/c103d08ec36a/api/client-admin.go#L593-L595)、[Server 校验和保存边界](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/admin-handlers-idp-config.go#L127-L150)、[OpenID 配置校验](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/config-current.go#L353-L359)。 - -`coreos/go-oidc.NewProvider` 在 Server 中的使用是 `MockOpenIDTestUserInteraction` 测试辅助函数,不是这次 IAM discovery 调用链。升级此依赖不能单独解释或修复该 GET。`crypto/tls` 和这里的 `net/http` 属于 Go 标准库,不能用 go.mod 中 `x/crypto`、`x/net` 的版本代替它们的实际行为。[辅助函数](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/utils.go#L994-L1012)。 - -### transport 参数与环境 - -| 项目 | 实际行为及意义 | -| --- | --- | -| 构造 | `NewHTTPTransport()` → `NewHTTPTransportWithTimeout(time.Minute)` → `xhttp.ConnSettings`。每次 IAM 初始化重试都会重新构造。 | -| TLS 版本 | 未显式设置 Min/Max;所比较 Go 工具链的正常默认范围是 TLS 1.2–1.3。证书、主机名验证开启。 | -| 密码套件 | 显式 `TLSCiphersBackwardCompatible()`,包含 curl 成功使用的 ECDHE-RSA/AES-256-GCM。没有理由为本 issue 添加旧 RSA、3DES 或 SHA-1 例外。 | -| 曲线 | 显式 `{X25519MLKEM768, P256, X25519, P384, P521}`,两个 release 和当前 main 相同。实际上线顺序还受 Go 实现控制,见实验。 | -| ALPN / HTTP | `EnableHTTP2=false`,同时设置自定义 TLS config 和 DialContext;实测 ClientHello 没有 ALPN,GET 使用 HTTP/1.1。三个版本一致。`GODEBUG=http2client=0` 对此基线路径无修复价值。 | -| 代理 | `http.ProxyFromEnvironment`;HTTPS URL 使用 `HTTPS_PROXY` / `https_proxy` 与 `NO_PROXY` / `no_proxy`。这两版标准库均优先非空大写值,不使用 `ALL_PROXY`;设置在进程内缓存。不能根据 curl 的路由推断它。注意 go.mod 的 x/net v0.58.0 代码不是这里所用的标准库 vendored 实现。 | -| DNS | `globalDNSCache.LookupHost`,dnscache v0.1.1;默认刷新窗口在容器/Kubernetes 为 30 秒,其他环境为 10 分钟,可配置。按返回地址逐个尝试 TCP,首次 TCP 成功就返回;随后 TLS/HTTP 失败不会回到这个循环尝试另一 IP。代码注释称随机选择,但该循环没有 shuffle。 | -| Linux TCP 参数 | 使用 SILO 的自定义 dialer,包含 TCP fast open/keepalive 等设置,部分参数来自 Server CLI,包括 interface、buffer、user timeout。第二轮执行了完整 Linux Server 的正常 CLI 初始化,但只验证 loopback;不能排除实际出口设备、路由或非默认 CLI 参数的交互。 | -| CA | `silo-pkg/certs.GetRootCAs` 加载系统根、Kubernetes CA 目录和 `certs/CAs`;Server 还加入自己的公开服务证书。相关 pkg CA 加载实现未在这次版本比较中变化;实际文件和路径仍可能因部署变化不同。 | -| 超时 | TCP 拨号 5 秒,TLS 握手 10 秒,响应头 1 分钟;discovery/JWKS 的 Client 没有总超时,discovery 使用的 Request 也没有调用方 context。响应体停滞不受响应头超时保护。 | -| 复用 | keep-alive 开启,idle 15 秒,TLS session cache 100。一次初始化内 discovery 与 JWKS 可复用连接;初始化重试的新 transport 没有旧连接或 session。持续首次握手失败不能用清理空闲连接解释。 | -| 请求标识 | UA 包含产品、OS、架构、模式和构建信息,产品名从 `MinIO` 变为 `Silo`;传输层禁用自动压缩。UA 规则只能在 HTTPS 被终止、HTTP 请求可见之后起作用。 | - -源码:[构造与超时](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/utils.go#L651-L670)、[HTTP/TLS 参数](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/internal/http/transports.go#L44-L94)、[密码套件与曲线](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/internal/crypto/crypto.go#L52-L78)、[DNS 拨号循环](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/internal/http/dial_dnscache.go#L42-L84)、[缓存刷新](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/common-main.go#L551-L578)、[CA 加载](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/server-main.go#L383-L395)、[UA](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/update.go#L228-L266)。 - -比较旧 tag 与故障 tag,OpenID discovery、transport、曲线实现的变动仅为 pkg 导入路径调整;IAM 另有品牌日志变更。比较故障 tag 与本次 main,这些关键实现没有变动。因此不能把当前依赖推进当成 #154 已解决的证据。 - -**Console 登录阶段是另一条出站路径。** 当前及故障版 Console 通过 `GetConsoleHTTPClient` / `GlobalTransport` 再获取 discovery、交换令牌,TLS config 未指定 CurvePreferences,仍做完整证书验证;不能把“添加配置走 Server”推广为所有 Console OIDC 请求都走 Server transport。任何修复最终都必须验证完整登录。`CONSOLE_MINIO_SERVER_TLS_SKIP_VERIFY` 只针对 SILO 端点,不能作为 IdP 修复。[Console transport](https://github.com/pgsty/silo-console/blob/c103d08ec36a/api/config.go#L68-L121)、[IdP 客户端](https://github.com/pgsty/silo-console/blob/c103d08ec36a/api/tls.go#L59-L70)、[Console discovery](https://github.com/pgsty/silo-console/blob/c103d08ec36a/pkg/auth/idp/oauth2/provider.go#L428-L449)。 - -## 本地实验与能排除的假设 - -工具与原始结果放在 [issue-154/](issue-154/): - -- `probe.go` 直接调用 `cmd.NewHTTPTransport()`,用同版本 `certs.GetRootCAs` 装入实验 CA;记录 TCP、TLS、HTTP 阶段。诊断参数仅修改被比较的一项。 -- `fixture.go` 仅监听 IPv4 loopback;使用即时生成的 RSA 测试证书与专用 CA,提供 discovery 和有效 JWKS。正常基线限制 TLS 1.2、P-256、`TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`,支持 HTTP/1.1 和 HTTP/2。 -- `admin-check.go` 仅允许 loopback Server,验证 Console 使用的 Admin API;使用本地虚构配置和临时凭据。 -- `evidence.json` 保存版本、握手参数、分阶段 trace 和完整 Server 实验结果;无客户信息、私钥或 token。 -- 第二轮扩展 fixture 的合成授权码、token 和 JWKS 流程;`run-linux.py` 驱动 Linux 完整 Server 及 Console API,`linux-evidence.json` 保存十二组场景和构建身份。敏感运行值不写入结果。 - -### 正常服务及握手差异 - -三个源码版本的实际 transport 都能通过完整证书验证,以 TLS 1.2 / AES-256-GCM / HTTP/1.1 获取测试文档。**Go 1.27 本身并非不能连接 TLS 1.2、RSA 证书或这套密码套件。** 正常服务也接受诊断性的 HTTP/2。 - -| 构建与选项 | fixture 读到的初始握手字节数 | 支持的 group ID | 新增 ML-DSA 签名编号 | -| --- | ---: | --- | --- | -| 旧源码 + Go 1.26.5,默认 | 1497 | 4588, 29, 23, 24, 25 | 无 | -| 故障源码 + Go 1.27.1,默认 | 1509 | 同上 | 0x0904, 0x0905, 0x0906 | -| 当前源码 + Go 1.27.1,默认 | 1509 | 同上 | 同上 | -| 旧源码 + Go 1.26.5,`tlsmlkem=0` | 275 | 29, 23, 24, 25 | 无 | -| 故障/当前源码 + Go 1.27.1,`tlsmlkem=0` | 1509 | 4588, 29, 23, 24, 25 | 有 | -| 旧源码、旧依赖,只改为 Go 1.27.1 | 1509 | 4588, 29, 23, 24, 25 | 有;`tlsmlkem=0` 也不再移除 4588 | - -4588 是 X25519MLKEM768,29 是 X25519,23/24/25 是 P-256/384/521。这些字节数包含本地 fixture 收到的 TLS record,测试 URL 是 IP,没有 DNS SNI;不能直接当成客户网络中的包长或 MTU 证据。默认新旧差异约 12 字节,没有证据支持“本次才突然出现巨大 ML-KEM 握手”的说法。设置上述环境选项时的差异则显著不同。 - -旧源码保留旧依赖、仅更换 Go 编译器后,行为随编译器变化,隔离了本次发现与 silo-pkg/Console 版本更新之间的关系。Go 1.27 官方说明也明确记录显式曲线配置不再受这些默认值开关限制,并新增 ML-DSA 支持。[Go 1.27 发布说明](https://go.dev/doc/go1.27)。本地进一步核对了两版 `crypto/tls/defaults.go`、`common.go` 的 `curvePreferences`/`supportsCurve` 和 `handshake_client.go`。 - -### 主动拒绝与对照结果 - -下列拒绝规则是人为设置的模型,**只证明机制可以产生相同症状,不证明真实 Keycloak 或其入口使用这些规则**。 - -| fixture 规则 | 对照结果 | 能支持的结论 | -| --- | --- | --- | -| ClientHello 带 ML-KEM 就发送 TCP RST | 旧版默认也失败;旧版 + `tlsmlkem=0` 成功;故障版 + 同选项失败;故障版显式 classical 曲线成功 | 需要旧环境选项或其他变化,才能用该机制解释升级回归。仅说 ML-KEM 不兼容不充分。 | -| ClientHello 带 ML-DSA 编号就 RST | 旧版默认成功;故障版默认/仅 classical 都失败;故障版 TLS 1.2-only 成功 | 新的签名算法列表是另一种可区分机制。ML-DSA 与 ML-KEM 不是同一项。TLS 1.2-only 会同时改变多项 ClientHello,成功不等于唯一定位 ML-DSA。 | -| 必须提供 h2 ALPN | 旧、新 Server transport 默认都失败;`-h2` 成功 | 可以解释 curl 与 Server 的不同,单独不能解释新旧版本差异。 | -| TLS 完成后,对 `Silo` UA 的 GET 发送 RST | 同一个新 transport,`MinIO` UA 成功、`Silo` UA 失败 | 报告的外层 `Get ... reset` 错误也可能来自 HTTP 层,必须先判断 TLS 是否完成。 | -| 不信任测试 CA | 返回 `*tls.CertificateVerificationError` / x509 类错误 | 与人为 RST 的错误不同;没有证据要求跳过证书验证。仍须比较客户实际连接收到的链。 | -| 正常连接复用 | 第二个请求 `reused=true`;关闭 idle 连接后重新建 TCP 可恢复 TLS session | keep-alive 与 TLS session 复用是两件事。首次新 transport 就失败时,此分支优先级低。 | - -### 完整 Server、IAM 和恢复 - -使用三个本地编译的完整 Server,独立空数据/配置目录、独立 CA 和临时凭据;未登录真实 IdP。 - -| 场景 | 实际结果 | -| --- | --- | -| 旧版、故障版连接正常 IdP | discovery + JWKS 成功;一条 TLS 连接;cluster health 200;Console HTTP 200;Admin ListUsers 成功。 | -| 当前版连接持续 reset 的 IdP | IAM 持续等待;cluster health 503,`X-Minio-Server-Status: iam-offline`;Console 端口尚未提供服务;5 秒限时的 Admin ListUsers 未完成。 | -| 上述场景恢复 IdP,不重启 Server | 约 0.43 秒后 cluster/Console 200,ListUsers 成功。该时间是单次本地样本,不是恢复 SLA。 | -| 旧版 + `tlsmlkem=0`,IdP 拒绝 ML-KEM | 完整启动成功。 | -| 故障版 + `tlsmlkem=0`,相同拒绝规则 | IAM 等待、cluster 503;撤掉规则后约 1.38 秒内完整恢复,无需重启。 | -| 当前版 discovery 成功、JWKS 返回 503 | 同样阻塞 IAM;JWKS 恢复后约 0.33 秒内恢复。只验证 discovery 200 不够。 | -| 当前版先不配置 OIDC,再经 Admin API 添加 | IdP reset 时返回同型 `Get ... read tcp ... connection reset by peer`;恢复后同名创建成功,`restart=true`。失败校验没有保存该 provider。 | - -在本地 IAM 阻塞的场景中,`/minio/health/live` 和 `/minio/health/ready` **仍为 200**。判断这次恢复应使用 `/minio/health/cluster` 并验证受认证操作和 Console,不能只看 ready。源码上 cluster 的 `checkHealth` 检查 IAM,而 ready 没有此检查;这是已有行为,本文不扩展为一次健康检查重构。[health 检查](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/healthcheck-handler.go#L32-L65)、[ready 检查](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/healthcheck-handler.go#L129-L186)。 - -现有 IAM 重试间隔随机为 0–3 秒,GET 本身还会占用网络等待时间;初始化成功前不会继续创建 IAM store,Console 又在 IAM 调用返回后启动。外部连接恢复可由现有重试自行恢复;这不意味着靠增加重试能修复持续不兼容。缺少整体请求期限、请求取消的部分是另外一个可单独修复的启动健壮性问题。[重试逻辑](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/iam.go#L342-L365)、[Console 启动顺序](https://github.com/pgsty/silo/blob/d1105bbb3d4a0afa33b3a4ac11b821235038ed0e/cmd/server-main.go#L1007-L1027)。 - -已通过:`CGO_ENABLED=0 GOWORK=off go test -mod=readonly -count=1 ./internal/http ./internal/config/identity/openid`。这些测试没有替代真实 Keycloak 登录或 Linux 故障网络的验证。 - -## 最少补充证据与诊断命令 - -先收集第一轮,按结果才展开后续。所有请求只取公开 discovery,不需要客户端密钥、密码、token 或私钥。不要求环境变量全量导出、配置导出或证书私钥。 - -### 第一轮:运行身份、网络一致性、协议阶段 - -在**已有故障容器/Pod 的实际网络环境**中运行;旧版也做同样检查。不要以新建默认网络容器替代。如果实际进程经启动脚本修改过环境,探针也应使用修改后的相关环境和同一 CA 挂载。 - -```sh -# 选择实际 Server 可执行文件;记录输出中的版本、Go、OS/架构。 -silo --version -# 旧镜像的程序名可能是 minio。 - -# Linux:PID 设为实际 Server 进程 PID,不预设一定是 1。 -# 只输出 Go 调试选项以及相关环境项是否存在,不输出代理凭据/地址。 -tr '\0' '\n' < "/proc/$PID/environ" | awk ' - /^GODEBUG=/ { print; next } - /^(HTTP_PROXY|HTTPS_PROXY|NO_PROXY|ALL_PROXY|http_proxy|https_proxy|no_proxy|all_proxy|SSL_CERT_FILE|SSL_CERT_DIR)=/ { - split($0, a, "="); print a[1] "=" - }' - -# OIDC_URL 仅在本地设为原 config_url;URL 不应带凭据或令牌。 -export OIDC_URL -curl --http1.1 --connect-timeout 5 --max-time 20 -sS -o /dev/null \ - -w 'ip=%{remote_ip} http=%{http_version} status=%{http_code} verify=%{ssl_verify_result}\n' "$OIDC_URL" -curl --http2 --connect-timeout 5 --max-time 20 -sS -o /dev/null \ - -w 'ip=%{remote_ip} http=%{http_version} status=%{http_code} verify=%{ssl_verify_result}\n' "$OIDC_URL" - -# 由维护者从对应源码构建的探针;OIDC_CA 是与 Server 相同的 CAs 目录。 -./oidc-probe -ca "$OIDC_CA" -``` - -探针从 `OIDC_URL` 读取 URL。URL、响应体、请求头不会打印;输出的 IP 可按一致映射替换为 IP-A/IP-B。它使用真实 Server transport 构造函数,但添加 20 秒总期限、不跟随重定向、限制响应体读取为 1 MiB,并使用 `issue-154-probe` UA;因此是定位连接阶段的工具,不是完整 OIDC 流程。它没有执行 Server 的 CLI 初始化,也不继承该进程的 `--interface`、socket buffer、TCP user timeout 或存量 DNS 缓存;若使用这些非默认参数,必须对齐后才能归因。若 Server 将自己的公开服务证书也作为根信任,需要把相同公开证书加入探针的临时 CA 目录。 - -读结果的方法: - -- `tls_start` 后 `tls_done ... err=reset`:先查 TLS ClientHello、代理 CONNECT 或 TLS 终止设备;尚不能从客户端确定 RST 由终端还是中间设备发出。 -- `tls_done ... err=none`、`wrote_request` 后 reset:检查 HTTP/WAF/UA/入口路由。此时更换证书信任或密钥交换没有针对性。 -- 出现 x509 类错误:比较该进程实际收到的证书链、SNI、系统 CA 和自定义 CA;保持验证开启。 -- 两次 curl 的 IP 不同,或探针目标与 curl 不同:先做同 IP 对照。curl HTTP/1.1 成功也不代表 Go ClientHello 相同。 -- 仅 curl h2 成功:确认实际协商的 `http_version` 是 2,再检查入口 HTTP/1.1 支持;不要先全局启用 Server HTTP/2。 - -### 只对命中的分支做 A/B - -```sh -# TLS 阶段失败:仅移除 hybrid key exchange,仍支持 TLS 1.3 和验证证书。 -./oidc-probe -ca "$OIDC_CA" -classical - -# 如果旧进程确实使用 tlsmlkem=0,验证最小候选是否恢复其效果。 -# 应保留原 GODEBUG 的其他相关选项;以下假定没有需要保留的其他值。 -GODEBUG=tlsmlkem=0 ./oidc-probe -ca "$OIDC_CA" -default-curves - -# 只有 classical 仍失败时,作为鉴别实验测试 TLS 1.2-only。 -./oidc-probe -ca "$OIDC_CA" -tls12 - -# 只有 HTTP/ALPN 对照指向此分支时才测试。 -./oidc-probe -ca "$OIDC_CA" -h2 - -# HTTP 阶段才失败:UA_OLD / UA_NEW 是两版实际请求的完整 UA,非密钥。 -./oidc-probe -ca "$OIDC_CA" -ua "$UA_OLD" -./oidc-probe -ca "$OIDC_CA" -ua "$UA_NEW" -``` - -若 first-hop 使用代理,先比较两进程的代理选择和 `NO_PROXY`,不公开含密码的代理 URL。只在明确允许直连的部署中使用 `-direct`。确认直连后,可针对每个 DNS 地址运行以下两项,保留原 URL 主机名与 SNI,不能直接把 HTTPS URL 改成 IP: - -```sh -curl --noproxy '*' --resolve "$OIDC_HOST:443:$IP_A" --http1.1 \ - --connect-timeout 5 --max-time 20 -sS -o /dev/null \ - -w 'ip=%{remote_ip} status=%{http_code} verify=%{ssl_verify_result}\n' "$OIDC_URL" -./oidc-probe -ca "$OIDC_CA" -direct -ip "$IP_A" -# 对 IP-B 重复;端口不是 443 时据实修改 --resolve。 -``` - -仅第二次请求失败时才补 `-n 2` 与 `-n 2 -fresh`;后者重建 TCP,但同一 transport 的 TLS session cache 仍保留。若仍无法区分,下一步要的是入口侧同一时间窗口的握手失败原因/命中规则,或由客户自行脱敏后的 ClientHello 参数和 RST 阶段,不是完整认证流量包。 - -## 条件性最小修复方案 - -### 1. 路由、代理或入口策略差异已证实 - -优先统一有问题的 IdP 入口、修正具体域名的代理/NO_PROXY 或后端节点配置;更新错误拒绝合法 ClientHello 的 TLS 终止器/WAF。证书仍按原 hostname 和有效 CA 验证,OIDC issuer 不随意改名。若是 `Silo` UA 被规则拒绝,调整该规则;不把全产品 UA 改回 MinIO。 - -这是配置层处理,可以不改 SILO。工作量取决于入口归属;成功标准是原版故障二进制在原网络环境中完成 discovery、JWKS 和完整登录,而非仅 curl 200。 - -### 2. 证实旧环境依赖 `tlsmlkem=0`,且 classical / default-curves 对照成功 - -最小代码候选是为外部 OpenID 请求使用 Go 的默认曲线集合,恢复已有 GODEBUG 选项的效果;保留当前 trust pool、SNI、密码套件、超时、代理及 HTTP/1.1 行为。候选代码如下,**已在隔离副本完成上述验证,尚未应用到产品工作区**: - -```go -func NewOpenIDHTTPTransport() *http.Transport { - tr := NewHTTPTransport() - tr.TLSClientConfig.CurvePreferences = nil - return tr -} -``` - -当时的候选只将 `cmd/iam.go` 和 `cmd/config-current.go` 两处 OpenID 调用改用该 helper,通过现有 UA wrapper 传入 `openid.LookupConfig`。后续排查确认节点互联、复制、远端存储等连接也存在同样的问题,此局部方案已被 [SILO 跨组件修复](go127-stack.md) 取代;请勿再应用下面归档的 OIDC-only 补丁。 - -候选已在真实 transport 探针及第二轮完整 Linux Server 中验证:`CurvePreferences=nil` + Go 1.27.1 + `tlsmlkem=0` 会移除 ML-KEM,并通过 `reject-mlkem` fixture、配置添加和合成登录。它仍不能通过 `reject-mldsa` fixture,说明此方案针对的是一个明确分支。 - -影响需要明确: - -- 不设置 GODEBUG 时会采用完整 Go 默认集合,实测额外提供 SecP256r1MLKEM768 和 SecP384r1MLKEM1024;这也是一项兼容性变化。第二轮默认 TLS 1.3 登录通过,仍不能称为完全无行为变化或所有曲线均已覆盖。 -- `GODEBUG` 是进程级设置,其他使用 Go 默认曲线的客户端也可能受到影响;本次 helper 的改动范围是 OpenID,但该环境选项本身不是逐 provider 开关。 -- 禁用 hybrid 后仍有标准 ECDHE/TLS 和证书验证,失去的是相应后量子密钥交换保护。优先修正入口,临时兼容设置应有撤销条件;不能自动遇到 reset 就降级重试。 -- 当前 Console IdP transport 本来使用默认曲线,同一进程中的该 GODEBUG 策略可以与之保持一致;第二轮合成登录链路已经验证,真实 IdP 和浏览器验收仍待进行。 -- 隔离源码副本中的候选实现及上述本地验证已完成;真实端点 A/B 证据、生产适用性和发布是尚未完成的步骤。 - -### 3. 未设置上述选项,或 classical 仍失败 - -不要套用方案 2。若同 IP、同代理、同 UA 下仅 Go 1.27 失败,优先收集入口对新签名算法/扩展的处理证据。`-tls12` 成功只能缩小到 ClientHello/TLS 特征集合;它同时移除 hybrid key share 和 TLS 1.3 特有签名编号,不能唯一证明 ML-DSA。 - -优先更新入口或获得 Go 上游可复现用例。只有真实证据证明 TLS 1.2 兼容模式是必要且有效、入口又短期无法处理时,才评估**明确限定于该 IdP**的临时 TLS 1.2 选项,并保留现代 ECDHE/AEAD 和证书验证。不要把 `MaxVersion=TLS12` 全局写死,不引入自定义 ClientHello/TLS 栈或未受支持的“关闭 ML-DSA”环境选项。该分支尚不足以确定补丁或工期。 - -### 4. 启动健壮性可单独改进 - -如需小幅改善可诊断性,应单独给 discovery/JWKS 加明确阶段标识和有限总请求期限,让 IAM 重试可感知取消;在请求完成前发生 stall 时及时释放资源。日志避免输出 client secret/token,错误保留原始 cause。保留已配置 OIDC 的失败关闭行为及 IdP 恢复后的自动初始化,不自动关闭 OIDC,也不把无限重试包装成根因修复。 - -这类改动约 0.5–1 个工程日,可分别回归 stalled body、取消、重试后恢复;它不会使持续 RST 的 TLS 连接成功,不应替代前面鉴别。 - -## 临时处置与验收边界 - -当前可沿用报告中已经恢复服务的旧版回退状态,尽快完成上述定位。不要将旧版本长期保留视为解决方案,也不要为了它整体降级新版本依赖。本次空数据实验不证明任意生产数据/配置的降级兼容;再次切换版本前应沿既有备份和升级边界操作。 - -修复应按以下范围验证,均从 PGSTY 源码本地构建: - -1. 对确认的分支增加最小回归:实际 OpenID transport、匹配的 ClientHello/入口拒绝条件、默认设置与明确 opt-out;确保通用和 internode transport 不变。 -2. TLS 1.2(报告密码套件)与 TLS 1.3、有效自定义 CA、错误 CA 和 hostname 拒绝;适用时覆盖代理及多地址入口。不放宽证书、JWT 签名或 audience 等认证校验。 -3. 完整 Server 的 discovery 与 JWKS、IdP 中断后恢复、cluster health、受认证 Admin 操作;Console 添加配置、浏览器重定向、回调、令牌交换、STS 授权和登出。mc 添加/读取配置路径应一致。 -4. 在实际 Linux 容器/Pod 网络和每个 IdP 入口地址复验。证据应绑定最终 Server/Console/pkg/mc commit 和编译器;发布、镜像及生产可用性属于后续独立验收。 - -第二轮完成 Linux loopback 上的合成 OAuth token/STS 登录;本次没有真实 Keycloak、真实 Linux 故障路径、浏览器页面或生产数据升级测试。当前 main 对正常 fixture 成功,并不能据此宣告 #154 修复。下一项最有价值的新增证据是**实际进程的 `tlsmlkem` 设置和一个带 TLS 完成事件的同环境 GET trace**。 - -## 复现实验 - -从本任务 worktree 根目录执行;工具均使用本地源码,输出目录必须是新建临时目录。 - -```sh -LAB_DIR=$(mktemp -d) -CGO_ENABLED=0 GOWORK=off go build -mod=readonly -tags kqueue \ - -o "$LAB_DIR/silo" . -CGO_ENABLED=0 GOWORK=off go build -mod=readonly -tags kqueue \ - -o "$LAB_DIR/probe" docs/investigations/issue-154/probe.go -go build -o "$LAB_DIR/fixture" docs/investigations/issue-154/fixture.go -go build -mod=readonly -o "$LAB_DIR/admin-check" docs/investigations/issue-154/admin-check.go -"$LAB_DIR/fixture" -dir "$LAB_DIR/idp" > "$LAB_DIR/hello.jsonl" 2> "$LAB_DIR/fixture.log" & -FIXTURE_PID=$! -trap 'kill "$FIXTURE_PID" 2>/dev/null || true' EXIT -attempt=0 -while [ ! -s "$LAB_DIR/idp/url" ] && [ "$attempt" -lt 50 ]; do - sleep 0.1 - attempt=$((attempt + 1)) -done -test -s "$LAB_DIR/idp/url" || exit 1 -# 私钥仅保存在 fixture 内存。 -OIDC_URL="$(cat "$LAB_DIR/idp/url")/.well-known/openid-configuration" -export OIDC_URL -"$LAB_DIR/probe" -ca "$LAB_DIR/idp/ca.pem" -printf '%s' reject-mlkem > "$LAB_DIR/idp/mode" -GODEBUG=tlsmlkem=0 "$LAB_DIR/probe" -ca "$LAB_DIR/idp/ca.pem" -GODEBUG=tlsmlkem=0 "$LAB_DIR/probe" -ca "$LAB_DIR/idp/ca.pem" -default-curves -printf '%s' reject-mldsa > "$LAB_DIR/idp/mode" -"$LAB_DIR/probe" -ca "$LAB_DIR/idp/ca.pem" -classical -"$LAB_DIR/probe" -ca "$LAB_DIR/idp/ca.pem" -tls12 -kill "$FIXTURE_PID" -``` - -完整 Server 实验使用上述临时目录下的 `certs/CAs` 和数据目录,设置虚构的 `MINIO_IDENTITY_OPENID_CLIENT_ID`、fixture URL、临时 root 凭据,并显式指定 `--config-dir`、`--certs-dir`、loopback API/Console 地址。用 mode 文件切换 `reset`/`bad-jwks` 到 `normal`,同时检查 cluster health 和 Admin ListUsers;结果已保存在 `evidence.json`。`admin-check add` 只操作 `LAB_SERVER=127.0.0.1:port` 的实验实例,读取临时 `LAB_USER`、`LAB_PASSWORD`、`LAB_OIDC_URL`,使用虚构 OIDC secret。 - -跨版本构建时从各 tag `git archive` 获取源码,把 `probe.go` 放入该 module 根目录;旧版探针仅将 certs 导入替换为 `github.com/minio/pkg/v3/certs`,由旧版 go.mod 选择 PGSTY v3.11.0。分别强制 `GOTOOLCHAIN=go1.26.5` / `go1.27.1`,使用 `-mod=readonly`,不修改历史 go.mod。另将旧源码原依赖用 Go 1.27.1 构建,隔离工具链因素。若为 Linux 客户端构建探针,按已确认的架构设置 `GOOS=linux GOARCH=amd64` 或 `arm64`,不使用 Server 镜像代替。 - -### 第二轮 Linux 完整 Server 对照 - -从本任务根目录运行以下 Bash 命令。需要预先准备 Go 1.26.5、1.27.1 及源码依赖缓存,以及带 Python 3、curl/OpenSSL/HTTP2 的通用 Linux 基础镜像。`GOTOOLCHAIN` 明确选定编译器,`-mod=readonly` 保留依赖版本;实际测试构建用已安装工具链的绝对路径配合 `GOTOOLCHAIN=local`,等价地避免自动切换编译器。以下固定 `arm64` 与本次实验一致。 - -```bash -LAB_DIR=$(mktemp -d) -mkdir -p "$LAB_DIR/old" "$LAB_DIR/head" "$LAB_DIR/candidate" "$LAB_DIR/bin" "$LAB_DIR/out" -git archive d88f46ccee345a9c2fabe2d221d9a9e56bc11aec | tar -x -C "$LAB_DIR/old" -git archive d1105bbb3d4a0afa33b3a4ac11b821235038ed0e | tar -x -C "$LAB_DIR/head" -git archive d1105bbb3d4a0afa33b3a4ac11b821235038ed0e | tar -x -C "$LAB_DIR/candidate" -git -C "$LAB_DIR/candidate" apply "$PWD/docs/investigations/issue-154/openid-default-curves.patch" - -(cd "$LAB_DIR/old" && CGO_ENABLED=0 GOWORK=off GOOS=linux GOARCH=arm64 \ - GOTOOLCHAIN=go1.26.5 go build -mod=readonly -tags kqueue -o "$LAB_DIR/bin/old-go126" .) -(cd "$LAB_DIR/old" && CGO_ENABLED=0 GOWORK=off GOOS=linux GOARCH=arm64 \ - GOTOOLCHAIN=go1.27.1 go build -mod=readonly -tags kqueue -o "$LAB_DIR/bin/old-go127" .) -(cd "$LAB_DIR/head" && CGO_ENABLED=0 GOWORK=off GOOS=linux GOARCH=arm64 \ - GOTOOLCHAIN=go1.27.1 go build -mod=readonly -tags kqueue -o "$LAB_DIR/bin/head-go127" .) -(cd "$LAB_DIR/candidate" && CGO_ENABLED=0 GOWORK=off GOOS=linux GOARCH=arm64 \ - GOTOOLCHAIN=go1.27.1 go build -mod=readonly -tags kqueue -o "$LAB_DIR/bin/candidate-go127" .) -CGO_ENABLED=0 GOWORK=off GOOS=linux GOARCH=arm64 GOTOOLCHAIN=go1.27.1 \ - go build -mod=readonly -o "$LAB_DIR/bin/fixture" docs/investigations/issue-154/fixture.go -CGO_ENABLED=0 GOWORK=off GOOS=linux GOARCH=arm64 GOTOOLCHAIN=go1.27.1 \ - go build -mod=readonly -o "$LAB_DIR/bin/admin-check" docs/investigations/issue-154/admin-check.go - -# 本机已存在的通用 Debian 12 arm64 基础镜像;不拉取镜像,不映射端口。 -LINUX_BASE_IMAGE=sha256:307af7711e2e04ab75759cb42a1eef45c43c4404894c0e30dd19f742b107b922 -docker run --rm --pull=never --network none \ - --mount "type=bind,source=$LAB_DIR/bin,target=/lab/bin,readonly" \ - --mount "type=bind,source=$LAB_DIR/out,target=/lab/out" \ - --mount "type=bind,source=$PWD/docs/investigations/issue-154/run-linux.py,target=/lab/run-linux.py,readonly" \ - --entrypoint python3 "$LINUX_BASE_IMAGE" /lab/run-linux.py -``` - -每组场景输出一行摘要,同时在独立输出目录保存 `result.json`。脚本用 `finally` 终止其 Server/fixture,容器结束自动删除。它没有导出会话 cookie、授权码、JWT、state 或临时密码;原始运行目录只用于该次隔离实验,交付证据只保留握手、结果和构建身份。 diff --git a/docs/investigations/issue-154/admin-check.go b/docs/investigations/issue-154/admin-check.go deleted file mode 100644 index 6e230e160..000000000 --- a/docs/investigations/issue-154/admin-check.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build ignore - -// Loopback-only lab client for the Admin API used by Console's OIDC form. -package main - -import ( - "context" - "fmt" - "net" - "os" - "time" - - "github.com/minio/madmin-go/v3" -) - -func main() { - endpoint := os.Getenv("LAB_SERVER") - host, _, err := net.SplitHostPort(endpoint) - if err != nil || host != "127.0.0.1" { - panic("LAB_SERVER must use IPv4 loopback") - } - a, err := madmin.New(endpoint, os.Getenv("LAB_USER"), os.Getenv("LAB_PASSWORD"), false) - if err != nil { - panic(err) - } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if len(os.Args) > 1 && os.Args[1] == "add" { - restart, err := a.AddOrUpdateIDPConfig(ctx, "openid", "local154", "enable=on client_id=local154 client_secret=local154-placeholder config_url="+os.Getenv("LAB_OIDC_URL"), false) - fmt.Printf("add restart=%v err=%v\n", restart, err) - if err != nil { - os.Exit(1) - } - return - } - users, err := a.ListUsers(ctx) - fmt.Printf("list_users count=%d err=%v\n", len(users), err) - if err != nil { - os.Exit(1) - } -} diff --git a/docs/investigations/issue-154/cert-roots.go b/docs/investigations/issue-154/cert-roots.go deleted file mode 100644 index 8fdfcf808..000000000 --- a/docs/investigations/issue-154/cert-roots.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build ignore - -// Run only with the public, synthetic CA made by fixture.go. -package main - -import ( - "crypto/x509" - "encoding/json" - "encoding/pem" - "os" - "runtime" - - "github.com/pgsty/silo-pkg/v3/certs" -) - -func main() { - if len(os.Args) != 3 { - panic("usage: cert-roots synthetic-ca.pem explicit-ca-path-or-empty") - } - data, err := os.ReadFile(os.Args[1]) - if err != nil { - panic(err) - } - block, _ := pem.Decode(data) - if block == nil || block.Type != "CERTIFICATE" { - panic("expected public certificate") - } - certificate, err := x509.ParseCertificate(block.Bytes) - if err != nil { - panic(err) - } - roots, err := certs.GetRootCAs(os.Args[2]) - if err != nil { - panic(err) - } - _, err = certificate.Verify(x509.VerifyOptions{Roots: roots}) - _ = json.NewEncoder(os.Stdout).Encode(map[string]any{ - "go": runtime.Version(), "os": runtime.GOOS, - "explicit_ca": os.Args[2] != "", "trusted": err == nil, - }) -} diff --git a/docs/investigations/issue-154/evidence.json b/docs/investigations/issue-154/evidence.json deleted file mode 100644 index 37219094c..000000000 --- a/docs/investigations/issue-154/evidence.json +++ /dev/null @@ -1,2499 +0,0 @@ -{ - "environment": "darwin/arm64; Server and transport probes locally built with CGO_ENABLED=0 and GOWORK=off; auxiliary fixture/Admin programs locally built; no Server/Console images", - "versions": { - "old": { - "sha": "d88f46ccee345a9c2fabe2d221d9a9e56bc11aec", - "toolchain": "go1.26.5" - }, - "reported": { - "sha": "9b11dc9469e650815b775cb47b039610644f5da4", - "toolchain": "go1.27.1" - }, - "current": { - "sha": "d1105bbb3d4a0afa33b3a4ac11b821235038ed0e", - "toolchain": "go1.27.1" - } - }, - "probe_matrix": [ - { - "mode": "normal", - "version": "old", - "options": [], - "godebug": null, - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 1497, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.26.5 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "normal", - "version": "reported", - "options": [], - "godebug": null, - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "normal", - "version": "current", - "options": [], - "godebug": null, - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "normal", - "version": "old", - "options": [], - "godebug": "tlsmlkem=0", - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 275, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.26.5 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "normal", - "version": "reported", - "options": [], - "godebug": "tlsmlkem=0", - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "normal", - "version": "current", - "options": [], - "godebug": "tlsmlkem=0", - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "reject-mlkem", - "version": "old", - "options": [], - "godebug": null, - "exit": 1, - "events": [ - { - "alpn": null, - "bytes_read": 1497, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - } - ], - "trace": "go=go1.26.5 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x0 cipher=0x0000 alpn=\"\" resumed=false verified_chains=0 err=connection reset by peer (*net.OpError)\nget_error=connection reset by peer (*url.Error)\n" - }, - { - "mode": "reject-mlkem", - "version": "old", - "options": [], - "godebug": "tlsmlkem=0", - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 275, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.26.5 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "reject-mlkem", - "version": "reported", - "options": [], - "godebug": "tlsmlkem=0", - "exit": 1, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x0 cipher=0x0000 alpn=\"\" resumed=false verified_chains=0 err=connection reset by peer (*net.OpError)\nget_error=connection reset by peer (*url.Error)\n" - }, - { - "mode": "reject-mlkem", - "version": "reported", - "options": [ - "-classical" - ], - "godebug": null, - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=true tls12=false\nroute=direct curves=[CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "reject-mldsa", - "version": "old", - "options": [], - "godebug": null, - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 1497, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mldsa", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.26.5 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "reject-mldsa", - "version": "reported", - "options": [], - "godebug": null, - "exit": 1, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mldsa", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x0 cipher=0x0000 alpn=\"\" resumed=false verified_chains=0 err=connection reset by peer (*net.OpError)\nget_error=connection reset by peer (*url.Error)\n" - }, - { - "mode": "reject-mldsa", - "version": "reported", - "options": [ - "-classical" - ], - "godebug": null, - "exit": 1, - "events": [ - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mldsa", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=true tls12=false\nroute=direct curves=[CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x0 cipher=0x0000 alpn=\"\" resumed=false verified_chains=0 err=connection reset by peer (*net.OpError)\nget_error=connection reset by peer (*url.Error)\n" - }, - { - "mode": "reject-mldsa", - "version": "reported", - "options": [ - "-tls12" - ], - "godebug": null, - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 219, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mldsa", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=true\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "require-h2", - "version": "old", - "options": [], - "godebug": null, - "exit": 1, - "events": [ - { - "alpn": null, - "bytes_read": 1497, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "require-h2", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - } - ], - "trace": "go=go1.26.5 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x0 cipher=0x0000 alpn=\"\" resumed=false verified_chains=0 err=connection reset by peer (*net.OpError)\nget_error=connection reset by peer (*url.Error)\n" - }, - { - "mode": "require-h2", - "version": "reported", - "options": [], - "godebug": null, - "exit": 1, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "require-h2", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x0 cipher=0x0000 alpn=\"\" resumed=false verified_chains=0 err=connection reset by peer (*net.OpError)\nget_error=connection reset by peer (*url.Error)\n" - }, - { - "mode": "require-h2", - "version": "reported", - "options": [ - "-h2" - ], - "godebug": null, - "exit": 0, - "events": [ - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 1527, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "require-h2", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=true classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"h2\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/2.0\n" - }, - { - "mode": "reject-silo-ua", - "version": "reported", - "options": [ - "-ua", - "MinIO (local probe)" - ], - "godebug": null, - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-silo-ua", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "MinIO (local probe)" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "reject-silo-ua", - "version": "reported", - "options": [ - "-ua", - "Silo (local probe)" - ], - "godebug": null, - "exit": 1, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-silo-ua", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (local probe)" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nget_error=connection reset by peer (*url.Error)\n" - } - ], - "same_source_toolchains": [ - { - "mode": "normal", - "godebug": null, - "source": "old", - "go": "1.27.1", - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "normal", - "godebug": "tlsmlkem=0", - "source": "old", - "go": "1.27.1", - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "source": "old", - "go": "1.27.1", - "exit": 1, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x0 cipher=0x0000 alpn=\"\" resumed=false verified_chains=0 err=connection reset by peer (*net.OpError)\nget_error=connection reset by peer (*url.Error)\n" - }, - { - "mode": "reject-mldsa", - "godebug": null, - "source": "old", - "go": "1.27.1", - "exit": 1, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mldsa", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false tls12=false\nroute=direct curves=[X25519MLKEM768 CurveP256 X25519 CurveP384 CurveP521]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x0 cipher=0x0000 alpn=\"\" resumed=false verified_chains=0 err=connection reset by peer (*net.OpError)\nget_error=connection reset by peer (*url.Error)\n" - } - ], - "candidate_probe": [ - { - "mode": "normal", - "godebug": null, - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 1513, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false default_curves=true tls12=false\nroute=direct curves=[]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "normal", - "godebug": "tlsmlkem=0", - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false default_curves=true tls12=false\nroute=direct curves=[]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "exit": 0, - "events": [ - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "issue-154-probe" - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false default_curves=true tls12=false\nroute=direct curves=[]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x303 cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 alpn=\"\" resumed=false verified_chains=1 err=none\ngot_conn=127.0.0.1:54794 reused=false\nwrote_request err=none\nfirst_response_byte\nstatus=200 protocol=HTTP/1.1\n" - }, - { - "mode": "reject-mldsa", - "godebug": "tlsmlkem=0", - "exit": 1, - "events": [ - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mldsa", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - } - ], - "trace": "go=go1.27.1 os=darwin arch=arm64 h2=false classical=false default_curves=true tls12=false\nroute=direct curves=[]\nrequest=1\nconnect=tcp 127.0.0.1:54794\nconnected=127.0.0.1:54794 err=none\ntls_start\ntls_done=0x0 cipher=0x0000 alpn=\"\" resumed=false verified_chains=0 err=connection reset by peer (*net.OpError)\nget_error=connection reset by peer (*url.Error)\n" - } - ], - "server_cases": [ - { - "case": "startup-old", - "version": "old", - "mode": "normal", - "godebug": null, - "before": { - "live": { - "status": 200, - "server_status": null - }, - "ready": { - "status": 200, - "server_status": null - }, - "cluster": { - "status": 200, - "server_status": null - }, - "console": { - "status": 200, - "server_status": null - } - }, - "before_admin": "list_users count=0 err=", - "events": [ - { - "alpn": null, - "bytes_read": 1497, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "MinIO (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "MinIO (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - } - ] - }, - { - "case": "startup-reported", - "version": "reported", - "mode": "normal", - "godebug": null, - "before": { - "live": { - "status": 200, - "server_status": null - }, - "ready": { - "status": 200, - "server_status": null - }, - "cluster": { - "status": 200, - "server_status": null - }, - "console": { - "status": 200, - "server_status": null - } - }, - "before_admin": "list_users count=0 err=", - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - } - ] - }, - { - "case": "startup-current-reset-recovery", - "version": "current", - "mode": "reset", - "godebug": null, - "before": { - "live": { - "status": 200, - "server_status": null - }, - "ready": { - "status": 200, - "server_status": null - }, - "cluster": { - "status": 503, - "server_status": "iam-offline" - }, - "console": { - "error": "URLError" - } - }, - "before_admin": "list_users count=0 err=context deadline exceeded", - "recovery_seconds": 0.433, - "after": { - "live": { - "status": 200, - "server_status": null - }, - "ready": { - "status": 200, - "server_status": null - }, - "cluster": { - "status": 200, - "server_status": null - }, - "console": { - "status": 200, - "server_status": null - } - }, - "after_admin": "list_users count=0 err=", - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reset", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reset", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reset", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - } - ] - }, - { - "case": "startup-old-debug", - "version": "old", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "before": { - "live": { - "status": 200, - "server_status": null - }, - "ready": { - "status": 200, - "server_status": null - }, - "cluster": { - "status": 200, - "server_status": null - }, - "console": { - "status": 200, - "server_status": null - } - }, - "before_admin": "list_users count=0 err=", - "events": [ - { - "alpn": null, - "bytes_read": 275, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "MinIO (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "MinIO (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - } - ] - }, - { - "case": "startup-reported-debug", - "version": "reported", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "before": { - "live": { - "status": 200, - "server_status": null - }, - "ready": { - "status": 200, - "server_status": null - }, - "cluster": { - "status": 503, - "server_status": "iam-offline" - }, - "console": { - "error": "URLError" - } - }, - "before_admin": "list_users count=0 err=context deadline exceeded", - "recovery_seconds": 1.38, - "after": { - "live": { - "status": 200, - "server_status": null - }, - "ready": { - "status": 200, - "server_status": null - }, - "cluster": { - "status": 200, - "server_status": null - }, - "console": { - "status": 200, - "server_status": null - } - }, - "after_admin": "list_users count=0 err=", - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - } - ] - }, - { - "case": "startup-current-jwks-recovery", - "version": "current", - "mode": "bad-jwks", - "godebug": null, - "before": { - "live": { - "status": 200, - "server_status": null - }, - "ready": { - "status": 200, - "server_status": null - }, - "cluster": { - "status": 503, - "server_status": "iam-offline" - }, - "console": { - "error": "URLError" - } - }, - "before_admin": "list_users count=0 err=context deadline exceeded", - "recovery_seconds": 0.328, - "after": { - "live": { - "status": 200, - "server_status": null - }, - "ready": { - "status": 200, - "server_status": null - }, - "cluster": { - "status": 200, - "server_status": null - }, - "console": { - "status": 200, - "server_status": null - } - }, - "after_admin": "list_users count=0 err=", - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "bad-jwks", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "bad-jwks", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "bad-jwks", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "bad-jwks", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "bad-jwks", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "bad-jwks", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - } - ] - }, - { - "case": "admin-add-valid", - "version": "current", - "mode": "reset", - "godebug": null, - "before": { - "live": { - "status": 200, - "server_status": null - }, - "ready": { - "status": 200, - "server_status": null - }, - "cluster": { - "status": 200, - "server_status": null - }, - "console": { - "status": 200, - "server_status": null - } - }, - "before_admin": "list_users count=0 err=", - "add_failed": { - "exit": 1, - "output": "add restart=false err=Get \"https://127.0.0.1:54794/.well-known/openid-configuration\": read tcp 127.0.0.1:56173->127.0.0.1:54794: read: connection reset by peer" - }, - "add_recovered": { - "exit": 0, - "output": "add restart=true err=" - }, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reset", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "ua": "Silo (darwin; arm64; mode-server-xl-single; source DEVELOPMENT.GOGET DEVELOPMENT.GOGET DEVELOPMENT.GOGET; CPU (total_cpus:1, total_cores:1; vendor:; family:0; model:0; stepping:0; model_name:Apple M5 Max))" - } - ] - } - ], - "binary_build_info": { - "old": [ - "go1.26.5", - "\tdep\tgithub.com/coreos/go-oidc/v3\tv3.17.0\th1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=", - "\t=>\tgithub.com/pgsty/silo-console\tv0.0.0-20260804042150-b952a1202869\th1:HLfc2ZdAycnI/bLl+TdsuckyzSWrwQarK38pS14yH/Q=", - "\t=>\tgithub.com/pgsty/mc\tv0.0.0-20260801042411-ad10a2a10b76\th1:UIlUuz0LQKw4QlAljhv7nPDDFC1+n+e0iED7rWZrgZ8=", - "\t=>\tgithub.com/pgsty/silo-pkg/v3\tv3.11.0\th1:wjN5d+tWD8Twq+e7k/KBBVhnWXC8xTIlfTcnGIKkmjc=", - "\tdep\tgolang.org/x/crypto\tv0.54.0\th1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=", - "\tdep\tgolang.org/x/net\tv0.57.0\th1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=", - "\tdep\tgolang.org/x/oauth2\tv0.36.0\th1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=", - "\tbuild\t-tags=kqueue", - "\tbuild\tCGO_ENABLED=0", - "\tbuild\tGOARCH=arm64", - "\tbuild\tGOOS=darwin" - ], - "reported": [ - "go1.27.1", - "\tdep\tgithub.com/coreos/go-oidc/v3\tv3.21.0\th1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM=", - "\t=>\tgithub.com/pgsty/silo-console\tv0.0.0-20260903111932-464a59d73ada\th1:vJkxm7GTLL0AvIGIt+JxMfhjMO5+msmj4rLeFueTaG8=", - "\t=>\tgithub.com/pgsty/mc\tv0.0.0-20260903063637-a2ef95c035d9\th1:kJkqK0hJrmvdTOPiI98HoPmET0SHYI9+ytaTRzQdGAs=", - "\tdep\tgithub.com/pgsty/silo-pkg/v3\tv3.13.2\th1:Clw11c/J54Tx6pijNCWtXiC7e0fwP/f5Tgeb6fsXg2w=", - "\tdep\tgolang.org/x/crypto\tv0.56.0\th1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=", - "\tdep\tgolang.org/x/net\tv0.58.0\th1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=", - "\tdep\tgolang.org/x/oauth2\tv0.36.0\th1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=", - "\tbuild\t-tags=kqueue", - "\tbuild\tCGO_ENABLED=0", - "\tbuild\tGOARCH=arm64", - "\tbuild\tGOOS=darwin" - ], - "current": [ - "go1.27.1", - "\tdep\tgithub.com/coreos/go-oidc/v3\tv3.21.0\th1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM=", - "\t=>\tgithub.com/pgsty/silo-console\tv0.0.0-20260908142700-c103d08ec36a\th1:aHLqQ7INozqGLEOB1tr+n/eKgrBhlQ20fHyLmeNu+ao=", - "\t=>\tgithub.com/pgsty/mc\tv0.0.0-20260909015522-fcd5cad8247f\th1:JiL/FcsGMsAhA+Iv+0Jzk9VnEAVVv4HUNbR0cGf+/CE=", - "\tdep\tgithub.com/pgsty/silo-pkg/v3\tv3.13.3\th1:d2xYTn4LXoWIAIjBlW/17wtA/Ut1ap29t+1ww4TFa8o=", - "\tdep\tgolang.org/x/crypto\tv0.56.0\th1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=", - "\tdep\tgolang.org/x/net\tv0.58.0\th1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=", - "\tdep\tgolang.org/x/oauth2\tv0.36.0\th1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=", - "\tbuild\t-tags=kqueue", - "\tbuild\tCGO_ENABLED=0", - "\tbuild\tGOARCH=arm64", - "\tbuild\tGOOS=darwin" - ] - } -} diff --git a/docs/investigations/issue-154/fixture.go b/docs/investigations/issue-154/fixture.go deleted file mode 100644 index 728819781..000000000 --- a/docs/investigations/issue-154/fixture.go +++ /dev/null @@ -1,179 +0,0 @@ -//go:build ignore - -// Loopback-only synthetic OIDC/TLS fixture. Only disposable lab identities are used. -// Rejection modes model hypotheses; they are not evidence about the user's IdP. -package main - -import ( - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/base64" - "encoding/json" - "encoding/pem" - "errors" - "flag" - "fmt" - "math/big" - "net" - "net/http" - "net/url" - "os" - "path/filepath" - "slices" - "strings" - "sync" - "time" -) - -type observedConn struct { - net.Conn - readBytes int -} - -func (c *observedConn) Read(p []byte) (int, error) { - n, e := c.Conn.Read(p) - c.readBytes += n - return n, e -} -func (c *observedConn) reset() { _ = c.Conn.(*net.TCPConn).SetLinger(0); _ = c.Conn.Close() } - -type observedListener struct{ net.Listener } - -func (l observedListener) Accept() (net.Conn, error) { - c, e := l.Listener.Accept() - if e != nil { - return nil, e - } - return &observedConn{Conn: c}, nil -} - -func main() { - dir := flag.String("dir", "", "isolated output directory for public CA, URL, and mode file") - tls13 := flag.Bool("tls13", false, "allow TLS 1.3 in addition to the TLS 1.2 baseline") - flag.Parse() - if *dir == "" { - panic("-dir required") - } - must(os.MkdirAll(*dir, 0700)) - key, err := rsa.GenerateKey(rand.Reader, 2048) - must(err) - root := &x509.Certificate{SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "issue-154 local CA"}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(24 * time.Hour), IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature} - rootDER, err := x509.CreateCertificate(rand.Reader, root, root, &key.PublicKey, key) - must(err) - leaf := &x509.Certificate{SerialNumber: big.NewInt(2), Subject: pkix.Name{CommonName: "localhost"}, DNSNames: []string{"localhost"}, IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, NotBefore: root.NotBefore, NotAfter: root.NotAfter, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature} - leafDER, err := x509.CreateCertificate(rand.Reader, leaf, root, &key.PublicKey, key) - must(err) - must(os.WriteFile(filepath.Join(*dir, "ca.pem"), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootDER}), 0600)) - ln, err := net.Listen("tcp", "127.0.0.1:0") - must(err) - base := "https://" + ln.Addr().String() - must(os.WriteFile(filepath.Join(*dir, "url"), []byte(base), 0600)) - mode := func() string { b, _ := os.ReadFile(filepath.Join(*dir, "mode")); return strings.TrimSpace(string(b)) } - var mu sync.Mutex - log := func(v any) { mu.Lock(); defer mu.Unlock(); _ = json.NewEncoder(os.Stdout).Encode(v) } - tc := &tls.Config{Certificates: []tls.Certificate{{Certificate: [][]byte{leafDER, rootDER}, PrivateKey: key}}, MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, CurvePreferences: []tls.CurveID{tls.CurveP256}, CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}} - if *tls13 { - tc.MaxVersion = tls.VersionTLS13 - } - peerConfig := tc.Clone() - peerConfig.NextProtos = []string{"h2", "http/1.1"} - // net/http validates HTTP/2 support before GetConfigForClient; the fixture - // then deliberately selects only the reported AES-256 suite. - tc.CipherSuites = append(tc.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) - tc.GetConfigForClient = func(chi *tls.ClientHelloInfo) (*tls.Config, error) { - m := mode() - c := chi.Conn.(*observedConn) - log(map[string]any{"event": "hello", "mode": m, "bytes_read": c.readBytes, "curves": chi.SupportedCurves, "signatures": chi.SignatureSchemes, "alpn": chi.SupportedProtos, "versions": chi.SupportedVersions}) - reject := m == "reset" || m == "reject-mlkem" && slices.Contains(chi.SupportedCurves, tls.CurveID(4588)) || m == "reject-mldsa" && slices.Contains(chi.SignatureSchemes, tls.SignatureScheme(0x0904)) || m == "require-h2" && !slices.Contains(chi.SupportedProtos, "h2") - if reject { - c.reset() - return nil, errors.New("synthetic ClientHello rejection") - } - return peerConfig, nil - } - server := &http.Server{TLSConfig: tc, ReadHeaderTimeout: 5 * time.Second} - var codes sync.Map - server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log(map[string]any{"event": "request", "path": r.URL.Path, "protocol": r.Proto, "tls": r.TLS.Version, "cipher": r.TLS.CipherSuite, "resumed": r.TLS.DidResume, "ua": r.UserAgent()}) - if mode() == "reject-silo-ua" && strings.HasPrefix(r.UserAgent(), "Silo") { - c, _, e := w.(http.Hijacker).Hijack() - if e == nil { - c.(*tls.Conn).NetConn().(*observedConn).reset() - } - return - } - w.Header().Set("Content-Type", "application/json") - switch r.URL.Path { - case "/authorize": - q := r.URL.Query() - redirect, err := url.Parse(q.Get("redirect_uri")) - if err != nil || redirect.Scheme != "http" || redirect.Hostname() != "127.0.0.1" || redirect.Path != "/oauth_callback" || q.Get("client_id") != "local154" { - http.Error(w, "loopback lab authorization only", 400) - return - } - codeBytes := make([]byte, 18) - _, err = rand.Read(codeBytes) - must(err) - code := base64.RawURLEncoding.EncodeToString(codeBytes) - codes.Store(code, q.Get("nonce")) - values := redirect.Query() - values.Set("code", code) - values.Set("state", q.Get("state")) - redirect.RawQuery = values.Encode() - http.Redirect(w, r, redirect.String(), http.StatusFound) - case "/token": - if r.Method != http.MethodPost || r.ParseForm() != nil { - http.Error(w, "bad token request", 400) - return - } - id, secret, ok := r.BasicAuth() - if !ok { - id, secret = r.Form.Get("client_id"), r.Form.Get("client_secret") - } - nonce, found := codes.LoadAndDelete(r.Form.Get("code")) - if id != "local154" || secret != "local154-placeholder" || !found || r.Form.Get("grant_type") != "authorization_code" { - w.WriteHeader(400) - _ = json.NewEncoder(w).Encode(map[string]string{"error": "invalid_grant"}) - return - } - audience := id - if mode() == "bad-audience" { - audience = "different-lab-client" - } - claims, _ := json.Marshal(map[string]any{"iss": base, "sub": "local154-user", "aud": audience, "iat": time.Now().Unix(), "exp": time.Now().Add(time.Hour).Unix(), "policy": "readwrite", "nonce": nonce}) - header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"RS256","kid":"local-154","typ":"JWT"}`)) - payload := header + "." + base64.RawURLEncoding.EncodeToString(claims) - hash := sha256.Sum256([]byte(payload)) - signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, hash[:]) - must(err) - if mode() == "bad-signature" { - signature[0] ^= 1 - } - token := payload + "." + base64.RawURLEncoding.EncodeToString(signature) - _ = json.NewEncoder(w).Encode(map[string]any{"access_token": token, "id_token": token, "token_type": "Bearer", "expires_in": 3600}) - case "/.well-known/openid-configuration": - _ = json.NewEncoder(w).Encode(map[string]any{"issuer": base, "jwks_uri": base + "/jwks", "authorization_endpoint": base + "/authorize", "token_endpoint": base + "/token", "response_types_supported": []string{"code"}, "subject_types_supported": []string{"public"}, "id_token_signing_alg_values_supported": []string{"RS256"}, "scopes_supported": []string{"openid"}}) - case "/jwks": - if mode() == "bad-jwks" { - http.Error(w, "synthetic JWKS outage", 503) - return - } - _ = json.NewEncoder(w).Encode(map[string]any{"keys": []any{map[string]any{"kty": "RSA", "kid": "local-154", "use": "sig", "alg": "RS256", "n": base64.RawURLEncoding.EncodeToString(key.N.Bytes()), "e": "AQAB"}}}) - default: - http.NotFound(w, r) - } - }) - fmt.Fprintln(os.Stderr, base) - must(server.ServeTLS(observedListener{ln}, "", "")) -} - -func must(err error) { - if err != nil { - panic(err) - } -} diff --git a/docs/investigations/issue-154/linux-evidence.json b/docs/investigations/issue-154/linux-evidence.json deleted file mode 100644 index 9554943f5..000000000 --- a/docs/investigations/issue-154/linux-evidence.json +++ /dev/null @@ -1,3423 +0,0 @@ -{ - "recorded_utc": "2026-09-09T08:19:11.671834+00:00", - "scope": "Isolated linux/arm64 integration tests of locally source-built Server and embedded Console. Synthetic loopback OIDC issuer; not the customer endpoint or real Keycloak. Browser flow driven through real Console HTTP APIs; no rendered browser UI validation.", - "container": { - "base_image": "existing generic pgsty/d12a:build", - "image_id": "sha256:307af7711e2e04ab75759cb42a1eef45c43c4404894c0e30dd19f742b107b922", - "os_arch": "linux/arm64", - "network": "none; all fixture/Server/Console traffic inside one loopback namespace", - "published_ports": [], - "server_or_console_image_used": false, - "curl": "7.88.1, OpenSSL 3.0.20, nghttp2, same namespace as Server" - }, - "build_flags": { - "CGO_ENABLED": "0", - "GOWORK": "off", - "GOOS": "linux", - "GOARCH": "arm64", - "GOTOOLCHAIN": "local", - "arguments": [ - "build", - "-mod=readonly", - "-tags", - "kqueue" - ], - "release_ldflags_used": false - }, - "candidate": { - "base_commit": "d1105bbb3d4a0afa33b3a4ac11b821235038ed0e", - "changed_files": [ - "cmd/config-current.go", - "cmd/iam.go", - "cmd/utils.go" - ], - "patch_sha256": "2a6095aa641805f3f6a79ec09f2c11fed2dc0ac38ae344a00a29734e9fb9aff9", - "product_worktree_modified": false, - "description": "Use Go default CurvePreferences only in the Server OpenID transport passed by IAM initialization and config validation." - }, - "builds": [ - { - "binary": "old-go126", - "commit": "d88f46ccee345a9c2fabe2d221d9a9e56bc11aec", - "go": "go1.26.5", - "sha256": "a75631117667c7533cc512ad7a71ee697d4e0149dd354cb20e3be64277ee5719", - "go_mod_sha256": "451822c94ee2431b9dcb019afadbb5366ab881317afd55cd95432707c8fc68fd", - "go_sum_sha256": "d5a4236fee117ccfbc63564c6d97b68e0e9d2290842b331d37135ae840cb47fa", - "dependencies": [ - "dep\taead.dev/mem\tv0.2.0\th1:ufgkESS9+lHV/GUjxgc2ObF43FLZGSemh+W+y27QFMI=", - "dep\taead.dev/minisign\tv0.3.0\th1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=", - "dep\taead.dev/mtls\tv0.3.0\th1:a+C0t15Y9SRX6qP1EqmQFZ4ZSMm88TPvNDymasu4ahQ=", - "dep\tcel.dev/expr\tv0.25.1\th1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=", - "dep\tcloud.google.com/go\tv0.123.0\th1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=", - "dep\tcloud.google.com/go/auth\tv0.20.0\th1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=", - "dep\tcloud.google.com/go/auth/oauth2adapt\tv0.2.8\th1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=", - "dep\tcloud.google.com/go/compute/metadata\tv0.9.0\th1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=", - "dep\tcloud.google.com/go/iam\tv1.5.3\th1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=", - "dep\tcloud.google.com/go/monitoring\tv1.24.3\th1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=", - "dep\tcloud.google.com/go/storage\tv1.61.3\th1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=", - "dep\tfilippo.io/edwards25519\tv1.2.0\th1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore\tv1.21.1\th1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity\tv1.13.1\th1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/internal\tv1.12.0\th1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob\tv1.6.4\th1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM=", - "dep\tgithub.com/Azure/go-ntlmssp\tv0.1.1\th1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=", - "dep\tgithub.com/AzureAD/microsoft-authentication-library-for-go\tv1.7.0\th1:4iB+IesclUXdP0ICgAabvq2FYLXrJWKx1fJQ+GxSo3Y=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp\tv1.32.0\th1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric\tv0.55.0\th1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping\tv0.55.0\th1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=", - "dep\tgithub.com/IBM/sarama\tv1.45.1\th1:nY30XqYpqyXOXSNoe2XCgjj9jklGM1Ye94ierUb1jQ0=", - "dep\tgithub.com/VividCortex/ewma\tv1.2.0\th1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=", - "dep\tgithub.com/acarl005/stripansi\tv0.0.0-20180116102854-5a71ef0e047d\th1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=", - "dep\tgithub.com/alecthomas/participle\tv0.7.1\th1:2bN7reTw//5f0cugJcTOnY/NYZcWQOaajW+BwZB5xWs=", - "dep\tgithub.com/apache/thrift\tv0.24.0\th1:zy31L1a49QTNB2bG1BBfMXol3yJrTH975G3pPubQVLQ=", - "dep\tgithub.com/aymanbagabas/go-osc52/v2\tv2.0.1\th1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=", - "dep\tgithub.com/beevik/ntp\tv1.5.0\th1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=", - "dep\tgithub.com/beorn7/perks\tv1.0.1\th1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=", - "dep\tgithub.com/buger/jsonparser\tv1.1.2\th1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=", - "dep\tgithub.com/cespare/xxhash/v2\tv2.3.0\th1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=", - "dep\tgithub.com/charmbracelet/bubbles\tv1.0.0\th1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=", - "dep\tgithub.com/charmbracelet/bubbletea\tv1.3.10\th1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=", - "dep\tgithub.com/charmbracelet/colorprofile\tv0.4.3\th1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=", - "dep\tgithub.com/charmbracelet/harmonica\tv0.2.0\th1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=", - "dep\tgithub.com/charmbracelet/lipgloss\tv1.1.0\th1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=", - "dep\tgithub.com/charmbracelet/x/ansi\tv0.11.6\th1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=", - "dep\tgithub.com/charmbracelet/x/cellbuf\tv0.0.15\th1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=", - "dep\tgithub.com/charmbracelet/x/term\tv0.2.2\th1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=", - "dep\tgithub.com/cheggaaa/pb\tv1.0.29\th1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo=", - "dep\tgithub.com/clipperhouse/displaywidth\tv0.11.0\th1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=", - "dep\tgithub.com/clipperhouse/uax29/v2\tv2.7.0\th1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=", - "dep\tgithub.com/cncf/xds/go\tv0.0.0-20260202195803-dba9d589def2\th1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=", - "dep\tgithub.com/coreos/go-oidc/v3\tv3.17.0\th1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=", - "dep\tgithub.com/coreos/go-semver\tv0.3.1\th1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=", - "dep\tgithub.com/coreos/go-systemd/v22\tv22.7.0", - "=>\tgithub.com/coreos/go-systemd/v22\tv22.6.0\th1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=", - "dep\tgithub.com/cosnicolaou/pbzip2\tv1.0.6\th1:FYF6b2j4X4q3hZezd2AoUN/emLCtH/MbDGwJjiOacak=", - "dep\tgithub.com/davecgh/go-spew\tv1.1.2-0.20180830191138-d8f796af33cc\th1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=", - "dep\tgithub.com/dchest/siphash\tv1.2.3\th1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=", - "dep\tgithub.com/docker/go-units\tv0.5.0\th1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=", - "dep\tgithub.com/dustin/go-humanize\tv1.0.1\th1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=", - "dep\tgithub.com/eapache/go-resiliency\tv1.7.0\th1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=", - "dep\tgithub.com/eapache/go-xerial-snappy\tv0.0.0-20230731223053-c322873962e3\th1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=", - "dep\tgithub.com/eapache/queue\tv1.1.0\th1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=", - "dep\tgithub.com/eclipse/paho.mqtt.golang\tv1.5.1\th1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=", - "dep\tgithub.com/elastic/go-elasticsearch/v7\tv7.17.10\th1:TCQ8i4PmIJuBunvBS6bwT2ybzVFxxUhhltAs3Gyu1yo=", - "dep\tgithub.com/envoyproxy/go-control-plane/envoy\tv1.37.0\th1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=", - "dep\tgithub.com/envoyproxy/protoc-gen-validate\tv1.3.3\th1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=", - "dep\tgithub.com/fatih/color\tv1.19.0\th1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=", - "dep\tgithub.com/fatih/structs\tv1.1.0\th1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=", - "dep\tgithub.com/felixge/fgprof\tv0.9.5\th1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=", - "dep\tgithub.com/felixge/httpsnoop\tv1.0.4\th1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=", - "dep\tgithub.com/fraugster/parquet-go\tv0.12.0\th1:1slnC5y2VWEOUSlzbeXatM0BvSWcLUDsR/EcZsXXCZc=", - "dep\tgithub.com/go-asn1-ber/asn1-ber\tv1.5.8\th1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=", - "dep\tgithub.com/go-ini/ini\tv1.67.0\th1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=", - "dep\tgithub.com/go-jose/go-jose/v4\tv4.1.4\th1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=", - "dep\tgithub.com/go-ldap/ldap/v3\tv3.4.14\th1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=", - "dep\tgithub.com/go-logr/logr\tv1.4.3\th1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=", - "dep\tgithub.com/go-logr/stdr\tv1.2.2\th1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=", - "dep\tgithub.com/go-openapi/analysis\tv0.25.0\th1:EnjAq1yO8wEO9HbPmY8vLPEIkdZuuFhCAKBPvCB7bCs=", - "dep\tgithub.com/go-openapi/errors\tv0.22.7\th1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA=", - "dep\tgithub.com/go-openapi/jsonpointer\tv0.23.1\th1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=", - "dep\tgithub.com/go-openapi/jsonreference\tv0.21.5\th1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=", - "dep\tgithub.com/go-openapi/loads\tv0.23.3\th1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ=", - "dep\tgithub.com/go-openapi/runtime\tv0.29.3\th1:h5twGaEqxtQg40ePiYm9vFFH1q06Czd7Ot6ufdK0w/Y=", - "dep\tgithub.com/go-openapi/spec\tv0.22.4\th1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=", - "dep\tgithub.com/go-openapi/strfmt\tv0.26.2\th1:ysjheCh4i1rmFEo2LanhELDNucNzfWTZhUDKgWWPaFM=", - "dep\tgithub.com/go-openapi/swag\tv0.25.5\th1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=", - "dep\tgithub.com/go-openapi/swag/cmdutils\tv0.25.5\th1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=", - "dep\tgithub.com/go-openapi/swag/conv\tv0.28.0\th1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8=", - "dep\tgithub.com/go-openapi/swag/fileutils\tv0.25.5\th1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=", - "dep\tgithub.com/go-openapi/swag/jsonname\tv0.26.0\th1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w=", - "dep\tgithub.com/go-openapi/swag/jsonutils\tv0.25.5\th1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=", - "dep\tgithub.com/go-openapi/swag/loading\tv0.25.5\th1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=", - "dep\tgithub.com/go-openapi/swag/mangling\tv0.25.5\th1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=", - "dep\tgithub.com/go-openapi/swag/netutils\tv0.25.5\th1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU=", - "dep\tgithub.com/go-openapi/swag/stringutils\tv0.25.5\th1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=", - "dep\tgithub.com/go-openapi/swag/typeutils\tv0.28.0\th1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY=", - "dep\tgithub.com/go-openapi/swag/yamlutils\tv0.25.5\th1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=", - "dep\tgithub.com/go-openapi/validate\tv0.25.2\th1:12NsfLAwGegqbGWr2CnvT65X/Q2USJipmJ9b7xDJZz0=", - "dep\tgithub.com/go-sql-driver/mysql\tv1.9.3\th1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=", - "dep\tgithub.com/go-viper/mapstructure/v2\tv2.5.0\th1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=", - "dep\tgithub.com/gobwas/httphead\tv0.1.0\th1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=", - "dep\tgithub.com/gobwas/pool\tv0.2.1\th1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=", - "dep\tgithub.com/gobwas/ws\tv1.4.0\th1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=", - "dep\tgithub.com/gogo/protobuf\tv1.3.2\th1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=", - "dep\tgithub.com/golang-jwt/jwt/v4\tv4.5.2\th1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=", - "dep\tgithub.com/golang-jwt/jwt/v5\tv5.3.1\th1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=", - "dep\tgithub.com/golang/protobuf\tv1.5.4\th1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=", - "dep\tgithub.com/golang/snappy\tv1.0.0\th1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=", - "dep\tgithub.com/gomodule/redigo\tv1.9.3\th1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=", - "dep\tgithub.com/google/pprof\tv0.0.0-20260507013755-92041b743c96\th1:YDDnaZ9afWajDboPMt9Vikqca/yWAX7KAxVzb4lJU1M=", - "dep\tgithub.com/google/s2a-go\tv0.1.9\th1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=", - "dep\tgithub.com/google/shlex\tv0.0.0-20191202100458-e7afc7fbc510\th1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=", - "dep\tgithub.com/google/uuid\tv1.6.0\th1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=", - "dep\tgithub.com/googleapis/enterprise-certificate-proxy\tv0.3.15\th1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=", - "dep\tgithub.com/googleapis/gax-go/v2\tv2.22.0\th1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=", - "dep\tgithub.com/gorilla/websocket\tv1.5.4-0.20250319132907-e064f32e3674\th1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=", - "dep\tgithub.com/grafana/regexp\tv0.0.0-20250905093917-f7b3be9d1853\th1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM=", - "dep\tgithub.com/grpc-ecosystem/grpc-gateway/v2\tv2.29.0\th1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=", - "dep\tgithub.com/hashicorp/errwrap\tv1.1.0\th1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=", - "dep\tgithub.com/hashicorp/go-multierror\tv1.1.1\th1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=", - "dep\tgithub.com/hashicorp/go-uuid\tv1.0.3\th1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=", - "dep\tgithub.com/inconshreveable/mousetrap\tv1.1.0\th1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=", - "dep\tgithub.com/jcmturner/aescts/v2\tv2.0.0\th1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=", - "dep\tgithub.com/jcmturner/dnsutils/v2\tv2.0.0\th1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=", - "dep\tgithub.com/jcmturner/gofork\tv1.7.6\th1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=", - "dep\tgithub.com/jcmturner/gokrb5/v8\tv8.4.4\th1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=", - "dep\tgithub.com/jcmturner/rpc/v2\tv2.0.3\th1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=", - "dep\tgithub.com/jedib0t/go-pretty/v6\tv6.7.8\th1:BVYrDy5DPBA3Qn9ICT+PokP9cvCv1KaHv2i+Hc8sr5o=", - "dep\tgithub.com/jessevdk/go-flags\tv1.6.1\th1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=", - "dep\tgithub.com/json-iterator/go\tv1.1.12\th1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=", - "dep\tgithub.com/juju/ratelimit\tv1.0.2\th1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=", - "dep\tgithub.com/klauspost/compress\tv1.18.7\th1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=", - "dep\tgithub.com/klauspost/cpuid/v2\tv2.3.0\th1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=", - "dep\tgithub.com/klauspost/crc32\tv1.3.0\th1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=", - "dep\tgithub.com/klauspost/filepathx\tv1.1.1\th1:201zvAsL1PhZvmXTP+QLer3AavWrO3U1NILWpniHK4w=", - "dep\tgithub.com/klauspost/pgzip\tv1.2.6\th1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=", - "dep\tgithub.com/klauspost/readahead\tv1.4.0\th1:w4hQ3BpdLjBnRQkZyNi+nwdHU7eGP9buTexWK9lU7gY=", - "dep\tgithub.com/klauspost/reedsolomon\tv1.13.3\th1:01GwnO2xoCSaM0ShP4qwl+FsHg3csFShC6Tu/RS1ji0=", - "dep\tgithub.com/kr/fs\tv0.1.0\th1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=", - "dep\tgithub.com/kylelemons/godebug\tv1.1.0\th1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=", - "dep\tgithub.com/lestrrat-go/blackmagic\tv1.0.4\th1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=", - "dep\tgithub.com/lestrrat-go/dsig\tv1.0.0\th1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=", - "dep\tgithub.com/lestrrat-go/httpcc\tv1.0.1\th1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=", - "dep\tgithub.com/lestrrat-go/httprc/v3\tv3.0.1\th1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=", - "dep\tgithub.com/lestrrat-go/jwx/v3\tv3.0.12\th1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJIRjg=", - "dep\tgithub.com/lestrrat-go/option\tv1.0.1\th1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=", - "dep\tgithub.com/lestrrat-go/option/v2\tv2.0.0\th1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=", - "dep\tgithub.com/lib/pq\tv1.10.9\th1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=", - "dep\tgithub.com/lithammer/shortuuid/v4\tv4.2.0\th1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=", - "dep\tgithub.com/lucasb-eyer/go-colorful\tv1.3.0\th1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=", - "dep\tgithub.com/mattn/go-colorable\tv0.1.15\th1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=", - "dep\tgithub.com/mattn/go-ieproxy\tv0.0.12\th1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M=", - "dep\tgithub.com/mattn/go-isatty\tv0.0.24\th1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=", - "dep\tgithub.com/mattn/go-runewidth\tv0.0.21\th1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=", - "dep\tgithub.com/matttproud/golang_protobuf_extensions\tv1.0.4\th1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=", - "dep\tgithub.com/miekg/dns\tv1.1.72\th1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=", - "dep\tgithub.com/minio/cli\tv1.24.2\th1:J+fCUh9mhPLjN3Lj/YhklXvxj8mnyE/D6FpFduXJ2jg=", - "dep\tgithub.com/minio/colorjson\tv1.0.8\th1:AS6gEQ1dTRYHmC4xuoodPDRILHP/9Wz5wYUGDQfPLpg=", - "dep\tgithub.com/minio/console\tv1.7.6", - "=>\tgithub.com/pgsty/silo-console\tv0.0.0-20260804042150-b952a1202869\th1:HLfc2ZdAycnI/bLl+TdsuckyzSWrwQarK38pS14yH/Q=", - "dep\tgithub.com/minio/crc64nvme\tv1.1.1\th1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=", - "dep\tgithub.com/minio/csvparser\tv1.0.0\th1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=", - "dep\tgithub.com/minio/dnscache\tv0.1.1\th1:AMYLqomzskpORiUA1ciN9k7bZT1oB3YZN4cEIi88W5o=", - "dep\tgithub.com/minio/dperf\tv0.7.1\th1:eBwtaBBjuANwgUy1waWoS+wP+0i5fkXJOdGU2RXuDxo=", - "dep\tgithub.com/minio/filepath\tv1.0.0\th1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY=", - "dep\tgithub.com/minio/highwayhash\tv1.0.4\th1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4=", - "dep\tgithub.com/minio/kms-go/kes\tv0.3.1\th1:K3sPFAvFbJx33XlCTUBnQo8JRmSZyDvT6T2/MQ2iC3A=", - "dep\tgithub.com/minio/kms-go/kms\tv0.6.0\th1:oGdGUyjfCZwRIi7em0aj4wk+oOm7+4a0lzSZny7ZIDU=", - "dep\tgithub.com/minio/madmin-go/v3\tv3.0.110\th1:FIYekj7YPc430ffpXFWiUtyut3qBt/unIAcDzJn9H5M=", - "dep\tgithub.com/minio/mc\tv0.0.0-20251106162529-77f82e18b540", - "=>\tgithub.com/pgsty/mc\tv0.0.0-20260801042411-ad10a2a10b76\th1:UIlUuz0LQKw4QlAljhv7nPDDFC1+n+e0iED7rWZrgZ8=", - "dep\tgithub.com/minio/md5-simd\tv1.1.2\th1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=", - "dep\tgithub.com/minio/minio-go/v7\tv7.0.99\th1:2vH/byrwUkIpFQFOilvTfaUpvAX3fEFhEzO+DR3DlCE=", - "dep\tgithub.com/minio/mux\tv1.9.2\th1:dQchne49BUBgOlxIHjx5wVe1gl5VXF2sxd4YCXkikTw=", - "dep\tgithub.com/minio/pkg/v3\tv3.6.1", - "=>\tgithub.com/pgsty/silo-pkg/v3\tv3.11.0\th1:wjN5d+tWD8Twq+e7k/KBBVhnWXC8xTIlfTcnGIKkmjc=", - "dep\tgithub.com/minio/selfupdate\tv0.6.0\th1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=", - "dep\tgithub.com/minio/simdjson-go\tv0.4.5\th1:r4IQwjRGmWCQ2VeMc7fGiilu1z5du0gJ/I/FsKwgo5A=", - "dep\tgithub.com/minio/sio\tv0.4.3\th1:JqyID1XM86KwBZox5RAdLD4MLPIDoCY2cke2CXCJCkg=", - "dep\tgithub.com/minio/websocket\tv1.6.0\th1:CPvnQvNvlVaQmvw5gtJNyYQhg4+xRmrPNhBbv8BdpAE=", - "dep\tgithub.com/minio/xxml\tv0.0.3\th1:ZIpPQpfyG5uZQnqqC0LZuWtPk/WT8G/qkxvO6jb7zMU=", - "dep\tgithub.com/minio/zipindex\tv0.5.0\th1:QydEWJW+uAFMd5xmQa580bm7JtC5krpuAtARXIQr72U=", - "dep\tgithub.com/mitchellh/go-homedir\tv1.1.0\th1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=", - "dep\tgithub.com/modern-go/concurrent\tv0.0.0-20180306012644-bacd9c7ef1dd\th1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=", - "dep\tgithub.com/modern-go/reflect2\tv1.0.3-0.20250322232337-35a7c28c31ee\th1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=", - "dep\tgithub.com/muesli/ansi\tv0.0.0-20230316100256-276c6243b2f6\th1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=", - "dep\tgithub.com/muesli/cancelreader\tv0.2.2\th1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=", - "dep\tgithub.com/muesli/reflow\tv0.3.0\th1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=", - "dep\tgithub.com/muesli/termenv\tv0.16.0\th1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=", - "dep\tgithub.com/munnerz/goautoneg\tv0.0.0-20191010083416-a7dc8b61c822\th1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=", - "dep\tgithub.com/nats-io/nats.go\tv1.49.0\th1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=", - "dep\tgithub.com/nats-io/nkeys\tv0.4.15\th1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=", - "dep\tgithub.com/nats-io/nuid\tv1.0.1\th1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=", - "dep\tgithub.com/nats-io/stan.go\tv0.10.4\th1:19GS/eD1SeQJaVkeM9EkvEYattnvnWrZ3wkSWSw4uXw=", - "dep\tgithub.com/ncw/directio\tv1.0.5\th1:JSUBhdjEvVaJvOoyPAbcW0fnd0tvRXD76wEfZ1KcQz4=", - "dep\tgithub.com/nsqio/go-nsq\tv1.1.0\th1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=", - "dep\tgithub.com/oklog/ulid/v2\tv2.1.1\th1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=", - "dep\tgithub.com/olekukonko/tablewriter\tv0.0.5\th1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=", - "dep\tgithub.com/philhofer/fwd\tv1.2.0\th1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=", - "dep\tgithub.com/pierrec/lz4/v4\tv4.1.26\th1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=", - "dep\tgithub.com/pkg/browser\tv0.0.0-20240102092130-5ac0b6a4141c\th1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=", - "dep\tgithub.com/pkg/errors\tv0.9.1\th1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=", - "dep\tgithub.com/pkg/sftp\tv1.13.10\th1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=", - "dep\tgithub.com/pkg/xattr\tv0.4.12\th1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM=", - "dep\tgithub.com/posener/complete\tv1.2.3\th1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=", - "dep\tgithub.com/prometheus/client_golang\tv1.23.2\th1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=", - "dep\tgithub.com/prometheus/client_model\tv0.6.2\th1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=", - "dep\tgithub.com/prometheus/common\tv0.67.5\th1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=", - "dep\tgithub.com/prometheus/procfs\tv0.20.1\th1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=", - "dep\tgithub.com/prometheus/prom2json\tv1.5.0\th1:WIcAOjLE1x476W3dUlmTL6E/e98CgVGuwwYusl6MPP8=", - "dep\tgithub.com/prometheus/prometheus\tv0.311.3\th1:3IrVxQv6v5i/ZCGi6OrYeBhtCwaPTn6Z3DYruXoYm3M=", - "dep\tgithub.com/puzpuzpuz/xsync/v3\tv3.5.1\th1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=", - "dep\tgithub.com/rabbitmq/amqp091-go\tv1.10.0\th1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=", - "dep\tgithub.com/rcrowley/go-metrics\tv0.0.0-20250401214520-65e299d6c5c9\th1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=", - "dep\tgithub.com/rivo/uniseg\tv0.4.7\th1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=", - "dep\tgithub.com/rjeczalik/notify\tv0.9.3\th1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=", - "dep\tgithub.com/rs/cors\tv1.11.1\th1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=", - "dep\tgithub.com/rs/xid\tv1.6.0\th1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=", - "dep\tgithub.com/safchain/ethtool\tv0.7.0\th1:rlJzfDetsVvT61uz8x1YIcFn12akMfuPulHtZjtb7Is=", - "dep\tgithub.com/secure-io/sio-go\tv0.3.1\th1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=", - "dep\tgithub.com/shirou/gopsutil/v3\tv3.24.5\th1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=", - "dep\tgithub.com/spiffe/go-spiffe/v2\tv2.6.0\th1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=", - "dep\tgithub.com/tidwall/gjson\tv1.18.0\th1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=", - "dep\tgithub.com/tidwall/match\tv1.2.0\th1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=", - "dep\tgithub.com/tidwall/pretty\tv1.2.1\th1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=", - "dep\tgithub.com/tinylib/msgp\tv1.6.4\th1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=", - "dep\tgithub.com/tklauser/go-sysconf\tv0.3.16\th1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=", - "dep\tgithub.com/tklauser/numcpus\tv0.11.0\th1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=", - "dep\tgithub.com/unrolled/secure\tv1.17.0\th1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=", - "dep\tgithub.com/valyala/bytebufferpool\tv1.0.0\th1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=", - "dep\tgithub.com/valyala/fastjson\tv1.6.4\th1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=", - "dep\tgithub.com/vbauerster/mpb/v8\tv8.12.0\th1:+gneY3ifzc88tKDzOtfG8k8gfngCx615S2ZmFM4liWg=", - "dep\tgithub.com/xdg/scram\tv1.0.5\th1:TuS0RFmt5Is5qm9Tm2SoD89OPqe4IRiFtyFY4iwWXsw=", - "dep\tgithub.com/xdg/stringprep\tv1.0.3\th1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4=", - "dep\tgithub.com/xo/terminfo\tv0.0.0-20220910002029-abceb7e1c41e\th1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=", - "dep\tgithub.com/zeebo/xxh3\tv1.1.0\th1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=", - "dep\tgo.etcd.io/etcd/api/v3\tv3.6.9\th1:UA7iKfEW1AzgihcBSGXci2kDGQiokSq41F9HMCI/RTI=", - "dep\tgo.etcd.io/etcd/client/pkg/v3\tv3.6.9\th1:T8nuk8Lz64C+Hzb0coBFLMSlVSQZBpAtFk46swdM1DA=", - "dep\tgo.etcd.io/etcd/client/v3\tv3.6.9\th1:3X555hQXmhRr27O37wls53g68CpUiPOiHXrZfz2Al+o=", - "dep\tgo.opentelemetry.io/auto/sdk\tv1.2.1\th1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=", - "dep\tgo.opentelemetry.io/contrib/detectors/gcp\tv1.43.0\th1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=", - "dep\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\tv0.67.0\th1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=", - "dep\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\tv0.69.0\th1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=", - "dep\tgo.opentelemetry.io/otel\tv1.44.0\th1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=", - "dep\tgo.opentelemetry.io/otel/metric\tv1.44.0\th1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=", - "dep\tgo.opentelemetry.io/otel/sdk\tv1.44.0\th1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=", - "dep\tgo.opentelemetry.io/otel/sdk/metric\tv1.44.0\th1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=", - "dep\tgo.opentelemetry.io/otel/trace\tv1.44.0\th1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=", - "dep\tgo.uber.org/atomic\tv1.11.0\th1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=", - "dep\tgo.uber.org/multierr\tv1.11.0\th1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=", - "dep\tgo.uber.org/zap\tv1.28.0\th1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=", - "dep\tgo.yaml.in/yaml/v2\tv2.4.4\th1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=", - "dep\tgo.yaml.in/yaml/v3\tv3.0.5\th1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=", - "dep\tgoftp.io/server/v2\tv2.0.2\th1:tkZpqyXys+vC15W5yGMi8Kzmbv1QSgeKr8qJXBnJbm8=", - "dep\tgolang.org/x/crypto\tv0.54.0\th1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=", - "dep\tgolang.org/x/net\tv0.57.0\th1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=", - "dep\tgolang.org/x/oauth2\tv0.36.0\th1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=", - "dep\tgolang.org/x/sync\tv0.22.0\th1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=", - "dep\tgolang.org/x/sys\tv0.47.0\th1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=", - "dep\tgolang.org/x/term\tv0.45.0\th1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=", - "dep\tgolang.org/x/text\tv0.40.0\th1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=", - "dep\tgolang.org/x/time\tv0.15.0\th1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=", - "dep\tgoogle.golang.org/api\tv0.278.0\th1:W7jiRvRi53VYFfZ/HoZjQBtJk7gOFbHD8ot1RzVZU6E=", - "dep\tgoogle.golang.org/genproto\tv0.0.0-20260319201613-d00831a3d3e7\th1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=", - "dep\tgoogle.golang.org/genproto/googleapis/api\tv0.0.0-20260526163538-3dc84a4a5aaa\th1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=", - "dep\tgoogle.golang.org/genproto/googleapis/rpc\tv0.0.0-20260526163538-3dc84a4a5aaa\th1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=", - "dep\tgoogle.golang.org/grpc\tv1.82.1\th1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=", - "dep\tgoogle.golang.org/protobuf\tv1.36.11\th1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=", - "dep\tgopkg.in/yaml.v2\tv2.4.0\th1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=" - ], - "build_settings": [ - "build\t-buildmode=exe", - "build\t-compiler=gc", - "build\t-tags=kqueue", - "build\tCGO_ENABLED=0", - "build\tGOARCH=arm64", - "build\tGOOS=linux", - "build\tGOARM64=v8.0" - ], - "patched": false, - "dependency_graph_sha256": "692c41d253e666fea024ec976b9d2728552bb58f75cdafe05ddb47dc15d4bad8" - }, - { - "binary": "old-go127", - "commit": "d88f46ccee345a9c2fabe2d221d9a9e56bc11aec", - "go": "go1.27.1", - "sha256": "b6bac7e3299575d1508257286edf5f081ac5aebb390cfc0fc49af695cc56f0c1", - "go_mod_sha256": "451822c94ee2431b9dcb019afadbb5366ab881317afd55cd95432707c8fc68fd", - "go_sum_sha256": "d5a4236fee117ccfbc63564c6d97b68e0e9d2290842b331d37135ae840cb47fa", - "dependencies": [ - "dep\taead.dev/mem\tv0.2.0\th1:ufgkESS9+lHV/GUjxgc2ObF43FLZGSemh+W+y27QFMI=", - "dep\taead.dev/minisign\tv0.3.0\th1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=", - "dep\taead.dev/mtls\tv0.3.0\th1:a+C0t15Y9SRX6qP1EqmQFZ4ZSMm88TPvNDymasu4ahQ=", - "dep\tcel.dev/expr\tv0.25.1\th1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=", - "dep\tcloud.google.com/go\tv0.123.0\th1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=", - "dep\tcloud.google.com/go/auth\tv0.20.0\th1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=", - "dep\tcloud.google.com/go/auth/oauth2adapt\tv0.2.8\th1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=", - "dep\tcloud.google.com/go/compute/metadata\tv0.9.0\th1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=", - "dep\tcloud.google.com/go/iam\tv1.5.3\th1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=", - "dep\tcloud.google.com/go/monitoring\tv1.24.3\th1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=", - "dep\tcloud.google.com/go/storage\tv1.61.3\th1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=", - "dep\tfilippo.io/edwards25519\tv1.2.0\th1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore\tv1.21.1\th1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity\tv1.13.1\th1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/internal\tv1.12.0\th1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob\tv1.6.4\th1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM=", - "dep\tgithub.com/Azure/go-ntlmssp\tv0.1.1\th1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=", - "dep\tgithub.com/AzureAD/microsoft-authentication-library-for-go\tv1.7.0\th1:4iB+IesclUXdP0ICgAabvq2FYLXrJWKx1fJQ+GxSo3Y=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp\tv1.32.0\th1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric\tv0.55.0\th1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping\tv0.55.0\th1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=", - "dep\tgithub.com/IBM/sarama\tv1.45.1\th1:nY30XqYpqyXOXSNoe2XCgjj9jklGM1Ye94ierUb1jQ0=", - "dep\tgithub.com/VividCortex/ewma\tv1.2.0\th1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=", - "dep\tgithub.com/acarl005/stripansi\tv0.0.0-20180116102854-5a71ef0e047d\th1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=", - "dep\tgithub.com/alecthomas/participle\tv0.7.1\th1:2bN7reTw//5f0cugJcTOnY/NYZcWQOaajW+BwZB5xWs=", - "dep\tgithub.com/apache/thrift\tv0.24.0\th1:zy31L1a49QTNB2bG1BBfMXol3yJrTH975G3pPubQVLQ=", - "dep\tgithub.com/aymanbagabas/go-osc52/v2\tv2.0.1\th1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=", - "dep\tgithub.com/beevik/ntp\tv1.5.0\th1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=", - "dep\tgithub.com/beorn7/perks\tv1.0.1\th1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=", - "dep\tgithub.com/buger/jsonparser\tv1.1.2\th1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=", - "dep\tgithub.com/cespare/xxhash/v2\tv2.3.0\th1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=", - "dep\tgithub.com/charmbracelet/bubbles\tv1.0.0\th1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=", - "dep\tgithub.com/charmbracelet/bubbletea\tv1.3.10\th1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=", - "dep\tgithub.com/charmbracelet/colorprofile\tv0.4.3\th1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=", - "dep\tgithub.com/charmbracelet/harmonica\tv0.2.0\th1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=", - "dep\tgithub.com/charmbracelet/lipgloss\tv1.1.0\th1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=", - "dep\tgithub.com/charmbracelet/x/ansi\tv0.11.6\th1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=", - "dep\tgithub.com/charmbracelet/x/cellbuf\tv0.0.15\th1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=", - "dep\tgithub.com/charmbracelet/x/term\tv0.2.2\th1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=", - "dep\tgithub.com/cheggaaa/pb\tv1.0.29\th1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo=", - "dep\tgithub.com/clipperhouse/displaywidth\tv0.11.0\th1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=", - "dep\tgithub.com/clipperhouse/uax29/v2\tv2.7.0\th1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=", - "dep\tgithub.com/cncf/xds/go\tv0.0.0-20260202195803-dba9d589def2\th1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=", - "dep\tgithub.com/coreos/go-oidc/v3\tv3.17.0\th1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=", - "dep\tgithub.com/coreos/go-semver\tv0.3.1\th1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=", - "dep\tgithub.com/coreos/go-systemd/v22\tv22.7.0", - "=>\tgithub.com/coreos/go-systemd/v22\tv22.6.0\th1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=", - "dep\tgithub.com/cosnicolaou/pbzip2\tv1.0.6\th1:FYF6b2j4X4q3hZezd2AoUN/emLCtH/MbDGwJjiOacak=", - "dep\tgithub.com/davecgh/go-spew\tv1.1.2-0.20180830191138-d8f796af33cc\th1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=", - "dep\tgithub.com/dchest/siphash\tv1.2.3\th1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=", - "dep\tgithub.com/docker/go-units\tv0.5.0\th1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=", - "dep\tgithub.com/dustin/go-humanize\tv1.0.1\th1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=", - "dep\tgithub.com/eapache/go-resiliency\tv1.7.0\th1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=", - "dep\tgithub.com/eapache/go-xerial-snappy\tv0.0.0-20230731223053-c322873962e3\th1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=", - "dep\tgithub.com/eapache/queue\tv1.1.0\th1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=", - "dep\tgithub.com/eclipse/paho.mqtt.golang\tv1.5.1\th1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=", - "dep\tgithub.com/elastic/go-elasticsearch/v7\tv7.17.10\th1:TCQ8i4PmIJuBunvBS6bwT2ybzVFxxUhhltAs3Gyu1yo=", - "dep\tgithub.com/envoyproxy/go-control-plane/envoy\tv1.37.0\th1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=", - "dep\tgithub.com/envoyproxy/protoc-gen-validate\tv1.3.3\th1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=", - "dep\tgithub.com/fatih/color\tv1.19.0\th1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=", - "dep\tgithub.com/fatih/structs\tv1.1.0\th1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=", - "dep\tgithub.com/felixge/fgprof\tv0.9.5\th1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=", - "dep\tgithub.com/felixge/httpsnoop\tv1.0.4\th1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=", - "dep\tgithub.com/fraugster/parquet-go\tv0.12.0\th1:1slnC5y2VWEOUSlzbeXatM0BvSWcLUDsR/EcZsXXCZc=", - "dep\tgithub.com/go-asn1-ber/asn1-ber\tv1.5.8\th1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=", - "dep\tgithub.com/go-ini/ini\tv1.67.0\th1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=", - "dep\tgithub.com/go-jose/go-jose/v4\tv4.1.4\th1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=", - "dep\tgithub.com/go-ldap/ldap/v3\tv3.4.14\th1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=", - "dep\tgithub.com/go-logr/logr\tv1.4.3\th1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=", - "dep\tgithub.com/go-logr/stdr\tv1.2.2\th1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=", - "dep\tgithub.com/go-openapi/analysis\tv0.25.0\th1:EnjAq1yO8wEO9HbPmY8vLPEIkdZuuFhCAKBPvCB7bCs=", - "dep\tgithub.com/go-openapi/errors\tv0.22.7\th1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA=", - "dep\tgithub.com/go-openapi/jsonpointer\tv0.23.1\th1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=", - "dep\tgithub.com/go-openapi/jsonreference\tv0.21.5\th1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=", - "dep\tgithub.com/go-openapi/loads\tv0.23.3\th1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ=", - "dep\tgithub.com/go-openapi/runtime\tv0.29.3\th1:h5twGaEqxtQg40ePiYm9vFFH1q06Czd7Ot6ufdK0w/Y=", - "dep\tgithub.com/go-openapi/spec\tv0.22.4\th1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=", - "dep\tgithub.com/go-openapi/strfmt\tv0.26.2\th1:ysjheCh4i1rmFEo2LanhELDNucNzfWTZhUDKgWWPaFM=", - "dep\tgithub.com/go-openapi/swag\tv0.25.5\th1:pNkwbUEeGwMtcgxDr+2GBPAk4kT+kJ+AaB+TMKAg+TU=", - "dep\tgithub.com/go-openapi/swag/cmdutils\tv0.25.5\th1:yh5hHrpgsw4NwM9KAEtaDTXILYzdXh/I8Whhx9hKj7c=", - "dep\tgithub.com/go-openapi/swag/conv\tv0.28.0\th1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8=", - "dep\tgithub.com/go-openapi/swag/fileutils\tv0.25.5\th1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk=", - "dep\tgithub.com/go-openapi/swag/jsonname\tv0.26.0\th1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w=", - "dep\tgithub.com/go-openapi/swag/jsonutils\tv0.25.5\th1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=", - "dep\tgithub.com/go-openapi/swag/loading\tv0.25.5\th1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=", - "dep\tgithub.com/go-openapi/swag/mangling\tv0.25.5\th1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw=", - "dep\tgithub.com/go-openapi/swag/netutils\tv0.25.5\th1:LZq2Xc2QI8+7838elRAaPCeqJnHODfSyOa7ZGfxDKlU=", - "dep\tgithub.com/go-openapi/swag/stringutils\tv0.25.5\th1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=", - "dep\tgithub.com/go-openapi/swag/typeutils\tv0.28.0\th1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY=", - "dep\tgithub.com/go-openapi/swag/yamlutils\tv0.25.5\th1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=", - "dep\tgithub.com/go-openapi/validate\tv0.25.2\th1:12NsfLAwGegqbGWr2CnvT65X/Q2USJipmJ9b7xDJZz0=", - "dep\tgithub.com/go-sql-driver/mysql\tv1.9.3\th1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=", - "dep\tgithub.com/go-viper/mapstructure/v2\tv2.5.0\th1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=", - "dep\tgithub.com/gobwas/httphead\tv0.1.0\th1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=", - "dep\tgithub.com/gobwas/pool\tv0.2.1\th1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=", - "dep\tgithub.com/gobwas/ws\tv1.4.0\th1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=", - "dep\tgithub.com/gogo/protobuf\tv1.3.2\th1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=", - "dep\tgithub.com/golang-jwt/jwt/v4\tv4.5.2\th1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=", - "dep\tgithub.com/golang-jwt/jwt/v5\tv5.3.1\th1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=", - "dep\tgithub.com/golang/protobuf\tv1.5.4\th1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=", - "dep\tgithub.com/golang/snappy\tv1.0.0\th1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=", - "dep\tgithub.com/gomodule/redigo\tv1.9.3\th1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=", - "dep\tgithub.com/google/pprof\tv0.0.0-20260507013755-92041b743c96\th1:YDDnaZ9afWajDboPMt9Vikqca/yWAX7KAxVzb4lJU1M=", - "dep\tgithub.com/google/s2a-go\tv0.1.9\th1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=", - "dep\tgithub.com/google/shlex\tv0.0.0-20191202100458-e7afc7fbc510\th1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=", - "dep\tgithub.com/google/uuid\tv1.6.0\th1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=", - "dep\tgithub.com/googleapis/enterprise-certificate-proxy\tv0.3.15\th1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=", - "dep\tgithub.com/googleapis/gax-go/v2\tv2.22.0\th1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=", - "dep\tgithub.com/gorilla/websocket\tv1.5.4-0.20250319132907-e064f32e3674\th1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=", - "dep\tgithub.com/grafana/regexp\tv0.0.0-20250905093917-f7b3be9d1853\th1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM=", - "dep\tgithub.com/grpc-ecosystem/grpc-gateway/v2\tv2.29.0\th1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=", - "dep\tgithub.com/hashicorp/errwrap\tv1.1.0\th1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=", - "dep\tgithub.com/hashicorp/go-multierror\tv1.1.1\th1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=", - "dep\tgithub.com/hashicorp/go-uuid\tv1.0.3\th1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=", - "dep\tgithub.com/inconshreveable/mousetrap\tv1.1.0\th1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=", - "dep\tgithub.com/jcmturner/aescts/v2\tv2.0.0\th1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=", - "dep\tgithub.com/jcmturner/dnsutils/v2\tv2.0.0\th1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=", - "dep\tgithub.com/jcmturner/gofork\tv1.7.6\th1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=", - "dep\tgithub.com/jcmturner/gokrb5/v8\tv8.4.4\th1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=", - "dep\tgithub.com/jcmturner/rpc/v2\tv2.0.3\th1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=", - "dep\tgithub.com/jedib0t/go-pretty/v6\tv6.7.8\th1:BVYrDy5DPBA3Qn9ICT+PokP9cvCv1KaHv2i+Hc8sr5o=", - "dep\tgithub.com/jessevdk/go-flags\tv1.6.1\th1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=", - "dep\tgithub.com/json-iterator/go\tv1.1.12\th1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=", - "dep\tgithub.com/juju/ratelimit\tv1.0.2\th1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=", - "dep\tgithub.com/klauspost/compress\tv1.18.7\th1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=", - "dep\tgithub.com/klauspost/cpuid/v2\tv2.3.0\th1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=", - "dep\tgithub.com/klauspost/crc32\tv1.3.0\th1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=", - "dep\tgithub.com/klauspost/filepathx\tv1.1.1\th1:201zvAsL1PhZvmXTP+QLer3AavWrO3U1NILWpniHK4w=", - "dep\tgithub.com/klauspost/pgzip\tv1.2.6\th1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=", - "dep\tgithub.com/klauspost/readahead\tv1.4.0\th1:w4hQ3BpdLjBnRQkZyNi+nwdHU7eGP9buTexWK9lU7gY=", - "dep\tgithub.com/klauspost/reedsolomon\tv1.13.3\th1:01GwnO2xoCSaM0ShP4qwl+FsHg3csFShC6Tu/RS1ji0=", - "dep\tgithub.com/kr/fs\tv0.1.0\th1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=", - "dep\tgithub.com/kylelemons/godebug\tv1.1.0\th1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=", - "dep\tgithub.com/lestrrat-go/blackmagic\tv1.0.4\th1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=", - "dep\tgithub.com/lestrrat-go/dsig\tv1.0.0\th1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=", - "dep\tgithub.com/lestrrat-go/httpcc\tv1.0.1\th1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=", - "dep\tgithub.com/lestrrat-go/httprc/v3\tv3.0.1\th1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=", - "dep\tgithub.com/lestrrat-go/jwx/v3\tv3.0.12\th1:p25r68Y4KrbBdYjIsQweYxq794CtGCzcrc5dGzJIRjg=", - "dep\tgithub.com/lestrrat-go/option\tv1.0.1\th1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=", - "dep\tgithub.com/lestrrat-go/option/v2\tv2.0.0\th1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=", - "dep\tgithub.com/lib/pq\tv1.10.9\th1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=", - "dep\tgithub.com/lithammer/shortuuid/v4\tv4.2.0\th1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=", - "dep\tgithub.com/lucasb-eyer/go-colorful\tv1.3.0\th1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=", - "dep\tgithub.com/mattn/go-colorable\tv0.1.15\th1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=", - "dep\tgithub.com/mattn/go-ieproxy\tv0.0.12\th1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M=", - "dep\tgithub.com/mattn/go-isatty\tv0.0.24\th1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=", - "dep\tgithub.com/mattn/go-runewidth\tv0.0.21\th1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=", - "dep\tgithub.com/matttproud/golang_protobuf_extensions\tv1.0.4\th1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=", - "dep\tgithub.com/miekg/dns\tv1.1.72\th1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=", - "dep\tgithub.com/minio/cli\tv1.24.2\th1:J+fCUh9mhPLjN3Lj/YhklXvxj8mnyE/D6FpFduXJ2jg=", - "dep\tgithub.com/minio/colorjson\tv1.0.8\th1:AS6gEQ1dTRYHmC4xuoodPDRILHP/9Wz5wYUGDQfPLpg=", - "dep\tgithub.com/minio/console\tv1.7.6", - "=>\tgithub.com/pgsty/silo-console\tv0.0.0-20260804042150-b952a1202869\th1:HLfc2ZdAycnI/bLl+TdsuckyzSWrwQarK38pS14yH/Q=", - "dep\tgithub.com/minio/crc64nvme\tv1.1.1\th1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=", - "dep\tgithub.com/minio/csvparser\tv1.0.0\th1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=", - "dep\tgithub.com/minio/dnscache\tv0.1.1\th1:AMYLqomzskpORiUA1ciN9k7bZT1oB3YZN4cEIi88W5o=", - "dep\tgithub.com/minio/dperf\tv0.7.1\th1:eBwtaBBjuANwgUy1waWoS+wP+0i5fkXJOdGU2RXuDxo=", - "dep\tgithub.com/minio/filepath\tv1.0.0\th1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY=", - "dep\tgithub.com/minio/highwayhash\tv1.0.4\th1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4=", - "dep\tgithub.com/minio/kms-go/kes\tv0.3.1\th1:K3sPFAvFbJx33XlCTUBnQo8JRmSZyDvT6T2/MQ2iC3A=", - "dep\tgithub.com/minio/kms-go/kms\tv0.6.0\th1:oGdGUyjfCZwRIi7em0aj4wk+oOm7+4a0lzSZny7ZIDU=", - "dep\tgithub.com/minio/madmin-go/v3\tv3.0.110\th1:FIYekj7YPc430ffpXFWiUtyut3qBt/unIAcDzJn9H5M=", - "dep\tgithub.com/minio/mc\tv0.0.0-20251106162529-77f82e18b540", - "=>\tgithub.com/pgsty/mc\tv0.0.0-20260801042411-ad10a2a10b76\th1:UIlUuz0LQKw4QlAljhv7nPDDFC1+n+e0iED7rWZrgZ8=", - "dep\tgithub.com/minio/md5-simd\tv1.1.2\th1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=", - "dep\tgithub.com/minio/minio-go/v7\tv7.0.99\th1:2vH/byrwUkIpFQFOilvTfaUpvAX3fEFhEzO+DR3DlCE=", - "dep\tgithub.com/minio/mux\tv1.9.2\th1:dQchne49BUBgOlxIHjx5wVe1gl5VXF2sxd4YCXkikTw=", - "dep\tgithub.com/minio/pkg/v3\tv3.6.1", - "=>\tgithub.com/pgsty/silo-pkg/v3\tv3.11.0\th1:wjN5d+tWD8Twq+e7k/KBBVhnWXC8xTIlfTcnGIKkmjc=", - "dep\tgithub.com/minio/selfupdate\tv0.6.0\th1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=", - "dep\tgithub.com/minio/simdjson-go\tv0.4.5\th1:r4IQwjRGmWCQ2VeMc7fGiilu1z5du0gJ/I/FsKwgo5A=", - "dep\tgithub.com/minio/sio\tv0.4.3\th1:JqyID1XM86KwBZox5RAdLD4MLPIDoCY2cke2CXCJCkg=", - "dep\tgithub.com/minio/websocket\tv1.6.0\th1:CPvnQvNvlVaQmvw5gtJNyYQhg4+xRmrPNhBbv8BdpAE=", - "dep\tgithub.com/minio/xxml\tv0.0.3\th1:ZIpPQpfyG5uZQnqqC0LZuWtPk/WT8G/qkxvO6jb7zMU=", - "dep\tgithub.com/minio/zipindex\tv0.5.0\th1:QydEWJW+uAFMd5xmQa580bm7JtC5krpuAtARXIQr72U=", - "dep\tgithub.com/mitchellh/go-homedir\tv1.1.0\th1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=", - "dep\tgithub.com/modern-go/concurrent\tv0.0.0-20180306012644-bacd9c7ef1dd\th1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=", - "dep\tgithub.com/modern-go/reflect2\tv1.0.3-0.20250322232337-35a7c28c31ee\th1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=", - "dep\tgithub.com/muesli/ansi\tv0.0.0-20230316100256-276c6243b2f6\th1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=", - "dep\tgithub.com/muesli/cancelreader\tv0.2.2\th1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=", - "dep\tgithub.com/muesli/reflow\tv0.3.0\th1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=", - "dep\tgithub.com/muesli/termenv\tv0.16.0\th1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=", - "dep\tgithub.com/munnerz/goautoneg\tv0.0.0-20191010083416-a7dc8b61c822\th1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=", - "dep\tgithub.com/nats-io/nats.go\tv1.49.0\th1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=", - "dep\tgithub.com/nats-io/nkeys\tv0.4.15\th1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=", - "dep\tgithub.com/nats-io/nuid\tv1.0.1\th1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=", - "dep\tgithub.com/nats-io/stan.go\tv0.10.4\th1:19GS/eD1SeQJaVkeM9EkvEYattnvnWrZ3wkSWSw4uXw=", - "dep\tgithub.com/ncw/directio\tv1.0.5\th1:JSUBhdjEvVaJvOoyPAbcW0fnd0tvRXD76wEfZ1KcQz4=", - "dep\tgithub.com/nsqio/go-nsq\tv1.1.0\th1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=", - "dep\tgithub.com/oklog/ulid/v2\tv2.1.1\th1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=", - "dep\tgithub.com/olekukonko/tablewriter\tv0.0.5\th1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=", - "dep\tgithub.com/philhofer/fwd\tv1.2.0\th1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=", - "dep\tgithub.com/pierrec/lz4/v4\tv4.1.26\th1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=", - "dep\tgithub.com/pkg/browser\tv0.0.0-20240102092130-5ac0b6a4141c\th1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=", - "dep\tgithub.com/pkg/errors\tv0.9.1\th1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=", - "dep\tgithub.com/pkg/sftp\tv1.13.10\th1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=", - "dep\tgithub.com/pkg/xattr\tv0.4.12\th1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM=", - "dep\tgithub.com/posener/complete\tv1.2.3\th1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=", - "dep\tgithub.com/prometheus/client_golang\tv1.23.2\th1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=", - "dep\tgithub.com/prometheus/client_model\tv0.6.2\th1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=", - "dep\tgithub.com/prometheus/common\tv0.67.5\th1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=", - "dep\tgithub.com/prometheus/procfs\tv0.20.1\th1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=", - "dep\tgithub.com/prometheus/prom2json\tv1.5.0\th1:WIcAOjLE1x476W3dUlmTL6E/e98CgVGuwwYusl6MPP8=", - "dep\tgithub.com/prometheus/prometheus\tv0.311.3\th1:3IrVxQv6v5i/ZCGi6OrYeBhtCwaPTn6Z3DYruXoYm3M=", - "dep\tgithub.com/puzpuzpuz/xsync/v3\tv3.5.1\th1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=", - "dep\tgithub.com/rabbitmq/amqp091-go\tv1.10.0\th1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=", - "dep\tgithub.com/rcrowley/go-metrics\tv0.0.0-20250401214520-65e299d6c5c9\th1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=", - "dep\tgithub.com/rivo/uniseg\tv0.4.7\th1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=", - "dep\tgithub.com/rjeczalik/notify\tv0.9.3\th1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=", - "dep\tgithub.com/rs/cors\tv1.11.1\th1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=", - "dep\tgithub.com/rs/xid\tv1.6.0\th1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=", - "dep\tgithub.com/safchain/ethtool\tv0.7.0\th1:rlJzfDetsVvT61uz8x1YIcFn12akMfuPulHtZjtb7Is=", - "dep\tgithub.com/secure-io/sio-go\tv0.3.1\th1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=", - "dep\tgithub.com/shirou/gopsutil/v3\tv3.24.5\th1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=", - "dep\tgithub.com/spiffe/go-spiffe/v2\tv2.6.0\th1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=", - "dep\tgithub.com/tidwall/gjson\tv1.18.0\th1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=", - "dep\tgithub.com/tidwall/match\tv1.2.0\th1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=", - "dep\tgithub.com/tidwall/pretty\tv1.2.1\th1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=", - "dep\tgithub.com/tinylib/msgp\tv1.6.4\th1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=", - "dep\tgithub.com/tklauser/go-sysconf\tv0.3.16\th1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=", - "dep\tgithub.com/tklauser/numcpus\tv0.11.0\th1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=", - "dep\tgithub.com/unrolled/secure\tv1.17.0\th1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=", - "dep\tgithub.com/valyala/bytebufferpool\tv1.0.0\th1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=", - "dep\tgithub.com/valyala/fastjson\tv1.6.4\th1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=", - "dep\tgithub.com/vbauerster/mpb/v8\tv8.12.0\th1:+gneY3ifzc88tKDzOtfG8k8gfngCx615S2ZmFM4liWg=", - "dep\tgithub.com/xdg/scram\tv1.0.5\th1:TuS0RFmt5Is5qm9Tm2SoD89OPqe4IRiFtyFY4iwWXsw=", - "dep\tgithub.com/xdg/stringprep\tv1.0.3\th1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4=", - "dep\tgithub.com/xo/terminfo\tv0.0.0-20220910002029-abceb7e1c41e\th1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=", - "dep\tgithub.com/zeebo/xxh3\tv1.1.0\th1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=", - "dep\tgo.etcd.io/etcd/api/v3\tv3.6.9\th1:UA7iKfEW1AzgihcBSGXci2kDGQiokSq41F9HMCI/RTI=", - "dep\tgo.etcd.io/etcd/client/pkg/v3\tv3.6.9\th1:T8nuk8Lz64C+Hzb0coBFLMSlVSQZBpAtFk46swdM1DA=", - "dep\tgo.etcd.io/etcd/client/v3\tv3.6.9\th1:3X555hQXmhRr27O37wls53g68CpUiPOiHXrZfz2Al+o=", - "dep\tgo.opentelemetry.io/auto/sdk\tv1.2.1\th1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=", - "dep\tgo.opentelemetry.io/contrib/detectors/gcp\tv1.43.0\th1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=", - "dep\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\tv0.67.0\th1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=", - "dep\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\tv0.69.0\th1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=", - "dep\tgo.opentelemetry.io/otel\tv1.44.0\th1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=", - "dep\tgo.opentelemetry.io/otel/metric\tv1.44.0\th1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=", - "dep\tgo.opentelemetry.io/otel/sdk\tv1.44.0\th1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=", - "dep\tgo.opentelemetry.io/otel/sdk/metric\tv1.44.0\th1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=", - "dep\tgo.opentelemetry.io/otel/trace\tv1.44.0\th1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=", - "dep\tgo.uber.org/atomic\tv1.11.0\th1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=", - "dep\tgo.uber.org/multierr\tv1.11.0\th1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=", - "dep\tgo.uber.org/zap\tv1.28.0\th1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=", - "dep\tgo.yaml.in/yaml/v2\tv2.4.4\th1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=", - "dep\tgo.yaml.in/yaml/v3\tv3.0.5\th1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=", - "dep\tgoftp.io/server/v2\tv2.0.2\th1:tkZpqyXys+vC15W5yGMi8Kzmbv1QSgeKr8qJXBnJbm8=", - "dep\tgolang.org/x/crypto\tv0.54.0\th1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=", - "dep\tgolang.org/x/net\tv0.57.0\th1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=", - "dep\tgolang.org/x/oauth2\tv0.36.0\th1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=", - "dep\tgolang.org/x/sync\tv0.22.0\th1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=", - "dep\tgolang.org/x/sys\tv0.47.0\th1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=", - "dep\tgolang.org/x/term\tv0.45.0\th1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=", - "dep\tgolang.org/x/text\tv0.40.0\th1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=", - "dep\tgolang.org/x/time\tv0.15.0\th1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=", - "dep\tgoogle.golang.org/api\tv0.278.0\th1:W7jiRvRi53VYFfZ/HoZjQBtJk7gOFbHD8ot1RzVZU6E=", - "dep\tgoogle.golang.org/genproto\tv0.0.0-20260319201613-d00831a3d3e7\th1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=", - "dep\tgoogle.golang.org/genproto/googleapis/api\tv0.0.0-20260526163538-3dc84a4a5aaa\th1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=", - "dep\tgoogle.golang.org/genproto/googleapis/rpc\tv0.0.0-20260526163538-3dc84a4a5aaa\th1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=", - "dep\tgoogle.golang.org/grpc\tv1.82.1\th1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=", - "dep\tgoogle.golang.org/protobuf\tv1.36.11\th1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=", - "dep\tgopkg.in/yaml.v2\tv2.4.0\th1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=" - ], - "build_settings": [ - "build\t-buildmode=exe", - "build\t-compiler=gc", - "build\t-tags=kqueue", - "build\tDefaultGODEBUG=tracebacklabels=0,x509sslcertoverrideplatform=0", - "build\tCGO_ENABLED=0", - "build\tGOARCH=arm64", - "build\tGOOS=linux", - "build\tGOARM64=v8.0" - ], - "patched": false, - "dependency_graph_sha256": "692c41d253e666fea024ec976b9d2728552bb58f75cdafe05ddb47dc15d4bad8" - }, - { - "binary": "head-go127", - "commit": "d1105bbb3d4a0afa33b3a4ac11b821235038ed0e", - "go": "go1.27.1", - "sha256": "55d2f2d73d5db4b449f7b4e037e8083acb3f26235e2df36114c50265b219ce1b", - "go_mod_sha256": "f8182f00d130b89bd794041cb804ac3255ce7b035c0b73f8e6ca7094d6ea4035", - "go_sum_sha256": "6913c1a5d63af9f1cc4b21205bf45a98bd3f53855e4aa49dd3eccac06cd6ef19", - "dependencies": [ - "dep\taead.dev/mem\tv0.2.0\th1:ufgkESS9+lHV/GUjxgc2ObF43FLZGSemh+W+y27QFMI=", - "dep\taead.dev/minisign\tv0.3.0\th1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=", - "dep\taead.dev/mtls\tv0.3.0\th1:a+C0t15Y9SRX6qP1EqmQFZ4ZSMm88TPvNDymasu4ahQ=", - "dep\tcel.dev/expr\tv0.25.2\th1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=", - "dep\tcloud.google.com/go\tv0.123.0\th1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=", - "dep\tcloud.google.com/go/auth\tv0.20.0\th1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=", - "dep\tcloud.google.com/go/auth/oauth2adapt\tv0.2.8\th1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=", - "dep\tcloud.google.com/go/compute/metadata\tv0.9.0\th1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=", - "dep\tcloud.google.com/go/iam\tv1.5.3\th1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=", - "dep\tcloud.google.com/go/monitoring\tv1.24.3\th1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=", - "dep\tcloud.google.com/go/storage\tv1.61.3\th1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=", - "dep\tfilippo.io/edwards25519\tv1.2.0\th1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore\tv1.22.0\th1:aokoqcHvaGjiM3VpjKDfMMnF/8epJ+Q1HLJ7CudztqE=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity\tv1.14.0\th1:CU4+EJeJi3TKYWEcYuSdWsjzw0nVsK/H0MSQOiPcymU=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/internal\tv1.12.0\th1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob\tv1.6.4\th1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM=", - "dep\tgithub.com/Azure/go-ntlmssp\tv0.1.1\th1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=", - "dep\tgithub.com/AzureAD/microsoft-authentication-library-for-go\tv1.7.2\th1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp\tv1.33.0\th1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric\tv0.55.0\th1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping\tv0.55.0\th1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=", - "dep\tgithub.com/IBM/sarama\tv1.45.1\th1:nY30XqYpqyXOXSNoe2XCgjj9jklGM1Ye94ierUb1jQ0=", - "dep\tgithub.com/VividCortex/ewma\tv1.2.0\th1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=", - "dep\tgithub.com/acarl005/stripansi\tv0.0.0-20180116102854-5a71ef0e047d\th1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=", - "dep\tgithub.com/alecthomas/participle\tv0.7.1\th1:2bN7reTw//5f0cugJcTOnY/NYZcWQOaajW+BwZB5xWs=", - "dep\tgithub.com/apache/thrift\tv0.24.0\th1:zy31L1a49QTNB2bG1BBfMXol3yJrTH975G3pPubQVLQ=", - "dep\tgithub.com/aymanbagabas/go-osc52/v2\tv2.0.1\th1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=", - "dep\tgithub.com/beevik/ntp\tv1.5.0\th1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=", - "dep\tgithub.com/beorn7/perks\tv1.0.1\th1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=", - "dep\tgithub.com/buger/jsonparser\tv1.1.2\th1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=", - "dep\tgithub.com/cespare/xxhash/v2\tv2.3.0\th1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=", - "dep\tgithub.com/charmbracelet/bubbles\tv1.0.0\th1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=", - "dep\tgithub.com/charmbracelet/bubbletea\tv1.3.10\th1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=", - "dep\tgithub.com/charmbracelet/colorprofile\tv0.4.3\th1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=", - "dep\tgithub.com/charmbracelet/harmonica\tv0.2.0\th1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=", - "dep\tgithub.com/charmbracelet/lipgloss\tv1.1.0\th1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=", - "dep\tgithub.com/charmbracelet/x/ansi\tv0.11.8\th1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=", - "dep\tgithub.com/charmbracelet/x/cellbuf\tv0.0.15\th1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=", - "dep\tgithub.com/charmbracelet/x/term\tv0.2.2\th1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=", - "dep\tgithub.com/cheggaaa/pb\tv1.0.30\th1:NylhgqJfXx3JVBGx6ywsXuhpz8caSMPmLArXyAv1bwU=", - "dep\tgithub.com/clipperhouse/displaywidth\tv0.11.0\th1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=", - "dep\tgithub.com/clipperhouse/uax29/v2\tv2.7.0\th1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=", - "dep\tgithub.com/cncf/xds/go\tv0.0.0-20260202195803-dba9d589def2\th1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=", - "dep\tgithub.com/coreos/go-oidc/v3\tv3.21.0\th1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM=", - "dep\tgithub.com/coreos/go-semver\tv0.3.1\th1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=", - "dep\tgithub.com/coreos/go-systemd/v22\tv22.7.0", - "=>\tgithub.com/coreos/go-systemd/v22\tv22.6.0\th1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=", - "dep\tgithub.com/cosnicolaou/pbzip2\tv1.0.6\th1:FYF6b2j4X4q3hZezd2AoUN/emLCtH/MbDGwJjiOacak=", - "dep\tgithub.com/davecgh/go-spew\tv1.1.2-0.20180830191138-d8f796af33cc\th1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=", - "dep\tgithub.com/dchest/siphash\tv1.2.3\th1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=", - "dep\tgithub.com/docker/go-units\tv0.5.0\th1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=", - "dep\tgithub.com/dustin/go-humanize\tv1.0.1\th1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=", - "dep\tgithub.com/eapache/go-resiliency\tv1.7.0\th1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=", - "dep\tgithub.com/eapache/go-xerial-snappy\tv0.0.0-20230731223053-c322873962e3\th1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=", - "dep\tgithub.com/eapache/queue\tv1.1.0\th1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=", - "dep\tgithub.com/eclipse/paho.mqtt.golang\tv1.5.1\th1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=", - "dep\tgithub.com/elastic/go-elasticsearch/v7\tv7.17.10\th1:TCQ8i4PmIJuBunvBS6bwT2ybzVFxxUhhltAs3Gyu1yo=", - "dep\tgithub.com/envoyproxy/go-control-plane/envoy\tv1.37.0\th1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=", - "dep\tgithub.com/envoyproxy/protoc-gen-validate\tv1.3.3\th1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=", - "dep\tgithub.com/fatih/color\tv1.19.0\th1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=", - "dep\tgithub.com/fatih/structs\tv1.1.0\th1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=", - "dep\tgithub.com/felixge/fgprof\tv0.9.5\th1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=", - "dep\tgithub.com/felixge/httpsnoop\tv1.1.0\th1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=", - "dep\tgithub.com/fraugster/parquet-go\tv0.12.0\th1:1slnC5y2VWEOUSlzbeXatM0BvSWcLUDsR/EcZsXXCZc=", - "dep\tgithub.com/go-asn1-ber/asn1-ber\tv1.5.8\th1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=", - "dep\tgithub.com/go-jose/go-jose/v4\tv4.1.4\th1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=", - "dep\tgithub.com/go-ldap/ldap/v3\tv3.4.14\th1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=", - "dep\tgithub.com/go-logr/logr\tv1.4.4\th1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=", - "dep\tgithub.com/go-logr/stdr\tv1.2.2\th1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=", - "dep\tgithub.com/go-openapi/analysis\tv0.26.2\th1:Q6wOwXW8mcVAkpDFMshj/F4PlK2Fx86tmLJjZW4vyEs=", - "dep\tgithub.com/go-openapi/errors\tv0.22.8\th1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I=", - "dep\tgithub.com/go-openapi/jsonpointer\tv1.0.0\th1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=", - "dep\tgithub.com/go-openapi/jsonreference\tv1.0.1\th1:4zJ7AmYDKNmD3aSpfPnFNCFA5E80/xMHUNKgydaLh38=", - "dep\tgithub.com/go-openapi/loads\tv0.25.2\th1:+uNsDlRQfYtZTrh+3pdwampcAqZVPuBJW0IA82aZHII=", - "dep\tgithub.com/go-openapi/runtime\tv0.33.1\th1:jCvhI+wAdsn29byy+RgcPcg+j39YT6E304QOE/WqIVk=", - "dep\tgithub.com/go-openapi/runtime/server-middleware\tv0.33.1\th1:IAeKbwWnBnpsYTpuPVS8t73ZrPpKvRZnK2iJ2KJGUV0=", - "dep\tgithub.com/go-openapi/spec\tv1.0.0\th1:JtB/GHOj+eetjse6YvxqLze88oEekl/4uPBethvzRrA=", - "dep\tgithub.com/go-openapi/strfmt\tv0.27.0\th1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM=", - "dep\tgithub.com/go-openapi/swag\tv0.29.1\th1:C6EeWzUwQtcWEhE9eqBdUubGXxhWY4PlzHMLD7kLaiQ=", - "dep\tgithub.com/go-openapi/swag/cmdutils\tv0.29.1\th1:3DorPGfUdE80BogKY22EzoHBcHMrkVomZMoV7kS4ANY=", - "dep\tgithub.com/go-openapi/swag/conv\tv0.29.1\th1:AC4Eh/5c/eUDOUCzzsRC9ghmFgOSBHeRMGIngY0ZUGA=", - "dep\tgithub.com/go-openapi/swag/fileutils\tv0.29.1\th1:ZcPzMceVhU1WPbK6N1G6sNQKdd1CWJlf3cA08UHuoM0=", - "dep\tgithub.com/go-openapi/swag/jsonutils\tv0.29.1\th1:AFCxs0eQZ24/QyfhVHM2t49rMz7Vv3XCsZQI6yrNy+c=", - "dep\tgithub.com/go-openapi/swag/loading\tv0.29.1\th1:FCv5fG8UhTdDJa2R7w+5O9Ekpcbw7tt0nFWvmDKGBjc=", - "dep\tgithub.com/go-openapi/swag/mangling\tv0.29.1\th1:lHALtvYCdxVnRl4GrHmFPwfBTZYIObqdGNSKyu/8D6I=", - "dep\tgithub.com/go-openapi/swag/netutils\tv0.29.1\th1:IjIvdEP5duKcghFqJEPSUraRnkKYHoM65kTluTu+Jb4=", - "dep\tgithub.com/go-openapi/swag/pools\tv0.29.1\th1:NRogYxdEW9SjRM4mkAOji9iefO4MRXq3p/ZJcoQbUKg=", - "dep\tgithub.com/go-openapi/swag/stringutils\tv0.29.1\th1:1ykunK7iJQk1uOO7+oUH1ukbsK85fFCOiCFMOVSY+F0=", - "dep\tgithub.com/go-openapi/swag/typeutils\tv0.29.1\th1:Nzv9nhnlLCRBPQqfOX+7lB6Guju370or8StT+lIOf6M=", - "dep\tgithub.com/go-openapi/swag/yamlutils\tv0.29.1\th1:69w3tsBajm7MR/fejLy7HD/3J68Ys1SeeZMEzZ3w2sk=", - "dep\tgithub.com/go-openapi/validate\tv0.26.5\th1:Vm02dSmhevDx/4v4m8KAtMwffHGfq9wRLqICeebE/D4=", - "dep\tgithub.com/go-sql-driver/mysql\tv1.9.3\th1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=", - "dep\tgithub.com/go-viper/mapstructure/v2\tv2.5.0\th1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=", - "dep\tgithub.com/gobwas/httphead\tv0.1.0\th1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=", - "dep\tgithub.com/gobwas/pool\tv0.2.1\th1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=", - "dep\tgithub.com/gobwas/ws\tv1.4.0\th1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=", - "dep\tgithub.com/gogo/protobuf\tv1.3.2\th1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=", - "dep\tgithub.com/golang-jwt/jwt/v4\tv4.5.2\th1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=", - "dep\tgithub.com/golang-jwt/jwt/v5\tv5.3.1\th1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=", - "dep\tgithub.com/golang/protobuf\tv1.5.4\th1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=", - "dep\tgithub.com/golang/snappy\tv1.0.0\th1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=", - "dep\tgithub.com/gomodule/redigo\tv1.9.3\th1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=", - "dep\tgithub.com/google/pprof\tv0.0.0-20260709232956-b9395ee17fa0\th1:du0WGc8xSKq/++e0cglxhS/mXVqsR7+c7jLEi5Vqduw=", - "dep\tgithub.com/google/s2a-go\tv0.1.9\th1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=", - "dep\tgithub.com/google/shlex\tv0.0.0-20191202100458-e7afc7fbc510\th1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=", - "dep\tgithub.com/google/uuid\tv1.6.0\th1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=", - "dep\tgithub.com/googleapis/enterprise-certificate-proxy\tv0.3.18\th1:hvVi34VucdrV1IIsiWuqYM8kutw/92MxNEFxCJZEh0k=", - "dep\tgithub.com/googleapis/gax-go/v2\tv2.23.0\th1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE=", - "dep\tgithub.com/gorilla/websocket\tv1.5.4-0.20250319132907-e064f32e3674\th1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=", - "dep\tgithub.com/grafana/regexp\tv0.0.0-20250905093917-f7b3be9d1853\th1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM=", - "dep\tgithub.com/grpc-ecosystem/grpc-gateway/v2\tv2.30.0\th1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=", - "dep\tgithub.com/hashicorp/errwrap\tv1.1.0\th1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=", - "dep\tgithub.com/hashicorp/go-multierror\tv1.1.1\th1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=", - "dep\tgithub.com/hashicorp/go-uuid\tv1.0.3\th1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=", - "dep\tgithub.com/inconshreveable/mousetrap\tv1.1.0\th1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=", - "dep\tgithub.com/jcmturner/aescts/v2\tv2.0.0\th1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=", - "dep\tgithub.com/jcmturner/dnsutils/v2\tv2.0.0\th1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=", - "dep\tgithub.com/jcmturner/gofork\tv1.7.6\th1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=", - "dep\tgithub.com/jcmturner/gokrb5/v8\tv8.4.4\th1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=", - "dep\tgithub.com/jcmturner/rpc/v2\tv2.0.3\th1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=", - "dep\tgithub.com/jedib0t/go-pretty/v6\tv6.8.3\th1:yVSk5aemoYHCvcrtqyXklwqcgHQIQzmy/oUzFlmffSQ=", - "dep\tgithub.com/jessevdk/go-flags\tv1.6.1\th1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=", - "dep\tgithub.com/json-iterator/go\tv1.1.12\th1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=", - "dep\tgithub.com/juju/ratelimit\tv1.0.2\th1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=", - "dep\tgithub.com/klauspost/compress\tv1.20.0\th1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=", - "dep\tgithub.com/klauspost/cpuid/v2\tv2.4.0\th1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=", - "dep\tgithub.com/klauspost/crc32\tv1.3.0\th1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=", - "dep\tgithub.com/klauspost/filepathx\tv1.1.1\th1:201zvAsL1PhZvmXTP+QLer3AavWrO3U1NILWpniHK4w=", - "dep\tgithub.com/klauspost/pgzip\tv1.2.6\th1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=", - "dep\tgithub.com/klauspost/readahead\tv1.4.0\th1:w4hQ3BpdLjBnRQkZyNi+nwdHU7eGP9buTexWK9lU7gY=", - "dep\tgithub.com/klauspost/reedsolomon\tv1.13.3\th1:01GwnO2xoCSaM0ShP4qwl+FsHg3csFShC6Tu/RS1ji0=", - "dep\tgithub.com/kr/fs\tv0.1.0\th1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=", - "dep\tgithub.com/kylelemons/godebug\tv1.1.0\th1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=", - "dep\tgithub.com/lestrrat-go/blackmagic\tv1.0.4\th1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=", - "dep\tgithub.com/lestrrat-go/dsig\tv1.4.0\th1:g7LUjK8cT74A5DzBXJI5HzsJuLhoYN0Wzj4nuOMIrH8=", - "dep\tgithub.com/lestrrat-go/httpcc\tv1.0.1\th1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=", - "dep\tgithub.com/lestrrat-go/httprc/v3\tv3.0.6\th1:4FpLQ18KK/ypPbVU3NLWJNRvH3kcYiqKqWfKGqNWxxI=", - "dep\tgithub.com/lestrrat-go/jwx/v3\tv3.2.0\th1:Jb3zBASTSZXz7gzzSAfYqxXF8KejvKC4xWoePLQqXCA=", - "dep\tgithub.com/lestrrat-go/option/v2\tv2.0.0\th1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=", - "dep\tgithub.com/lib/pq\tv1.10.9\th1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=", - "dep\tgithub.com/lithammer/shortuuid/v4\tv4.2.0\th1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=", - "dep\tgithub.com/lucasb-eyer/go-colorful\tv1.4.1\th1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=", - "dep\tgithub.com/mattn/go-colorable\tv0.1.15\th1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=", - "dep\tgithub.com/mattn/go-ieproxy\tv0.0.12\th1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M=", - "dep\tgithub.com/mattn/go-isatty\tv0.0.24\th1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=", - "dep\tgithub.com/mattn/go-runewidth\tv0.0.29\th1:3oGF3R/S2N9DQ3ptftzVIvg2eicmojCzlwBEmqEPDfQ=", - "dep\tgithub.com/matttproud/golang_protobuf_extensions\tv1.0.4\th1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=", - "dep\tgithub.com/miekg/dns\tv1.1.73\th1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=", - "dep\tgithub.com/minio/cli\tv1.24.2\th1:J+fCUh9mhPLjN3Lj/YhklXvxj8mnyE/D6FpFduXJ2jg=", - "dep\tgithub.com/minio/colorjson\tv1.0.8\th1:AS6gEQ1dTRYHmC4xuoodPDRILHP/9Wz5wYUGDQfPLpg=", - "dep\tgithub.com/minio/console\tv1.7.6", - "=>\tgithub.com/pgsty/silo-console\tv0.0.0-20260908142700-c103d08ec36a\th1:aHLqQ7INozqGLEOB1tr+n/eKgrBhlQ20fHyLmeNu+ao=", - "dep\tgithub.com/minio/crc64nvme\tv1.1.1\th1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=", - "dep\tgithub.com/minio/csvparser\tv1.0.0\th1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=", - "dep\tgithub.com/minio/dnscache\tv0.1.1\th1:AMYLqomzskpORiUA1ciN9k7bZT1oB3YZN4cEIi88W5o=", - "dep\tgithub.com/minio/dperf\tv0.7.1\th1:eBwtaBBjuANwgUy1waWoS+wP+0i5fkXJOdGU2RXuDxo=", - "dep\tgithub.com/minio/filepath\tv1.0.0\th1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY=", - "dep\tgithub.com/minio/highwayhash\tv1.0.4\th1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4=", - "dep\tgithub.com/minio/kms-go/kes\tv0.3.1\th1:K3sPFAvFbJx33XlCTUBnQo8JRmSZyDvT6T2/MQ2iC3A=", - "dep\tgithub.com/minio/kms-go/kms\tv0.6.0\th1:oGdGUyjfCZwRIi7em0aj4wk+oOm7+4a0lzSZny7ZIDU=", - "dep\tgithub.com/minio/madmin-go/v3\tv3.0.110\th1:FIYekj7YPc430ffpXFWiUtyut3qBt/unIAcDzJn9H5M=", - "dep\tgithub.com/minio/mc\tv0.0.0-20251106162529-77f82e18b540", - "=>\tgithub.com/pgsty/mc\tv0.0.0-20260909015522-fcd5cad8247f\th1:JiL/FcsGMsAhA+Iv+0Jzk9VnEAVVv4HUNbR0cGf+/CE=", - "dep\tgithub.com/minio/md5-simd\tv1.1.2\th1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=", - "dep\tgithub.com/minio/minio-go/v7\tv7.3.1-0.20260828014306-0e78d3f18efe\th1:By2FKNSOUGLOeb0x4D7xJMHr8x/X1ZW8PG780SpKUwQ=", - "dep\tgithub.com/minio/mux\tv1.10.1\th1:grrK8SwRKbkNFE6qG7WAvFGH09bB46d5teOOtKfQ14s=", - "dep\tgithub.com/minio/pkg/v3\tv3.6.1\th1:gaNT80BS/iuIany5ylTkVmfN4s6UYY30OtImFv4GQA8=", - "dep\tgithub.com/minio/selfupdate\tv0.6.0\th1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=", - "dep\tgithub.com/minio/simdjson-go\tv0.4.5\th1:r4IQwjRGmWCQ2VeMc7fGiilu1z5du0gJ/I/FsKwgo5A=", - "dep\tgithub.com/minio/sio\tv0.4.3\th1:JqyID1XM86KwBZox5RAdLD4MLPIDoCY2cke2CXCJCkg=", - "dep\tgithub.com/minio/websocket\tv1.6.0\th1:CPvnQvNvlVaQmvw5gtJNyYQhg4+xRmrPNhBbv8BdpAE=", - "dep\tgithub.com/minio/xxml\tv0.0.3\th1:ZIpPQpfyG5uZQnqqC0LZuWtPk/WT8G/qkxvO6jb7zMU=", - "dep\tgithub.com/minio/zipindex\tv0.5.0\th1:QydEWJW+uAFMd5xmQa580bm7JtC5krpuAtARXIQr72U=", - "dep\tgithub.com/mitchellh/go-homedir\tv1.1.0\th1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=", - "dep\tgithub.com/modern-go/concurrent\tv0.0.0-20180306012644-bacd9c7ef1dd\th1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=", - "dep\tgithub.com/modern-go/reflect2\tv1.0.3-0.20250322232337-35a7c28c31ee\th1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=", - "dep\tgithub.com/muesli/ansi\tv0.0.0-20230316100256-276c6243b2f6\th1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=", - "dep\tgithub.com/muesli/cancelreader\tv0.2.2\th1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=", - "dep\tgithub.com/muesli/reflow\tv0.3.0\th1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=", - "dep\tgithub.com/muesli/termenv\tv0.16.0\th1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=", - "dep\tgithub.com/munnerz/goautoneg\tv0.0.0-20191010083416-a7dc8b61c822\th1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=", - "dep\tgithub.com/nats-io/nats.go\tv1.49.0\th1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=", - "dep\tgithub.com/nats-io/nkeys\tv0.4.15\th1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=", - "dep\tgithub.com/nats-io/nuid\tv1.0.1\th1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=", - "dep\tgithub.com/nats-io/stan.go\tv0.10.4\th1:19GS/eD1SeQJaVkeM9EkvEYattnvnWrZ3wkSWSw4uXw=", - "dep\tgithub.com/ncw/directio\tv1.0.5\th1:JSUBhdjEvVaJvOoyPAbcW0fnd0tvRXD76wEfZ1KcQz4=", - "dep\tgithub.com/nsqio/go-nsq\tv1.1.0\th1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=", - "dep\tgithub.com/oklog/ulid/v2\tv2.1.2\th1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec=", - "dep\tgithub.com/olekukonko/tablewriter\tv0.0.5\th1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=", - "dep\tgithub.com/pgsty/silo-pkg/v3\tv3.13.3\th1:d2xYTn4LXoWIAIjBlW/17wtA/Ut1ap29t+1ww4TFa8o=", - "dep\tgithub.com/philhofer/fwd\tv1.2.0\th1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=", - "dep\tgithub.com/pierrec/lz4/v4\tv4.1.29\th1:CDQY6qZOLI4DW0Nx6R1vRrifrCeQHnNXkMb0hZWXFjg=", - "dep\tgithub.com/pkg/browser\tv0.0.0-20240102092130-5ac0b6a4141c\th1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=", - "dep\tgithub.com/pkg/errors\tv0.9.1\th1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=", - "dep\tgithub.com/pkg/sftp\tv1.13.11\th1:0N92SLTB8JqASJB14ZLHHzFnBV8mG9zw4K7jghEFWuE=", - "dep\tgithub.com/pkg/xattr\tv0.4.12\th1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM=", - "dep\tgithub.com/posener/complete\tv1.2.3\th1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=", - "dep\tgithub.com/prometheus/client_golang\tv1.24.1\th1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=", - "dep\tgithub.com/prometheus/client_model\tv0.6.3\th1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo=", - "dep\tgithub.com/prometheus/common\tv0.71.0\th1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8=", - "dep\tgithub.com/prometheus/procfs\tv0.22.0\th1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics=", - "dep\tgithub.com/prometheus/prom2json\tv1.5.0\th1:WIcAOjLE1x476W3dUlmTL6E/e98CgVGuwwYusl6MPP8=", - "dep\tgithub.com/prometheus/prometheus\tv0.314.0\th1:YjsimqsIi6/mOtzZcrPEYUALO6zpfaht9O5sXqDz2vg=", - "dep\tgithub.com/puzpuzpuz/xsync/v3\tv3.5.1\th1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=", - "dep\tgithub.com/rabbitmq/amqp091-go\tv1.10.0\th1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=", - "dep\tgithub.com/rcrowley/go-metrics\tv0.0.0-20250401214520-65e299d6c5c9\th1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=", - "dep\tgithub.com/rivo/uniseg\tv0.4.7\th1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=", - "dep\tgithub.com/rjeczalik/notify\tv0.9.3\th1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=", - "dep\tgithub.com/rs/cors\tv1.11.1\th1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=", - "dep\tgithub.com/rs/xid\tv1.6.0\th1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=", - "dep\tgithub.com/safchain/ethtool\tv0.7.0\th1:rlJzfDetsVvT61uz8x1YIcFn12akMfuPulHtZjtb7Is=", - "dep\tgithub.com/secure-io/sio-go\tv0.3.1\th1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=", - "dep\tgithub.com/shirou/gopsutil/v3\tv3.24.5\th1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=", - "dep\tgithub.com/spiffe/go-spiffe/v2\tv2.7.0\th1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=", - "dep\tgithub.com/tidwall/gjson\tv1.19.0\th1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=", - "dep\tgithub.com/tidwall/match\tv1.2.0\th1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=", - "dep\tgithub.com/tidwall/pretty\tv1.2.1\th1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=", - "dep\tgithub.com/tinylib/msgp\tv1.6.4\th1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=", - "dep\tgithub.com/tklauser/go-sysconf\tv0.4.0\th1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=", - "dep\tgithub.com/tklauser/numcpus\tv0.12.0\th1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=", - "dep\tgithub.com/unrolled/secure\tv1.17.0\th1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=", - "dep\tgithub.com/valyala/bytebufferpool\tv1.0.0\th1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=", - "dep\tgithub.com/valyala/fastjson\tv1.6.10\th1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=", - "dep\tgithub.com/vbauerster/cupwriter\tv0.0.4\th1:9sBPe0uXWLZuWQU5lqVbhyFlxX6c09asST/YfatFAys=", - "dep\tgithub.com/vbauerster/mpb/v8\tv8.16.1\th1:gNYmwMip9xRWNGAiblZOgUNXWeU2P0NIGd5x0f8ffbc=", - "dep\tgithub.com/xdg/scram\tv1.0.5\th1:TuS0RFmt5Is5qm9Tm2SoD89OPqe4IRiFtyFY4iwWXsw=", - "dep\tgithub.com/xdg/stringprep\tv1.0.3\th1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4=", - "dep\tgithub.com/xo/terminfo\tv1.0.0\th1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY=", - "dep\tgithub.com/zeebo/xxh3\tv1.1.0\th1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=", - "dep\tgo.etcd.io/etcd/api/v3\tv3.7.1\th1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E=", - "dep\tgo.etcd.io/etcd/client/pkg/v3\tv3.7.1\th1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mfFv4=", - "dep\tgo.etcd.io/etcd/client/v3\tv3.7.1\th1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg=", - "dep\tgo.opentelemetry.io/auto/sdk\tv1.2.1\th1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=", - "dep\tgo.opentelemetry.io/contrib/detectors/gcp\tv1.44.0\th1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=", - "dep\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\tv0.70.0\th1:oECp5f+hN7nkwjU/8BxQ/q23bGPb8FIrD839owX222E=", - "dep\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\tv0.70.0\th1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI=", - "dep\tgo.opentelemetry.io/otel\tv1.45.0\th1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=", - "dep\tgo.opentelemetry.io/otel/metric\tv1.45.0\th1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=", - "dep\tgo.opentelemetry.io/otel/sdk\tv1.45.0\th1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=", - "dep\tgo.opentelemetry.io/otel/sdk/metric\tv1.45.0\th1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=", - "dep\tgo.opentelemetry.io/otel/trace\tv1.45.0\th1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=", - "dep\tgo.uber.org/atomic\tv1.11.0\th1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=", - "dep\tgo.uber.org/multierr\tv1.11.0\th1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=", - "dep\tgo.uber.org/zap\tv1.28.0\th1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=", - "dep\tgo.yaml.in/yaml/v3\tv3.0.5\th1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=", - "dep\tgoftp.io/server/v2\tv2.0.3\th1:iz6Gxj7f2SFQVxrj0s1is+gueE6O9yTc+Ab0vtQ6Zn4=", - "dep\tgolang.org/x/crypto\tv0.56.0\th1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=", - "dep\tgolang.org/x/net\tv0.58.0\th1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=", - "dep\tgolang.org/x/oauth2\tv0.36.0\th1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=", - "dep\tgolang.org/x/sync\tv0.22.0\th1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=", - "dep\tgolang.org/x/sys\tv0.47.0\th1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=", - "dep\tgolang.org/x/term\tv0.45.0\th1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=", - "dep\tgolang.org/x/text\tv0.41.0\th1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=", - "dep\tgolang.org/x/time\tv0.15.0\th1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=", - "dep\tgoogle.golang.org/api\tv0.290.0\th1:eMw0Xo+IfbbMlKmW7aHvpyQRv9RCXuWx/vs8AD+0x9A=", - "dep\tgoogle.golang.org/genproto\tv0.0.0-20260319201613-d00831a3d3e7\th1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=", - "dep\tgoogle.golang.org/genproto/googleapis/api\tv0.0.0-20260831171406-18b4a7587f8a\th1:i3TAXhpKc7TUP1VAPiBBrv45kamjoizCC3rOC0cAbOs=", - "dep\tgoogle.golang.org/genproto/googleapis/rpc\tv0.0.0-20260831171406-18b4a7587f8a\th1:3Dnd1cDaZlB68lziofO+bJXpjOy8UfRv8Unt+yH8tQ4=", - "dep\tgoogle.golang.org/grpc\tv1.83.2\th1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=", - "dep\tgoogle.golang.org/protobuf\tv1.36.12\th1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=", - "dep\tgopkg.in/ini.v1\tv1.67.3\th1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw=", - "dep\tgopkg.in/yaml.v2\tv2.4.0\th1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=" - ], - "build_settings": [ - "build\t-buildmode=exe", - "build\t-compiler=gc", - "build\t-tags=kqueue", - "build\tCGO_ENABLED=0", - "build\tGOARCH=arm64", - "build\tGOOS=linux", - "build\tGOARM64=v8.0" - ], - "patched": false, - "dependency_graph_sha256": "8abb47f79286fb552648941de615a293a666e8c88685750ef54f06eab46e8660" - }, - { - "binary": "candidate-go127", - "commit": "d1105bbb3d4a0afa33b3a4ac11b821235038ed0e", - "go": "go1.27.1", - "sha256": "6932cc87cd6d525e4a591abfe66fcc713be243393e9360122468d120031491b3", - "go_mod_sha256": "f8182f00d130b89bd794041cb804ac3255ce7b035c0b73f8e6ca7094d6ea4035", - "go_sum_sha256": "6913c1a5d63af9f1cc4b21205bf45a98bd3f53855e4aa49dd3eccac06cd6ef19", - "dependencies": [ - "dep\taead.dev/mem\tv0.2.0\th1:ufgkESS9+lHV/GUjxgc2ObF43FLZGSemh+W+y27QFMI=", - "dep\taead.dev/minisign\tv0.3.0\th1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=", - "dep\taead.dev/mtls\tv0.3.0\th1:a+C0t15Y9SRX6qP1EqmQFZ4ZSMm88TPvNDymasu4ahQ=", - "dep\tcel.dev/expr\tv0.25.2\th1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=", - "dep\tcloud.google.com/go\tv0.123.0\th1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=", - "dep\tcloud.google.com/go/auth\tv0.20.0\th1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=", - "dep\tcloud.google.com/go/auth/oauth2adapt\tv0.2.8\th1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=", - "dep\tcloud.google.com/go/compute/metadata\tv0.9.0\th1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=", - "dep\tcloud.google.com/go/iam\tv1.5.3\th1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=", - "dep\tcloud.google.com/go/monitoring\tv1.24.3\th1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=", - "dep\tcloud.google.com/go/storage\tv1.61.3\th1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=", - "dep\tfilippo.io/edwards25519\tv1.2.0\th1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore\tv1.22.0\th1:aokoqcHvaGjiM3VpjKDfMMnF/8epJ+Q1HLJ7CudztqE=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity\tv1.14.0\th1:CU4+EJeJi3TKYWEcYuSdWsjzw0nVsK/H0MSQOiPcymU=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/internal\tv1.12.0\th1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4=", - "dep\tgithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob\tv1.6.4\th1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM=", - "dep\tgithub.com/Azure/go-ntlmssp\tv0.1.1\th1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=", - "dep\tgithub.com/AzureAD/microsoft-authentication-library-for-go\tv1.7.2\th1:RHK7bS+HQMslb1sZpAokUt+zTVmue0hKSs2C791hhzU=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp\tv1.33.0\th1:l7+6kwRMJNwdCvYdDl7Eax+wzEYHSnNY7zrrfbhDdTA=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric\tv0.55.0\th1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=", - "dep\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping\tv0.55.0\th1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=", - "dep\tgithub.com/IBM/sarama\tv1.45.1\th1:nY30XqYpqyXOXSNoe2XCgjj9jklGM1Ye94ierUb1jQ0=", - "dep\tgithub.com/VividCortex/ewma\tv1.2.0\th1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=", - "dep\tgithub.com/acarl005/stripansi\tv0.0.0-20180116102854-5a71ef0e047d\th1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=", - "dep\tgithub.com/alecthomas/participle\tv0.7.1\th1:2bN7reTw//5f0cugJcTOnY/NYZcWQOaajW+BwZB5xWs=", - "dep\tgithub.com/apache/thrift\tv0.24.0\th1:zy31L1a49QTNB2bG1BBfMXol3yJrTH975G3pPubQVLQ=", - "dep\tgithub.com/aymanbagabas/go-osc52/v2\tv2.0.1\th1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=", - "dep\tgithub.com/beevik/ntp\tv1.5.0\th1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4=", - "dep\tgithub.com/beorn7/perks\tv1.0.1\th1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=", - "dep\tgithub.com/buger/jsonparser\tv1.1.2\th1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=", - "dep\tgithub.com/cespare/xxhash/v2\tv2.3.0\th1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=", - "dep\tgithub.com/charmbracelet/bubbles\tv1.0.0\th1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=", - "dep\tgithub.com/charmbracelet/bubbletea\tv1.3.10\th1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=", - "dep\tgithub.com/charmbracelet/colorprofile\tv0.4.3\th1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=", - "dep\tgithub.com/charmbracelet/harmonica\tv0.2.0\th1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=", - "dep\tgithub.com/charmbracelet/lipgloss\tv1.1.0\th1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=", - "dep\tgithub.com/charmbracelet/x/ansi\tv0.11.8\th1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=", - "dep\tgithub.com/charmbracelet/x/cellbuf\tv0.0.15\th1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=", - "dep\tgithub.com/charmbracelet/x/term\tv0.2.2\th1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=", - "dep\tgithub.com/cheggaaa/pb\tv1.0.30\th1:NylhgqJfXx3JVBGx6ywsXuhpz8caSMPmLArXyAv1bwU=", - "dep\tgithub.com/clipperhouse/displaywidth\tv0.11.0\th1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=", - "dep\tgithub.com/clipperhouse/uax29/v2\tv2.7.0\th1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=", - "dep\tgithub.com/cncf/xds/go\tv0.0.0-20260202195803-dba9d589def2\th1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=", - "dep\tgithub.com/coreos/go-oidc/v3\tv3.21.0\th1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM=", - "dep\tgithub.com/coreos/go-semver\tv0.3.1\th1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=", - "dep\tgithub.com/coreos/go-systemd/v22\tv22.7.0", - "=>\tgithub.com/coreos/go-systemd/v22\tv22.6.0\th1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=", - "dep\tgithub.com/cosnicolaou/pbzip2\tv1.0.6\th1:FYF6b2j4X4q3hZezd2AoUN/emLCtH/MbDGwJjiOacak=", - "dep\tgithub.com/davecgh/go-spew\tv1.1.2-0.20180830191138-d8f796af33cc\th1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=", - "dep\tgithub.com/dchest/siphash\tv1.2.3\th1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=", - "dep\tgithub.com/docker/go-units\tv0.5.0\th1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=", - "dep\tgithub.com/dustin/go-humanize\tv1.0.1\th1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=", - "dep\tgithub.com/eapache/go-resiliency\tv1.7.0\th1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=", - "dep\tgithub.com/eapache/go-xerial-snappy\tv0.0.0-20230731223053-c322873962e3\th1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=", - "dep\tgithub.com/eapache/queue\tv1.1.0\th1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=", - "dep\tgithub.com/eclipse/paho.mqtt.golang\tv1.5.1\th1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=", - "dep\tgithub.com/elastic/go-elasticsearch/v7\tv7.17.10\th1:TCQ8i4PmIJuBunvBS6bwT2ybzVFxxUhhltAs3Gyu1yo=", - "dep\tgithub.com/envoyproxy/go-control-plane/envoy\tv1.37.0\th1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=", - "dep\tgithub.com/envoyproxy/protoc-gen-validate\tv1.3.3\th1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=", - "dep\tgithub.com/fatih/color\tv1.19.0\th1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=", - "dep\tgithub.com/fatih/structs\tv1.1.0\th1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=", - "dep\tgithub.com/felixge/fgprof\tv0.9.5\th1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=", - "dep\tgithub.com/felixge/httpsnoop\tv1.1.0\th1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=", - "dep\tgithub.com/fraugster/parquet-go\tv0.12.0\th1:1slnC5y2VWEOUSlzbeXatM0BvSWcLUDsR/EcZsXXCZc=", - "dep\tgithub.com/go-asn1-ber/asn1-ber\tv1.5.8\th1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=", - "dep\tgithub.com/go-jose/go-jose/v4\tv4.1.4\th1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=", - "dep\tgithub.com/go-ldap/ldap/v3\tv3.4.14\th1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=", - "dep\tgithub.com/go-logr/logr\tv1.4.4\th1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=", - "dep\tgithub.com/go-logr/stdr\tv1.2.2\th1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=", - "dep\tgithub.com/go-openapi/analysis\tv0.26.2\th1:Q6wOwXW8mcVAkpDFMshj/F4PlK2Fx86tmLJjZW4vyEs=", - "dep\tgithub.com/go-openapi/errors\tv0.22.8\th1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I=", - "dep\tgithub.com/go-openapi/jsonpointer\tv1.0.0\th1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=", - "dep\tgithub.com/go-openapi/jsonreference\tv1.0.1\th1:4zJ7AmYDKNmD3aSpfPnFNCFA5E80/xMHUNKgydaLh38=", - "dep\tgithub.com/go-openapi/loads\tv0.25.2\th1:+uNsDlRQfYtZTrh+3pdwampcAqZVPuBJW0IA82aZHII=", - "dep\tgithub.com/go-openapi/runtime\tv0.33.1\th1:jCvhI+wAdsn29byy+RgcPcg+j39YT6E304QOE/WqIVk=", - "dep\tgithub.com/go-openapi/runtime/server-middleware\tv0.33.1\th1:IAeKbwWnBnpsYTpuPVS8t73ZrPpKvRZnK2iJ2KJGUV0=", - "dep\tgithub.com/go-openapi/spec\tv1.0.0\th1:JtB/GHOj+eetjse6YvxqLze88oEekl/4uPBethvzRrA=", - "dep\tgithub.com/go-openapi/strfmt\tv0.27.0\th1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM=", - "dep\tgithub.com/go-openapi/swag\tv0.29.1\th1:C6EeWzUwQtcWEhE9eqBdUubGXxhWY4PlzHMLD7kLaiQ=", - "dep\tgithub.com/go-openapi/swag/cmdutils\tv0.29.1\th1:3DorPGfUdE80BogKY22EzoHBcHMrkVomZMoV7kS4ANY=", - "dep\tgithub.com/go-openapi/swag/conv\tv0.29.1\th1:AC4Eh/5c/eUDOUCzzsRC9ghmFgOSBHeRMGIngY0ZUGA=", - "dep\tgithub.com/go-openapi/swag/fileutils\tv0.29.1\th1:ZcPzMceVhU1WPbK6N1G6sNQKdd1CWJlf3cA08UHuoM0=", - "dep\tgithub.com/go-openapi/swag/jsonutils\tv0.29.1\th1:AFCxs0eQZ24/QyfhVHM2t49rMz7Vv3XCsZQI6yrNy+c=", - "dep\tgithub.com/go-openapi/swag/loading\tv0.29.1\th1:FCv5fG8UhTdDJa2R7w+5O9Ekpcbw7tt0nFWvmDKGBjc=", - "dep\tgithub.com/go-openapi/swag/mangling\tv0.29.1\th1:lHALtvYCdxVnRl4GrHmFPwfBTZYIObqdGNSKyu/8D6I=", - "dep\tgithub.com/go-openapi/swag/netutils\tv0.29.1\th1:IjIvdEP5duKcghFqJEPSUraRnkKYHoM65kTluTu+Jb4=", - "dep\tgithub.com/go-openapi/swag/pools\tv0.29.1\th1:NRogYxdEW9SjRM4mkAOji9iefO4MRXq3p/ZJcoQbUKg=", - "dep\tgithub.com/go-openapi/swag/stringutils\tv0.29.1\th1:1ykunK7iJQk1uOO7+oUH1ukbsK85fFCOiCFMOVSY+F0=", - "dep\tgithub.com/go-openapi/swag/typeutils\tv0.29.1\th1:Nzv9nhnlLCRBPQqfOX+7lB6Guju370or8StT+lIOf6M=", - "dep\tgithub.com/go-openapi/swag/yamlutils\tv0.29.1\th1:69w3tsBajm7MR/fejLy7HD/3J68Ys1SeeZMEzZ3w2sk=", - "dep\tgithub.com/go-openapi/validate\tv0.26.5\th1:Vm02dSmhevDx/4v4m8KAtMwffHGfq9wRLqICeebE/D4=", - "dep\tgithub.com/go-sql-driver/mysql\tv1.9.3\th1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=", - "dep\tgithub.com/go-viper/mapstructure/v2\tv2.5.0\th1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=", - "dep\tgithub.com/gobwas/httphead\tv0.1.0\th1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=", - "dep\tgithub.com/gobwas/pool\tv0.2.1\th1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=", - "dep\tgithub.com/gobwas/ws\tv1.4.0\th1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=", - "dep\tgithub.com/gogo/protobuf\tv1.3.2\th1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=", - "dep\tgithub.com/golang-jwt/jwt/v4\tv4.5.2\th1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=", - "dep\tgithub.com/golang-jwt/jwt/v5\tv5.3.1\th1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=", - "dep\tgithub.com/golang/protobuf\tv1.5.4\th1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=", - "dep\tgithub.com/golang/snappy\tv1.0.0\th1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=", - "dep\tgithub.com/gomodule/redigo\tv1.9.3\th1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=", - "dep\tgithub.com/google/pprof\tv0.0.0-20260709232956-b9395ee17fa0\th1:du0WGc8xSKq/++e0cglxhS/mXVqsR7+c7jLEi5Vqduw=", - "dep\tgithub.com/google/s2a-go\tv0.1.9\th1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=", - "dep\tgithub.com/google/shlex\tv0.0.0-20191202100458-e7afc7fbc510\th1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=", - "dep\tgithub.com/google/uuid\tv1.6.0\th1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=", - "dep\tgithub.com/googleapis/enterprise-certificate-proxy\tv0.3.18\th1:hvVi34VucdrV1IIsiWuqYM8kutw/92MxNEFxCJZEh0k=", - "dep\tgithub.com/googleapis/gax-go/v2\tv2.23.0\th1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE=", - "dep\tgithub.com/gorilla/websocket\tv1.5.4-0.20250319132907-e064f32e3674\th1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=", - "dep\tgithub.com/grafana/regexp\tv0.0.0-20250905093917-f7b3be9d1853\th1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM=", - "dep\tgithub.com/grpc-ecosystem/grpc-gateway/v2\tv2.30.0\th1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=", - "dep\tgithub.com/hashicorp/errwrap\tv1.1.0\th1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=", - "dep\tgithub.com/hashicorp/go-multierror\tv1.1.1\th1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=", - "dep\tgithub.com/hashicorp/go-uuid\tv1.0.3\th1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=", - "dep\tgithub.com/inconshreveable/mousetrap\tv1.1.0\th1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=", - "dep\tgithub.com/jcmturner/aescts/v2\tv2.0.0\th1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=", - "dep\tgithub.com/jcmturner/dnsutils/v2\tv2.0.0\th1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=", - "dep\tgithub.com/jcmturner/gofork\tv1.7.6\th1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=", - "dep\tgithub.com/jcmturner/gokrb5/v8\tv8.4.4\th1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=", - "dep\tgithub.com/jcmturner/rpc/v2\tv2.0.3\th1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=", - "dep\tgithub.com/jedib0t/go-pretty/v6\tv6.8.3\th1:yVSk5aemoYHCvcrtqyXklwqcgHQIQzmy/oUzFlmffSQ=", - "dep\tgithub.com/jessevdk/go-flags\tv1.6.1\th1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=", - "dep\tgithub.com/json-iterator/go\tv1.1.12\th1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=", - "dep\tgithub.com/juju/ratelimit\tv1.0.2\th1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=", - "dep\tgithub.com/klauspost/compress\tv1.20.0\th1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=", - "dep\tgithub.com/klauspost/cpuid/v2\tv2.4.0\th1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=", - "dep\tgithub.com/klauspost/crc32\tv1.3.0\th1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=", - "dep\tgithub.com/klauspost/filepathx\tv1.1.1\th1:201zvAsL1PhZvmXTP+QLer3AavWrO3U1NILWpniHK4w=", - "dep\tgithub.com/klauspost/pgzip\tv1.2.6\th1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=", - "dep\tgithub.com/klauspost/readahead\tv1.4.0\th1:w4hQ3BpdLjBnRQkZyNi+nwdHU7eGP9buTexWK9lU7gY=", - "dep\tgithub.com/klauspost/reedsolomon\tv1.13.3\th1:01GwnO2xoCSaM0ShP4qwl+FsHg3csFShC6Tu/RS1ji0=", - "dep\tgithub.com/kr/fs\tv0.1.0\th1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=", - "dep\tgithub.com/kylelemons/godebug\tv1.1.0\th1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=", - "dep\tgithub.com/lestrrat-go/blackmagic\tv1.0.4\th1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=", - "dep\tgithub.com/lestrrat-go/dsig\tv1.4.0\th1:g7LUjK8cT74A5DzBXJI5HzsJuLhoYN0Wzj4nuOMIrH8=", - "dep\tgithub.com/lestrrat-go/httpcc\tv1.0.1\th1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=", - "dep\tgithub.com/lestrrat-go/httprc/v3\tv3.0.6\th1:4FpLQ18KK/ypPbVU3NLWJNRvH3kcYiqKqWfKGqNWxxI=", - "dep\tgithub.com/lestrrat-go/jwx/v3\tv3.2.0\th1:Jb3zBASTSZXz7gzzSAfYqxXF8KejvKC4xWoePLQqXCA=", - "dep\tgithub.com/lestrrat-go/option/v2\tv2.0.0\th1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=", - "dep\tgithub.com/lib/pq\tv1.10.9\th1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=", - "dep\tgithub.com/lithammer/shortuuid/v4\tv4.2.0\th1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=", - "dep\tgithub.com/lucasb-eyer/go-colorful\tv1.4.1\th1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=", - "dep\tgithub.com/mattn/go-colorable\tv0.1.15\th1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=", - "dep\tgithub.com/mattn/go-ieproxy\tv0.0.12\th1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M=", - "dep\tgithub.com/mattn/go-isatty\tv0.0.24\th1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=", - "dep\tgithub.com/mattn/go-runewidth\tv0.0.29\th1:3oGF3R/S2N9DQ3ptftzVIvg2eicmojCzlwBEmqEPDfQ=", - "dep\tgithub.com/matttproud/golang_protobuf_extensions\tv1.0.4\th1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=", - "dep\tgithub.com/miekg/dns\tv1.1.73\th1:uhT8nJxmTrPJYClxVxTCX+CVn6qnzSiybRk72Z6DgrE=", - "dep\tgithub.com/minio/cli\tv1.24.2\th1:J+fCUh9mhPLjN3Lj/YhklXvxj8mnyE/D6FpFduXJ2jg=", - "dep\tgithub.com/minio/colorjson\tv1.0.8\th1:AS6gEQ1dTRYHmC4xuoodPDRILHP/9Wz5wYUGDQfPLpg=", - "dep\tgithub.com/minio/console\tv1.7.6", - "=>\tgithub.com/pgsty/silo-console\tv0.0.0-20260908142700-c103d08ec36a\th1:aHLqQ7INozqGLEOB1tr+n/eKgrBhlQ20fHyLmeNu+ao=", - "dep\tgithub.com/minio/crc64nvme\tv1.1.1\th1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=", - "dep\tgithub.com/minio/csvparser\tv1.0.0\th1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=", - "dep\tgithub.com/minio/dnscache\tv0.1.1\th1:AMYLqomzskpORiUA1ciN9k7bZT1oB3YZN4cEIi88W5o=", - "dep\tgithub.com/minio/dperf\tv0.7.1\th1:eBwtaBBjuANwgUy1waWoS+wP+0i5fkXJOdGU2RXuDxo=", - "dep\tgithub.com/minio/filepath\tv1.0.0\th1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY=", - "dep\tgithub.com/minio/highwayhash\tv1.0.4\th1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4=", - "dep\tgithub.com/minio/kms-go/kes\tv0.3.1\th1:K3sPFAvFbJx33XlCTUBnQo8JRmSZyDvT6T2/MQ2iC3A=", - "dep\tgithub.com/minio/kms-go/kms\tv0.6.0\th1:oGdGUyjfCZwRIi7em0aj4wk+oOm7+4a0lzSZny7ZIDU=", - "dep\tgithub.com/minio/madmin-go/v3\tv3.0.110\th1:FIYekj7YPc430ffpXFWiUtyut3qBt/unIAcDzJn9H5M=", - "dep\tgithub.com/minio/mc\tv0.0.0-20251106162529-77f82e18b540", - "=>\tgithub.com/pgsty/mc\tv0.0.0-20260909015522-fcd5cad8247f\th1:JiL/FcsGMsAhA+Iv+0Jzk9VnEAVVv4HUNbR0cGf+/CE=", - "dep\tgithub.com/minio/md5-simd\tv1.1.2\th1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=", - "dep\tgithub.com/minio/minio-go/v7\tv7.3.1-0.20260828014306-0e78d3f18efe\th1:By2FKNSOUGLOeb0x4D7xJMHr8x/X1ZW8PG780SpKUwQ=", - "dep\tgithub.com/minio/mux\tv1.10.1\th1:grrK8SwRKbkNFE6qG7WAvFGH09bB46d5teOOtKfQ14s=", - "dep\tgithub.com/minio/pkg/v3\tv3.6.1\th1:gaNT80BS/iuIany5ylTkVmfN4s6UYY30OtImFv4GQA8=", - "dep\tgithub.com/minio/selfupdate\tv0.6.0\th1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=", - "dep\tgithub.com/minio/simdjson-go\tv0.4.5\th1:r4IQwjRGmWCQ2VeMc7fGiilu1z5du0gJ/I/FsKwgo5A=", - "dep\tgithub.com/minio/sio\tv0.4.3\th1:JqyID1XM86KwBZox5RAdLD4MLPIDoCY2cke2CXCJCkg=", - "dep\tgithub.com/minio/websocket\tv1.6.0\th1:CPvnQvNvlVaQmvw5gtJNyYQhg4+xRmrPNhBbv8BdpAE=", - "dep\tgithub.com/minio/xxml\tv0.0.3\th1:ZIpPQpfyG5uZQnqqC0LZuWtPk/WT8G/qkxvO6jb7zMU=", - "dep\tgithub.com/minio/zipindex\tv0.5.0\th1:QydEWJW+uAFMd5xmQa580bm7JtC5krpuAtARXIQr72U=", - "dep\tgithub.com/mitchellh/go-homedir\tv1.1.0\th1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=", - "dep\tgithub.com/modern-go/concurrent\tv0.0.0-20180306012644-bacd9c7ef1dd\th1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=", - "dep\tgithub.com/modern-go/reflect2\tv1.0.3-0.20250322232337-35a7c28c31ee\th1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=", - "dep\tgithub.com/muesli/ansi\tv0.0.0-20230316100256-276c6243b2f6\th1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=", - "dep\tgithub.com/muesli/cancelreader\tv0.2.2\th1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=", - "dep\tgithub.com/muesli/reflow\tv0.3.0\th1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=", - "dep\tgithub.com/muesli/termenv\tv0.16.0\th1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=", - "dep\tgithub.com/munnerz/goautoneg\tv0.0.0-20191010083416-a7dc8b61c822\th1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=", - "dep\tgithub.com/nats-io/nats.go\tv1.49.0\th1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=", - "dep\tgithub.com/nats-io/nkeys\tv0.4.15\th1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=", - "dep\tgithub.com/nats-io/nuid\tv1.0.1\th1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=", - "dep\tgithub.com/nats-io/stan.go\tv0.10.4\th1:19GS/eD1SeQJaVkeM9EkvEYattnvnWrZ3wkSWSw4uXw=", - "dep\tgithub.com/ncw/directio\tv1.0.5\th1:JSUBhdjEvVaJvOoyPAbcW0fnd0tvRXD76wEfZ1KcQz4=", - "dep\tgithub.com/nsqio/go-nsq\tv1.1.0\th1:PQg+xxiUjA7V+TLdXw7nVrJ5Jbl3sN86EhGCQj4+FYE=", - "dep\tgithub.com/oklog/ulid/v2\tv2.1.2\th1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec=", - "dep\tgithub.com/olekukonko/tablewriter\tv0.0.5\th1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=", - "dep\tgithub.com/pgsty/silo-pkg/v3\tv3.13.3\th1:d2xYTn4LXoWIAIjBlW/17wtA/Ut1ap29t+1ww4TFa8o=", - "dep\tgithub.com/philhofer/fwd\tv1.2.0\th1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=", - "dep\tgithub.com/pierrec/lz4/v4\tv4.1.29\th1:CDQY6qZOLI4DW0Nx6R1vRrifrCeQHnNXkMb0hZWXFjg=", - "dep\tgithub.com/pkg/browser\tv0.0.0-20240102092130-5ac0b6a4141c\th1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=", - "dep\tgithub.com/pkg/errors\tv0.9.1\th1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=", - "dep\tgithub.com/pkg/sftp\tv1.13.11\th1:0N92SLTB8JqASJB14ZLHHzFnBV8mG9zw4K7jghEFWuE=", - "dep\tgithub.com/pkg/xattr\tv0.4.12\th1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM=", - "dep\tgithub.com/posener/complete\tv1.2.3\th1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=", - "dep\tgithub.com/prometheus/client_golang\tv1.24.1\th1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=", - "dep\tgithub.com/prometheus/client_model\tv0.6.3\th1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo=", - "dep\tgithub.com/prometheus/common\tv0.71.0\th1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8=", - "dep\tgithub.com/prometheus/procfs\tv0.22.0\th1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics=", - "dep\tgithub.com/prometheus/prom2json\tv1.5.0\th1:WIcAOjLE1x476W3dUlmTL6E/e98CgVGuwwYusl6MPP8=", - "dep\tgithub.com/prometheus/prometheus\tv0.314.0\th1:YjsimqsIi6/mOtzZcrPEYUALO6zpfaht9O5sXqDz2vg=", - "dep\tgithub.com/puzpuzpuz/xsync/v3\tv3.5.1\th1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=", - "dep\tgithub.com/rabbitmq/amqp091-go\tv1.10.0\th1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=", - "dep\tgithub.com/rcrowley/go-metrics\tv0.0.0-20250401214520-65e299d6c5c9\th1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=", - "dep\tgithub.com/rivo/uniseg\tv0.4.7\th1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=", - "dep\tgithub.com/rjeczalik/notify\tv0.9.3\th1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=", - "dep\tgithub.com/rs/cors\tv1.11.1\th1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=", - "dep\tgithub.com/rs/xid\tv1.6.0\th1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=", - "dep\tgithub.com/safchain/ethtool\tv0.7.0\th1:rlJzfDetsVvT61uz8x1YIcFn12akMfuPulHtZjtb7Is=", - "dep\tgithub.com/secure-io/sio-go\tv0.3.1\th1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=", - "dep\tgithub.com/shirou/gopsutil/v3\tv3.24.5\th1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=", - "dep\tgithub.com/spiffe/go-spiffe/v2\tv2.7.0\th1:uXe1MflJoHw58wAUvxVlcM7WpKtijWG7I1UidcGh6g4=", - "dep\tgithub.com/tidwall/gjson\tv1.19.0\th1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=", - "dep\tgithub.com/tidwall/match\tv1.2.0\th1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=", - "dep\tgithub.com/tidwall/pretty\tv1.2.1\th1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=", - "dep\tgithub.com/tinylib/msgp\tv1.6.4\th1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=", - "dep\tgithub.com/tklauser/go-sysconf\tv0.4.0\th1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=", - "dep\tgithub.com/tklauser/numcpus\tv0.12.0\th1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=", - "dep\tgithub.com/unrolled/secure\tv1.17.0\th1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=", - "dep\tgithub.com/valyala/bytebufferpool\tv1.0.0\th1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=", - "dep\tgithub.com/valyala/fastjson\tv1.6.10\th1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=", - "dep\tgithub.com/vbauerster/cupwriter\tv0.0.4\th1:9sBPe0uXWLZuWQU5lqVbhyFlxX6c09asST/YfatFAys=", - "dep\tgithub.com/vbauerster/mpb/v8\tv8.16.1\th1:gNYmwMip9xRWNGAiblZOgUNXWeU2P0NIGd5x0f8ffbc=", - "dep\tgithub.com/xdg/scram\tv1.0.5\th1:TuS0RFmt5Is5qm9Tm2SoD89OPqe4IRiFtyFY4iwWXsw=", - "dep\tgithub.com/xdg/stringprep\tv1.0.3\th1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4=", - "dep\tgithub.com/xo/terminfo\tv1.0.0\th1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY=", - "dep\tgithub.com/zeebo/xxh3\tv1.1.0\th1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=", - "dep\tgo.etcd.io/etcd/api/v3\tv3.7.1\th1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E=", - "dep\tgo.etcd.io/etcd/client/pkg/v3\tv3.7.1\th1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mfFv4=", - "dep\tgo.etcd.io/etcd/client/v3\tv3.7.1\th1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg=", - "dep\tgo.opentelemetry.io/auto/sdk\tv1.2.1\th1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=", - "dep\tgo.opentelemetry.io/contrib/detectors/gcp\tv1.44.0\th1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw=", - "dep\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc\tv0.70.0\th1:oECp5f+hN7nkwjU/8BxQ/q23bGPb8FIrD839owX222E=", - "dep\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp\tv0.70.0\th1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI=", - "dep\tgo.opentelemetry.io/otel\tv1.45.0\th1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=", - "dep\tgo.opentelemetry.io/otel/metric\tv1.45.0\th1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=", - "dep\tgo.opentelemetry.io/otel/sdk\tv1.45.0\th1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=", - "dep\tgo.opentelemetry.io/otel/sdk/metric\tv1.45.0\th1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o=", - "dep\tgo.opentelemetry.io/otel/trace\tv1.45.0\th1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=", - "dep\tgo.uber.org/atomic\tv1.11.0\th1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=", - "dep\tgo.uber.org/multierr\tv1.11.0\th1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=", - "dep\tgo.uber.org/zap\tv1.28.0\th1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=", - "dep\tgo.yaml.in/yaml/v3\tv3.0.5\th1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=", - "dep\tgoftp.io/server/v2\tv2.0.3\th1:iz6Gxj7f2SFQVxrj0s1is+gueE6O9yTc+Ab0vtQ6Zn4=", - "dep\tgolang.org/x/crypto\tv0.56.0\th1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=", - "dep\tgolang.org/x/net\tv0.58.0\th1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=", - "dep\tgolang.org/x/oauth2\tv0.36.0\th1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=", - "dep\tgolang.org/x/sync\tv0.22.0\th1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=", - "dep\tgolang.org/x/sys\tv0.47.0\th1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=", - "dep\tgolang.org/x/term\tv0.45.0\th1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=", - "dep\tgolang.org/x/text\tv0.41.0\th1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=", - "dep\tgolang.org/x/time\tv0.15.0\th1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=", - "dep\tgoogle.golang.org/api\tv0.290.0\th1:eMw0Xo+IfbbMlKmW7aHvpyQRv9RCXuWx/vs8AD+0x9A=", - "dep\tgoogle.golang.org/genproto\tv0.0.0-20260319201613-d00831a3d3e7\th1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=", - "dep\tgoogle.golang.org/genproto/googleapis/api\tv0.0.0-20260831171406-18b4a7587f8a\th1:i3TAXhpKc7TUP1VAPiBBrv45kamjoizCC3rOC0cAbOs=", - "dep\tgoogle.golang.org/genproto/googleapis/rpc\tv0.0.0-20260831171406-18b4a7587f8a\th1:3Dnd1cDaZlB68lziofO+bJXpjOy8UfRv8Unt+yH8tQ4=", - "dep\tgoogle.golang.org/grpc\tv1.83.2\th1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=", - "dep\tgoogle.golang.org/protobuf\tv1.36.12\th1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=", - "dep\tgopkg.in/ini.v1\tv1.67.3\th1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw=", - "dep\tgopkg.in/yaml.v2\tv2.4.0\th1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=" - ], - "build_settings": [ - "build\t-buildmode=exe", - "build\t-compiler=gc", - "build\t-tags=kqueue", - "build\tCGO_ENABLED=0", - "build\tGOARCH=arm64", - "build\tGOOS=linux", - "build\tGOARM64=v8.0" - ], - "patched": true, - "dependency_graph_sha256": "8abb47f79286fb552648941de615a293a666e8c88685750ef54f06eab46e8660" - } - ], - "results": [ - { - "case": "candidate127-add", - "binary": "candidate-go127", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "add_ok": true, - "add_reset": false, - "events": [ - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - }, - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Silo" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Silo" - } - ], - "server_log_summary": { - "connection_reset_count": 0, - "openid_wait_count": 0, - "untrusted_ca": false - } - }, - { - "case": "candidate127-compat-login", - "binary": "candidate-go127", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "oauth": { - "callback_status": 200, - "login_status": 204, - "buckets_status": 200, - "session_cookie": true - }, - "bad-signature": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "bad-audience": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "events": [ - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Silo" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Silo" - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - }, - { - "alpn": null, - "bytes_read": 269, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-signature", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Silo" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-audience", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 49200, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "Go-http-client/1.1" - } - ], - "server_log_summary": { - "connection_reset_count": 0, - "openid_wait_count": 0, - "untrusted_ca": false - } - }, - { - "case": "candidate127-mldsa", - "binary": "candidate-go127", - "mode": "reject-mldsa", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 503, - "ready": 200, - "console": 0, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "events": [ - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mldsa", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 287, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mldsa", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mldsa", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - } - ], - "server_log_summary": { - "connection_reset_count": 2, - "openid_wait_count": 2, - "untrusted_ca": false - } - }, - { - "case": "candidate127-no-optout", - "binary": "candidate-go127", - "mode": "reject-mlkem", - "godebug": null, - "tls13": false, - "cluster": 503, - "ready": 200, - "console": 0, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "events": [ - { - "alpn": null, - "bytes_read": 1513, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1513, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - } - ], - "server_log_summary": { - "connection_reset_count": 2, - "openid_wait_count": 2, - "untrusted_ca": false - } - }, - { - "case": "candidate127-tls13-login", - "binary": "candidate-go127", - "mode": "normal", - "godebug": null, - "tls13": true, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "oauth": { - "callback_status": 200, - "login_status": 204, - "buckets_status": 200, - "session_cookie": true - }, - "bad-signature": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "bad-audience": { - "callback_status": 200, - "login_status": 500, - "buckets_status": 403, - "session_cookie": false - }, - "events": [ - { - "alpn": null, - "bytes_read": 1513, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Silo" - }, - { - "cipher": 4865, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Silo" - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 772, - "user_agent_family": "curl/7.88.1" - }, - { - "alpn": null, - "bytes_read": 1495, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-signature", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Silo" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "alpn": null, - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "bad-audience", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 4865, - "event": "request", - "path": "/authorize", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Python-urllib/3.11" - }, - { - "cipher": 4865, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - }, - { - "cipher": 4865, - "event": "request", - "path": "/token", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 772, - "user_agent_family": "Go-http-client/1.1" - } - ], - "server_log_summary": { - "connection_reset_count": 0, - "openid_wait_count": 0, - "untrusted_ca": false - } - }, - { - "case": "candidate127-untrusted", - "binary": "candidate-go127", - "mode": "normal", - "godebug": null, - "tls13": false, - "cluster": 503, - "ready": 200, - "console": 0, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "events": [ - { - "alpn": null, - "bytes_read": 1513, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1513, - "curves": [ - 4588, - 4587, - 4589, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - } - ], - "server_log_summary": { - "connection_reset_count": 0, - "openid_wait_count": 2, - "untrusted_ca": true - } - }, - { - "case": "head127-add", - "binary": "head-go127", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "add_ok": false, - "add_reset": true, - "events": [ - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - } - ], - "server_log_summary": { - "connection_reset_count": 0, - "openid_wait_count": 0, - "untrusted_ca": false - } - }, - { - "case": "head127-compat", - "binary": "head-go127", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 503, - "ready": 200, - "console": 0, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - } - ], - "server_log_summary": { - "connection_reset_count": 2, - "openid_wait_count": 2, - "untrusted_ca": false - } - }, - { - "case": "old126-compat", - "binary": "old-go126", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "events": [ - { - "alpn": null, - "bytes_read": 275, - "curves": [ - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "MinIO" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "MinIO" - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - } - ], - "server_log_summary": { - "connection_reset_count": 0, - "openid_wait_count": 0, - "untrusted_ca": false - } - }, - { - "case": "old126-normal", - "binary": "old-go126", - "mode": "normal", - "godebug": null, - "tls13": false, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "events": [ - { - "alpn": null, - "bytes_read": 1497, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "MinIO" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "MinIO" - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - } - ], - "server_log_summary": { - "connection_reset_count": 0, - "openid_wait_count": 0, - "untrusted_ca": false - } - }, - { - "case": "old127-compat", - "binary": "old-go127", - "mode": "reject-mlkem", - "godebug": "tlsmlkem=0", - "tls13": false, - "cluster": 503, - "ready": 200, - "console": 0, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "reject-mlkem", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - } - ], - "server_log_summary": { - "connection_reset_count": 2, - "openid_wait_count": 2, - "untrusted_ca": false - } - }, - { - "case": "old127-normal", - "binary": "old-go127", - "mode": "normal", - "godebug": null, - "tls13": false, - "cluster": 200, - "ready": 200, - "console": 200, - "admin_list_ok": true, - "curl": { - "exit": 0, - "status_protocol": "200 2" - }, - "events": [ - { - "alpn": null, - "bytes_read": 1509, - "curves": [ - 4588, - 29, - 23, - 24, - 25 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 2308, - 2309, - 2310, - 2052, - 1027, - 2055, - 2053, - 2054, - 1025, - 1281, - 1537, - 1283, - 1539 - ], - "versions": [ - 772, - 771 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "MinIO" - }, - { - "cipher": 49200, - "event": "request", - "path": "/jwks", - "protocol": "HTTP/1.1", - "resumed": false, - "tls": 771, - "user_agent_family": "MinIO" - }, - { - "alpn": [ - "h2", - "http/1.1" - ], - "bytes_read": 517, - "curves": [ - 29, - 23, - 30, - 25, - 24, - 256, - 257, - 258, - 259, - 260 - ], - "event": "hello", - "mode": "normal", - "signatures": [ - 1027, - 1283, - 1539, - 2055, - 2056, - 2057, - 2058, - 2059, - 2052, - 2053, - 2054, - 1025, - 1281, - 1537, - 771, - 769, - 770, - 1026, - 1282, - 1538 - ], - "versions": [ - 772, - 771, - 770, - 769 - ] - }, - { - "cipher": 49200, - "event": "request", - "path": "/.well-known/openid-configuration", - "protocol": "HTTP/2.0", - "resumed": false, - "tls": 771, - "user_agent_family": "curl/7.88.1" - } - ], - "server_log_summary": { - "connection_reset_count": 0, - "openid_wait_count": 0, - "untrusted_ca": false - } - } - ], - "current_graph_with_old_toolchain": { - "toolchain": "go1.26.7", - "command": "GOTOOLCHAIN=local go list -mod=readonly ./cmd", - "exit": 1, - "error": "go: go.mod requires go >= 1.27.1 (running go 1.26.7; GOTOOLCHAIN=local)", - "server_console_mc_go_directives": "1.27.1" - }, - "limitations": [ - "The reject-mlkem and reject-mldsa policies were deliberately programmed into the synthetic fixture. They prove conditional mechanisms, not the customer ingress behavior.", - "No external network, customer endpoint, real Keycloak, production data, image publication or deployment.", - "TLS 1.3 fixture permits TLS 1.3 and negotiates it using P-256, not a full interoperability matrix for the newly offered PQ groups.", - "The valid TLS 1.2/1.3 OAuth flows, invalid signature and invalid audience use a synthetic lab-only issuer and API-driven callbacks. No real browser UI or logout test.", - "The scoped patch still needs tlsmlkem=0 for the ML-KEM-intolerant fixture, and does not fix ML-DSA intolerance." - ], - "candidate_package_tests": { - "go": "go1.27.1", - "platform": "darwin/arm64", - "CGO_ENABLED": "0", - "GOWORK": "off", - "GOTOOLCHAIN": "local", - "command": "go test -mod=readonly -count=1 ./internal/http ./internal/config/identity/openid", - "exit": 0, - "packages": { - "internal/http": "ok 1.109s", - "internal/config/identity/openid": "ok 1.807s" - } - } -} diff --git a/docs/investigations/issue-154/openid-default-curves.patch b/docs/investigations/issue-154/openid-default-curves.patch deleted file mode 100644 index 603fc6e19..000000000 --- a/docs/investigations/issue-154/openid-default-curves.patch +++ /dev/null @@ -1,40 +0,0 @@ -# Historical OIDC-only candidate; superseded by ../go127-stack.md. Do not apply on top of the stack fix. ---- a/cmd/utils.go -+++ b/cmd/utils.go -@@ -654,6 +654,14 @@ - return NewHTTPTransportWithTimeout(1 * time.Minute) - } - -+// NewOpenIDHTTPTransport uses Go defaults for external identity-provider key exchange. -+// This lets tlsmlkem/tlssecpmlkem configure their documented default sets. -+func NewOpenIDHTTPTransport() *http.Transport { -+ tr := NewHTTPTransport() -+ tr.TLSClientConfig.CurvePreferences = nil -+ return tr -+} -+ - // Default values for dial timeout - const defaultDialTimeout = 5 * time.Second - ---- a/cmd/iam.go -+++ b/cmd/iam.go -@@ -277,7 +277,7 @@ - for { - if !openidInit { - openidConfig, err := openid.LookupConfig(s, -- xhttp.WithUserAgent(NewHTTPTransport(), func() string { -+ xhttp.WithUserAgent(NewOpenIDHTTPTransport(), func() string { - return getUserAgent(getMinioMode()) - }), xhttp.DrainBody, globalSite.Region()) - if err != nil { ---- a/cmd/config-current.go -+++ b/cmd/config-current.go -@@ -352,7 +352,7 @@ - } - case config.IdentityOpenIDSubSys: - if _, err := openid.LookupConfig(s, -- xhttp.WithUserAgent(NewHTTPTransport(), func() string { -+ xhttp.WithUserAgent(NewOpenIDHTTPTransport(), func() string { - return getUserAgent(getMinioMode()) - }), xhttp.DrainBody, globalSite.Region()); err != nil { - return err diff --git a/docs/investigations/issue-154/probe.go b/docs/investigations/issue-154/probe.go deleted file mode 100644 index ebf8b4ee8..000000000 --- a/docs/investigations/issue-154/probe.go +++ /dev/null @@ -1,165 +0,0 @@ -//go:build ignore - -// Diagnostic GET using the Server's actual transport constructor. -// Build explicitly from the SILO module root; see ../issue-154.md. -package main - -import ( - "context" - "crypto/tls" - "errors" - "flag" - "fmt" - "io" - "net" - "net/http" - "net/http/httptrace" - "net/url" - "os" - "runtime" - "strings" - "sync" - "time" - - "github.com/minio/minio/cmd" - xhttp "github.com/minio/minio/internal/http" - "github.com/pgsty/silo-pkg/v3/certs" -) - -func main() { - endpoint := flag.String("url", os.Getenv("OIDC_URL"), "discovery URL; no credentials or query string") - ca := flag.String("ca", "", "same CA file or certs/CAs directory as Server") - h2 := flag.Bool("h2", false, "diagnostic: opt in to HTTP/2") - classical := flag.Bool("classical", false, "diagnostic: omit hybrid key exchange only") - defaultCurves := flag.Bool("default-curves", false, "diagnostic: let Go choose curves and honor its GODEBUG defaults") - tls12 := flag.Bool("tls12", false, "diagnostic: TLS 1.2 only; keeps certificate verification") - direct := flag.Bool("direct", false, "diagnostic: bypass environment proxy") - ip := flag.String("ip", "", "diagnostic: pin destination IP, preserving Host/SNI; requires -direct") - fresh := flag.Bool("fresh", false, "diagnostic: close idle connections between requests") - ua := flag.String("ua", "issue-154-probe", "HTTP User-Agent; supply actual Server UA to investigate a WAF") - n := flag.Int("n", 1, "number of GETs (1 to 3)") - flag.Parse() - u, err := url.Parse(*endpoint) - if err != nil || u.Scheme != "https" || u.Host == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" || *n < 1 || *n > 3 { - fmt.Fprintln(os.Stderr, "require an HTTPS URL without credentials/query/fragment and -n between 1 and 3") - os.Exit(2) - } - if *ip != "" && (!*direct || net.ParseIP(*ip) == nil) { - fmt.Fprintln(os.Stderr, "-ip requires a literal IP and -direct") - os.Exit(2) - } - if *classical && *defaultCurves { - fmt.Fprintln(os.Stderr, "choose at most one of -classical and -default-curves") - os.Exit(2) - } - tr := cmd.NewHTTPTransport() - tr.TLSClientConfig.RootCAs, err = certs.GetRootCAs(*ca) - if err != nil { - fmt.Fprintln(os.Stderr, "CA loading failed; check the local CA path") - os.Exit(2) - } - if *h2 { - tr.ForceAttemptHTTP2 = true - } - if *classical { - tr.TLSClientConfig.CurvePreferences = []tls.CurveID{tls.CurveP256, tls.X25519, tls.CurveP384, tls.CurveP521} - } - if *defaultCurves { - tr.TLSClientConfig.CurvePreferences = nil - } - if *tls12 { - tr.TLSClientConfig.MinVersion = tls.VersionTLS12 - tr.TLSClientConfig.MaxVersion = tls.VersionTLS12 - } - if *direct { - tr.Proxy = nil - } - if *ip != "" { - base := tr.DialContext - tr.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { - host, port, e := net.SplitHostPort(address) - if e != nil { - return nil, e - } - if host == u.Hostname() { - address = net.JoinHostPort(*ip, port) - } - return base(ctx, network, address) - } - } - defer tr.CloseIdleConnections() - var mu sync.Mutex - log := func(format string, args ...any) { mu.Lock(); defer mu.Unlock(); fmt.Printf(format+"\n", args...) } - log("go=%s os=%s arch=%s h2=%v classical=%v default_curves=%v tls12=%v", runtime.Version(), runtime.GOOS, runtime.GOARCH, *h2, *classical, *defaultCurves, *tls12) - // Deliberately print no URL, headers, body, client ID, secret, or token. - req, _ := http.NewRequest(http.MethodGet, u.String(), nil) - proxy := "direct" - if tr.Proxy != nil { - p, e := tr.Proxy(req) - if e != nil { - log("proxy_selection_error=%T", e) - os.Exit(2) - } - if p != nil { - proxy = p.Scheme + " proxy (address omitted)" - } - } - log("route=%s curves=%v", proxy, tr.TLSClientConfig.CurvePreferences) - client := &http.Client{Transport: xhttp.WithUserAgent(tr, func() string { return *ua }), Timeout: 20 * time.Second, - CheckRedirect: func(_ *http.Request, _ []*http.Request) error { return http.ErrUseLastResponse }} - failed := false - for i := 0; i < *n; i++ { - if *fresh { - tr.CloseIdleConnections() - } - log("request=%d", i+1) - trace := &httptrace.ClientTrace{ - DNSDone: func(d httptrace.DNSDoneInfo) { log("dns_addresses=%v err=%s", d.Addrs, errorClass(d.Err)) }, - ConnectStart: func(network, addr string) { log("connect=%s %s", network, addr) }, - ConnectDone: func(_, addr string, e error) { log("connected=%s err=%s", addr, errorClass(e)) }, - TLSHandshakeStart: func() { log("tls_start") }, - TLSHandshakeDone: func(s tls.ConnectionState, e error) { - log("tls_done=0x%x cipher=%s alpn=%q resumed=%v verified_chains=%d err=%s", s.Version, tls.CipherSuiteName(s.CipherSuite), s.NegotiatedProtocol, s.DidResume, len(s.VerifiedChains), errorClass(e)) - }, - GotConn: func(c httptrace.GotConnInfo) { log("got_conn=%s reused=%v", c.Conn.RemoteAddr(), c.Reused) }, - WroteRequest: func(w httptrace.WroteRequestInfo) { log("wrote_request err=%s", errorClass(w.Err)) }, - GotFirstResponseByte: func() { log("first_response_byte") }, - } - r := req.Clone(httptrace.WithClientTrace(context.Background(), trace)) - resp, e := client.Do(r) - if e != nil { - log("get_error=%s", errorClass(e)) - failed = true - continue - } - log("status=%d protocol=%s", resp.StatusCode, resp.Proto) - _, e = io.Copy(io.Discard, io.LimitReader(resp.Body, 1<<20)) - resp.Body.Close() - if e != nil || resp.StatusCode != http.StatusOK { - failed = true - log("body_error=%s", errorClass(e)) - } - } - if failed { - os.Exit(1) - } -} - -func errorClass(err error) string { - if err == nil { - return "none" - } - if errors.Is(err, context.DeadlineExceeded) { - return "deadline" - } - // Error text may contain a private URL. Emit only category and concrete type. - category := "other" - s := err.Error() - for _, k := range []string{"connection reset by peer", "x509:", "TLS handshake timeout", "connection refused", "EOF"} { - if strings.Contains(s, k) { - category = k - break - } - } - return fmt.Sprintf("%s (%T)", category, err) -} diff --git a/docs/investigations/issue-154/run-linux.py b/docs/investigations/issue-154/run-linux.py deleted file mode 100644 index 50306835d..000000000 --- a/docs/investigations/issue-154/run-linux.py +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env python3 -"""Bounded, loopback-only full-Server comparison. See the investigation report. - -Run in an isolated generic Linux container with locally built binaries in -/lab/bin and a new disposable /lab/out. No customer identities or endpoints. -""" -import http.cookiejar -import json -import os -from pathlib import Path -import secrets -import shutil -import socket -import ssl -import subprocess -import time -import urllib.error -import urllib.parse -import urllib.request - -ROOT = Path("/lab") -OUT = ROOT / "out" -BASE = {k: v for k, v in os.environ.items() - if not k.startswith(("MINIO_", "SILO_", "CONSOLE_")) - and k.lower() not in {"http_proxy", "https_proxy", "all_proxy", "no_proxy", "godebug"}} - - -def port(): - with socket.socket() as sock: - sock.bind(("127.0.0.1", 0)) - return sock.getsockname()[1] - - -def request(url, opener=None, payload=None): - headers = {"Origin": f"http://{urllib.parse.urlparse(url).netloc}"} - if payload is not None: - headers["Content-Type"] = "application/json" - req = urllib.request.Request(url, data=None if payload is None else json.dumps(payload).encode(), headers=headers) - try: - response = (opener.open if opener else urllib.request.urlopen)(req, timeout=2) - except urllib.error.HTTPError as err: - response = err - except (urllib.error.URLError, TimeoutError): - return 0, {}, b"" - with response: - return response.code, dict(response.headers), response.read(1 << 20) - - -def stop(proc): - proc.terminate() - try: - proc.wait(timeout=4) - except subprocess.TimeoutExpired: - proc.kill() - proc.wait(timeout=2) - - -class NoRedirect(urllib.request.HTTPRedirectHandler): - def redirect_request(self, req, fp, code, msg, headers, newurl): - return None - - -def login(console, ca): - jar = http.cookiejar.CookieJar() - client = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar)) - status, _, raw = request(console + "/api/v1/login", client) - details = json.loads(raw) - rules = details.get("redirectRules", []) - assert status == 200 and len(rules) == 1, (status, details) - auth_url = rules[0]["redirect"] - provider = urllib.request.build_opener(NoRedirect(), urllib.request.HTTPSHandler(context=ssl.create_default_context(cafile=str(ca)))) - status, headers, _ = request(auth_url, provider) - callback = headers.get("Location", headers.get("location", "")) - assert status == 302 and callback.startswith(console + "/oauth_callback?"), (status, callback) - values = urllib.parse.parse_qs(urllib.parse.urlparse(callback).query) - callback_status, _, _ = request(callback, client) - status, _, _ = request(console + "/api/v1/login/oauth2/auth", client, - {"code": values["code"][0], "state": values["state"][0]}) - buckets_status, _, _ = request(console + "/api/v1/buckets", client) - # Do not record cookies, codes, JWTs, or the state value. - return {"callback_status": callback_status, "login_status": status, - "buckets_status": buckets_status, "session_cookie": any(c.name == "token" for c in jar)} - - -def run(name, binary, mode="normal", debug=None, tls13=False, - expected=True, trusted=True, oidc=True, oauth=False, add=False): - d = OUT / name - (d / "certs/CAs").mkdir(parents=True, exist_ok=False) - idp = d / "idp" - idp.mkdir() - (idp / "mode").write_text(mode) - with (d / "fixture.jsonl").open("w") as events, (d / "fixture.stderr").open("w") as errors, (d / "server.log").open("w") as logs: - fixture = subprocess.Popen([str(ROOT / "bin/fixture"), "-dir", str(idp), *(["-tls13"] if tls13 else [])], env=BASE, stdout=events, stderr=errors) - server = None - try: - until = time.monotonic() + 5 - while not (idp / "url").is_file() and time.monotonic() < until: - time.sleep(.05) - assert (idp / "url").is_file(), "fixture did not initialize" - url = (idp / "url").read_text() + "/.well-known/openid-configuration" - if trusted: - shutil.copyfile(idp / "ca.pem", d / "certs/CAs/lab.pem") - sport, cport = port(), port() - address = f"127.0.0.1:{sport}" - api, console = "http://" + address, f"http://127.0.0.1:{cport}" - password = secrets.token_urlsafe(24) - env = dict(BASE, MINIO_ROOT_USER="local154", MINIO_ROOT_PASSWORD=password, MINIO_BROWSER="on") - if debug: - env["GODEBUG"] = debug - if oidc: - env.update(MINIO_IDENTITY_OPENID_CONFIG_URL=url, - MINIO_IDENTITY_OPENID_CLIENT_ID="local154", - MINIO_IDENTITY_OPENID_CLIENT_SECRET="local154-placeholder", - MINIO_IDENTITY_OPENID_REDIRECT_URI=console + "/oauth_callback") - server = subprocess.Popen([str(ROOT / "bin" / binary), "--config-dir", str(d / "config"), "--certs-dir", str(d / "certs"), "server", "--address", address, "--console-address", f"127.0.0.1:{cport}", str(d / "data")], env=env, stdout=logs, stderr=subprocess.STDOUT) - until = time.monotonic() + 15 - while time.monotonic() < until: - assert server.poll() is None, "Server exited; inspect its local log" - status, _, _ = request(api + "/minio/health/cluster") - if status == 200 and request(console)[0] == 200: - break - if not expected and (d / "server.log").read_text().count("Waiting for OpenID") >= 2: - break - time.sleep(.1) - result = {"case": name, "binary": binary, "mode": mode, "godebug": debug, "tls13": tls13, - "cluster": request(api + "/minio/health/cluster")[0], - "ready": request(api + "/minio/health/ready")[0], "console": request(console)[0]} - assert result["cluster"] == (200 if expected else 503), result - aenv = dict(BASE, LAB_SERVER=address, LAB_USER="local154", LAB_PASSWORD=password, LAB_OIDC_URL=url) - if expected: - admin = subprocess.run([str(ROOT / "bin/admin-check")], env=aenv, capture_output=True, text=True, timeout=7) - result["admin_list_ok"] = admin.returncode == 0 - assert result["admin_list_ok"], admin.stdout - curl = subprocess.run(["curl", "--cacert", str(idp / "ca.pem"), "--http2", "--max-time", "3", "-sS", "-o", "/dev/null", "-w", "%{http_code} %{http_version}", url], env=BASE, capture_output=True, text=True, timeout=5) - result["curl"] = {"exit": curl.returncode, "status_protocol": curl.stdout} - if add: - attempt = subprocess.run([str(ROOT / "bin/admin-check"), "add"], env=aenv, capture_output=True, text=True, timeout=7) - result["add_ok"] = attempt.returncode == 0 - result["add_reset"] = "connection reset by peer" in attempt.stdout - assert result["add_ok"] == binary.startswith("candidate"), result - if oauth: - result["oauth"] = login(console, idp / "ca.pem") - assert result["oauth"]["login_status"] == 204 and result["oauth"]["buckets_status"] == 200, result - for bad in ("bad-signature", "bad-audience"): - (idp / "mode").write_text(bad) - result[bad] = login(console, idp / "ca.pem") - assert result[bad]["login_status"] >= 400 and result[bad]["buckets_status"] >= 400, result - # Public handshake metadata only; no authorization parameters. - result["events"] = [json.loads(line) for line in (d / "fixture.jsonl").read_text().splitlines()] - (d / "result.json").write_text(json.dumps(result, indent=2) + "\n") - print(json.dumps({k: v for k, v in result.items() if k != "events"}), flush=True) - finally: - if server is not None: - stop(server) - stop(fixture) - - -if __name__ == "__main__": - run("old126-normal", "old-go126") - run("old127-normal", "old-go127") - run("old126-compat", "old-go126", "reject-mlkem", "tlsmlkem=0") - run("old127-compat", "old-go127", "reject-mlkem", "tlsmlkem=0", expected=False) - run("head127-compat", "head-go127", "reject-mlkem", "tlsmlkem=0", expected=False) - run("candidate127-compat-login", "candidate-go127", "reject-mlkem", "tlsmlkem=0", oauth=True) - run("candidate127-no-optout", "candidate-go127", "reject-mlkem", expected=False) - run("candidate127-tls13-login", "candidate-go127", tls13=True, oauth=True) - run("candidate127-untrusted", "candidate-go127", trusted=False, expected=False) - run("candidate127-mldsa", "candidate-go127", "reject-mldsa", "tlsmlkem=0", expected=False) - run("head127-add", "head-go127", "reject-mlkem", "tlsmlkem=0", oidc=False, add=True) - run("candidate127-add", "candidate-go127", "reject-mlkem", "tlsmlkem=0", oidc=False, add=True) diff --git a/docs/investigations/issue-77-current.md b/docs/investigations/issue-77-current.md deleted file mode 100644 index 2a8652463..000000000 --- a/docs/investigations/issue-77-current.md +++ /dev/null @@ -1,103 +0,0 @@ -# Issue #77 当前核验与最小修复建议 - -> 历史快照(2026-09-12,实施前)。其中的 Issue 状态、待办和方案约束只描述当时情况;最终实现、修正与验收以 [归档总记录](issue-77.md) 为准。 - -核验日期:2026-09-12。对象:[SILO #77](https://github.com/pgsty/silo/issues/77)。代码基准:远端 `main` 的 `5c576581631561c446f30ae5b566f0aa793adc1c`,在独立 detached worktree 中运行测试。用户工作目录仍位于 `12f631b50`;两者差异为 #179 的 federation 修复,不涉及本次复制元数据代码。 - -**结论:问题真实,当前主干仍未修完,应保持打开。原方案的 A/B 核心必要,但不能照搬 8 月的实施清单;已有基础设施可以复用,同时必须补上批量复制入口和相同内容的时间戳同步。** - -本次是分析与复现,没有修改产品代码、提交、发布或修改 GitHub Issue。下述测试是本地 ObjectLayer 与进程内 HTTP/RPC 复现,不是线上多站点验收。 - -## 1. 当前状态 - -GitHub API 实时返回 #77 为 `OPEN`,未分配负责人或 milestone,最后更新为 `2026-09-08T14:45:10Z`。核验时仓库没有打开的 PR。 - -| 范围 | 当前事实 | 是否仍属遗留项 | -| --- | --- | --- | -| #77-C,各站点统计 | [#91](https://github.com/pgsty/silo/pull/91) 于 8 月 29 日合并;本次现有统计回归测试通过 | 否,不应重复实现 | -| #77-A,heal 选源与输入 | 仍先用 map 首项初始化,再跳过创建默认值;Tag 远端 heal 仍不携带时间戳 | 是 | -| #77-B,源时间与删除状态 | 六类专用处理函数仍调用到达时间写入路径;部分删除状态不导出 | 是 | -| #77-D,持续不一致诊断 | 已有损坏配置日志;旧事件静默跳过、默认状态无法选源等诊断未完成 | 是 | -| Object Lock 错误 wire 字段、接管桶覆盖已有配置 | [#76](https://github.com/pgsty/silo/issues/76)、[#78](https://github.com/pgsty/silo/issues/78) 已关闭,对应 #89、#90 已合并;相关测试本次通过 | 否 | -| 元数据整记录并发写、删除后重建 | [#103](https://github.com/pgsty/silo/pull/103)、[#156](https://github.com/pgsty/silo/pull/156) 已提供桶锁和保存前物理桶检查;删除后排队写测试本次通过 | 基础已具备,但不能代替同字段时间排序 | -| 评论提及的 MRF 丢弃观测、delete-marker purge | [#152](https://github.com/pgsty/silo/issues/152)、[#153](https://github.com/pgsty/silo/issues/153) 已于 9 月 9 日随 [#162](https://github.com/pgsty/silo/pull/162) 关闭 | 不再列为 #77 的待实现项 | - -#162 明确保留“405 表示 marker 仍存在”的语义,没有采用报告人建议的“405 一律判定永久删除完成”;它也未声称复现外部报告的请求量或持续 405 风暴。本次只核验其合并/关闭状态与提交说明,没有重跑那组故障实验。 - -最新公开 Release 仍是 `RELEASE.2026-09-03T13-18-01Z`。#77 的状态不能用“已有统计修复”或“已有桶锁”推导为已修复,也不能用较晚 PR 的合并推导为已发布。 - -## 2. 已复现的真实问题 - -最直接的例子:源端 10:00 PUT 配置,10:01 DELETE;目标端积压到 10:05 才接收 PUT,并把配置时间写成 10:05。随后接收源时间为 10:01 的 DELETE,判断它比本地旧,返回 HTTP 200,却保留配置。这个例子不需要机器时钟偏差,只需要事件延迟。 - -根因在 [updateAndParse](https://github.com/pgsty/silo/blob/5c576581631561c446f30ae5b566f0aa793adc1c/cmd/bucket-metadata-sys.go#L129):它在锁内统一使用 `UTCNow()`;专用 peer handler 在调用它之前,用 getter 返回的时间做比较。 - -| 配置类型 | peer PUT 保留源时间 | 较新源 DELETE | 本地删除后旧 PUT | 删除时间在元数据导出中可见 | -| --- | --- | --- | --- | --- | -| Policy | 否 | HTTP 200,但未删除 | 会复活 | 是 | -| Tags | 否 | HTTP 200,但未删除 | 会复活 | 否 | -| SSE | 否 | HTTP 200,但未删除 | 会复活 | 否 | -| Quota | 否 | HTTP 200,但未删除 | 本次未复活,getter 保留删除时间 | 否 | -| Versioning | 否 | nil 正确地不修改配置 | 不适用 | 不应套用可删除配置规则 | -| Object Lock | 否 | nil 正确地不修改配置 | 不适用 | 不应套用可删除配置规则 | - -矩阵每项均在 `ErasureSD` 与 16 盘 `Erasure` 两个本地后端执行。六类专用事件通过实际签名 admin 路由调用,检查落盘状态;导出调用实际 `SiteReplicationMetaInfo`。不能把 Quota 与 Policy/Tags/SSE 的 getter 行为写成完全相同。 - -另外复现了以下路径: - -1. **检查与写入不在同一临界区。** 阻塞旧 Tag 事件的锁获取,在锁内提交较新 Tag,再释放旧事件;旧事件仍覆盖新内容。#103 解决跨字段丢更新,没有解决 handler 的 getter → Update 竞态。 -2. **批量元数据入口绕过逐字段排序。** 已持久化较新 Tag 后,发送较旧 bulk 事件,实际 admin 路由返回 HTTP 200,Tag 内容和时间都倒退。该入口被 [桶元数据导入](https://github.com/pgsty/silo/blob/5c576581631561c446f30ae5b566f0aa793adc1c/cmd/admin-bucket-handlers.go#L1106) 使用;[PeerBucketMetadataUpdateHandler](https://github.com/pgsty/silo/blob/5c576581631561c446f30ae5b566f0aa793adc1c/cmd/site-replication.go#L1608) 只检查桶创建时间,非 CORS 字段缺少与已有字段时间的比较。这是旧实施清单漏掉的入口。 -3. **默认值能够成为错误的 heal 来源。** 两站点状态中,一个有真实 Policy,另一个只有更晚的 `CreatedAt == PolicyUpdatedAt` 默认值。最后一次复现分别有 5/32、2/32 轮错误删除有效 Policy,取决于 Go map 遍历顺序。该计数只是复现样本,不能推断生产发生率。 -4. **远端 Tag heal 丢失源时间。** 实际 HTTP 捕获的 `UpdatedAt` 为零,见 [发送字段](https://github.com/pgsty/silo/blob/5c576581631561c446f30ae5b566f0aa793adc1c/cmd/site-replication.go#L5011)。 -5. **同内容、不同时间戳没有同步。** 即便强制 `TagMismatch=true`,内容比较仍使 heal 跳过,较旧排序时间保持不变。两站点看似内容相同,之后却可能对同一个延迟事件做出不同决定。旧方案只统一写入时间、保留全部 heal 跳过条件,仍不完整。 -6. **Quota 的 nil heal 留下缓存。** 向现有 heal 传入显式较新删除状态,磁盘 `QuotaConfigJSON` 被清空,但 `GetQuotaConfig` 仍返回 1024 字节的旧硬配额。这里显式构造了删除状态,因为当前 exporter 正在隐藏它;这是新增 tombstone 导出不能直接交给旧 heal 的具体证据。 - -从当前 `git blame` 和基线比对看,核心错误继承自 MinIO:heal 初始化逻辑来自 2022 年 `3a64580663`,专用 handler 的旧事件检查来自 2022 年 `7cc9286e0f`,bulk 入口来自 2023 年 `0cde37be50`;0806 基线已包含相关逻辑。此次没有发现其由 SILO 最近的 CORS 或统计修改引入。 - -对于使用 site replication 的部署,建议按 P1 正确性问题处理:撤销的桶策略可能保留或复活,默认加密和配额也可能与源端不一致。没有启用站点复制的正常单站点请求不触发这些复制路径;本次没有证明对象数据本体丢失。 - -## 3. 最小且必要的修复范围 - -**第一部分:收束 A 的选源和输入修复。** 提取一个小的内部选择函数,在初始化候选之前排除零时间和创建默认值;无可信候选时不写入。六类 heal 使用它,补齐 Tag 的源 `UpdatedAt`。Versioning/Object Lock 遇到 nil 保持 no-op,Quota 删除使用正确的删除路径。不能重新做 #76/#78 或把 Lifecycle、CORS 一并重构。 - -**第二部分:收束 B 的状态更新,包含本次发现的入口遗漏。** 在既有 `metadata.lock` 内完成读取原始字段时间、判旧、写入源时间和保存;普通本地写入保留现有契约。复用 `updateAndParse` 的类型分支及现有 `saveMetadata`,不再从公开 getter 获取删除时间,也不改变 getter 的 S3 错误语义。peer 更新和删除保留各自解析规则,内部返回是否应用,重复且相同的状态不重复保存。 - -本地 heal 必须使用同一排序路径,不能选好源后调用普通 `Update/Delete` 再生成到达时间。批量复制入口也必须在其现有整记录锁内逐字段判旧;bulk 的 nil 继续表示“未提供此字段”,不能改成批量删除。heal 需要同步较新的时间戳,即便内容已经一致;这不要求重写 #91 的计数或增加公开 API。 - -当前 `saveMetadata` 已在锁内检查物理桶存在,桶删除也使用同一把锁。8 月方案里另加一套 `peer-require-existing` 防幽灵桶机制已没有必要,应保留并复用现有保护。确有 `Created == 0` 的历史桶如何补齐创建时间仍应覆盖,但不能退化当前所有写入都执行的存在性检查。 - -**第三部分:单独启用删除时间导出,补足轻量诊断。** Policy 已导出时间;实际新增的是 Tags/SSE/Quota 的 nil payload 时间。先使接收与 heal 能正确处理删除,再开放这些状态。混合旧版 SILO 的风险已经有 Quota 缓存复现支撑;这是产品自身的滚动升级问题,不是要求兼容未经修改的上游 MinIO。 - -可以使用仅针对这项导出的明确 opt-in:默认关闭新增导出,全站点升级到具备 A/B 修复的版本后启用。若项目选择自动能力协商,可后续单独实现;不必为了它阻塞没有新增 wire 字段的选源与接收端修复,也不需要为本 Issue 建一个通用能力框架。具体开关尚未实现,不应把这里的建议当成现有配置。 - -诊断只需解释“过旧事件”“早于桶创建”“只有默认/未知候选”等跳过原因,使用现有有界去重/限频设施,保持 RPC 的既有成功语义。不需要另建重试队列、每轮日志或大型监控系统。 - -有三项边界必须在实现中明确: - -- 旧版 Tag heal 会发送零时间。新实现不能无说明地全部拒绝;可保留明确的兼容降级路径,在旧节点存在时不承诺完整排序收敛。 -- 相同时间戳、不同内容的冲突:只给 heal 增加 deployment ID 平局规则,并不能让到达顺序不同的 peer apply 本身确定。若承诺此类冲突也收敛,apply/heal 应共用一个类型内比较规则;可参考 CORS 的删除优先和载荷排序,不需要新增持久化源站 ID。若不纳入本轮,必须列为剩余边界,不能称全量收敛审计完成。 -- 旧版本已经写坏的到达时间无法从现有记录还原。升级不会自动恢复历史操作顺序;应由操作者确认权威状态,并在升级和时钟检查后重新提交相关配置/删除,再验证各站点。 - -因此,建议交付为有限的选源修复、统一源时间更新、删除导出与诊断三个可审核部分。已有锁、物理桶保护和 C 的计数修复直接复用。预期产品修改集中在 Server 的 site-replication 与 bucket-metadata 路径,无须为核心修复升级 Console/mcli/silo-pkg、改存储格式或重写复制架构。 - -## 4. 验证证据与关闭条件 - -本次最后一轮执行: - -```text -GOWORK=off go test -tags kqueue,dev ./cmd \ - -run 'TestIssue77Current|TestSiteReplicationStatusAccountsPerSiteAndSurvivesMalformedConfig|TestPeerBucketObjectLockMetadata|TestPeerBucketAdoption|TestQueuedMetadataUpdateAfterDelete' \ - -count=1 -timeout=5m -v -``` - -四组审计复现测试包含 24 个后端/场景子测试,均暴露预期的现存缺陷;八个现有回归测试通过。总命令退出码为 1,原因是上述针对期望正确行为的断言失败,不代表修复验收通过。未运行全量测试、race suite、真实多站点中断/重启或滚动升级实验。 - -保留的证据: - -- [完整运行日志](issue-77/current-tests.log) -- [源时间、删除和 bulk 审计测试](issue-77/issue77_review_test.go.txt) -- [heal 与锁竞态审计测试](issue-77/issue77_heal_review_test.go.txt) - -测试以 `.go.txt` 保存,避免将故意失败的审计用例加入正常 Go 测试集。可在上述 SHA 的独立 worktree 中复制为 `cmd/issue77_review_test.go` 和 `cmd/issue77_heal_review_test.go` 后重跑。创建默认值测试使用重复 map 遍历来观测缺陷,正式回归应在抽出选择函数后改成确定性用例。 - -关闭 #77 前需要:上述失败场景转为正确行为;分别覆盖六类配置、落盘重载、漏发/重复/乱序、同字段竞态、同内容新时间、批量导入入口、legacy 桶、删除后的桶及混合版本。全站点升级后的实际断线重连/重启验证应保留独立证据。当前结论是“缺陷与实施范围已核实”,不是“修复完成”。 diff --git a/docs/investigations/issue-77-plan.md b/docs/investigations/issue-77-plan.md deleted file mode 100644 index dab248f42..000000000 --- a/docs/investigations/issue-77-plan.md +++ /dev/null @@ -1,123 +0,0 @@ -# Issue #77 最小充分修复计划 - -> 历史快照(2026-09-12,实施前)。其中的 Issue 状态、待办和方案约束只描述当时情况;最终实现、修正与验收以 [归档总记录](issue-77.md) 为准。 - -版本:v4 定稿,2026-09-12。状态:作者复核完成,Claude Code `claude-opus-5 --effort max` 四轮实际评审后,最终结论为 **GO_WITH_NONBLOCKING_NOTES,实施前阻断 0**。非阻断说明已落实到本计划,见 [最终评审汇总](issue-77/review/final-review.md)。 - -基准:`pgsty/silo` main `5c576581631561c446f30ae5b566f0aa793adc1c`。本轮交付是方案与评审,不表示已经实现、合并或发布。问题证据见 [当前核验](issue-77-current.md),意见处置见 [首轮处置](issue-77/review/decisions-v2.md)、[第二轮处置](issue-77/review/decisions-v3.md)、[接管边界补查](issue-77/review/decisions-v4.md)。 - -## 目标与边界 - -修复 Policy、Tags、SSE、Quota、Versioning、Object Lock 六类桶配置的源时间丢失、锁外判旧、heal 错选源和删除传播不完整。覆盖普通本地写入、专用 peer 事件、bulk/import、local/remote heal、元数据导出与初次同步。 - -在修复 PGSTY 栈、同一已知桶世代、合法带源时间事件的范围内,使重复、乱序和漏发后的状态能够确定性收敛。开关关闭期间新增删除信息不可见,不承诺完整删除收敛;无时间旧事件、旧版污染时间和桶创建世代冲突需要单独解释,不能自动推断历史真相。 - -不重做 #91 的计数、#76 的 Object Lock wire 修复、#78 的桶接管、#103/#156 的锁与删除保护。不改变 CORS、Lifecycle/expiry、notification、对象复制、MRF、resync、IAM 的语义;不改存储 schema、SDK、Console、mcli 或 silo-pkg,不新建能力协商、复制框架、锁或重试系统。 - -## 行为契约 - -### 1. 明确事件、缺省值与删除 - -| 类型 | 专用事件 | bulk 字段未提供 | bulk 字段明确提供 | -| --- | --- | --- | --- | -| Policy | nil 为删除;沿用现有解析器 `IsEmpty()` 为删除 | 保留 | 非空 RawMessage 按现有解析器处理;语义空策略归一为删除 | -| Quota | nil 为删除;非 nil 按现有 quota 解析器处理 | 保留 | 非空 RawMessage 按现有解析器处理,零 quota 仍是 live 文档 | -| Tags / SSE | nil 或 base64 解码后空内容为删除 | 保留 | 空字符串为删除,其他内容按原规则解码、校验 | -| Versioning / Object Lock | nil/空内容为 no-op | 保留 | nil/空内容仍为 no-op,不能清空配置 | - -“未提供”必须依据真实 wire 类型判定:Policy/Quota 的 `json.RawMessage` 为 nil 或空切片时经 `omitempty` 省略;**显式 JSON `null` 解码后是非 nil 的 `[]byte("null")`**,不能与缺省混淆。按现有解析器,Policy `null` 是语义空策略,Quota `null` 是零值 quota 文档。`*string` 类型的 JSON `null` 则解码为 nil。不得使用统一的 `len(payload)==0 => 删除` 来处理 bulk。 - -合法 `{}`/零值 quota 保持 live;取消普通 quota PUT 出站时“零配额改写为 nil”的逻辑,保存与发送同一个语义状态。Policy 保留现有专用 peer 的“空策略=删除”解释,本地 PUT、导入和 bulk 统一归一为同样的删除状态;这是需要写入兼容说明的小范围变化:空策略本地立即按删除处理,GET 返回既有 NotFound 行为,不再先保留空文档、等复制后才被清除。 - -**同一次操作的落盘状态与出站事件,经同一归一规则后,必须具有相同的 `(kind, payload key, source time)`。** JSON 的无意义编码次序不要求字节相同;Object Lock 改写后的有效 Versioning 文档必须来自提交结果。 - -### 2. 统一状态与排序 - -内部仅需一个小状态表示:baseline / live / tombstone,以及比较键和字段源时间。baseline 可携带历史有效配置,但没有真实修改时间;它绝不能被当成删除。复用 CORS 已有设计思路,不改 CORS 本身或扩展为通用框架。 - -- 使用 `BucketMetadata` 原始字段时间;字段时间为零时在比较视图中补为 Created,与 `defaultTimestamps()` 一致,不借用会隐藏墓碑时间的 getter 或整记录 `lastUpdate()`。 -- 在已知 Created 下,零字段时间回退后等于 Created 的状态是 baseline:有有效非空配置的 baseline-live 可作为初始化候选;空 baseline 只是缺省值,不能作为删除或 heal 来源。真正严格早于自身 Created 的字段不是候选;Versioning/Object Lock 的空候选无论时间如何都不参与选源。真实 live/tombstone 的时间必须大于 Created。 -- 专用 peer 的零时间保留兼容例外:锁内按本地新操作分配时间并限频记录 legacy-zero;不受删除传播开关影响,不在源时间排序保证之内。bulk 零时间仍按现状拒绝。 -- 非零事件**严格早于**目标桶 Created 才成功/no-op 并记录 before-created,heal 对这种目标跳过。等于目标 Created 的 live 事件可更新仍是 baseline 的目标字段,时间仍保存为源 Created,不盖上到达时间;同时间的空/nil 只算空 baseline,不能删除配置。不同桶世代不能自动合并,需要运维处理,不纳入收敛承诺。 -- 排序先比较是否为真实状态:任何真实 live/tombstone 都胜 baseline,不能让较晚创建的默认值压过较早的真实修改。真实状态之间再比较源时间,同时间 tombstone 胜 live、live/live 的**稳定比较键字节序较大者胜**。baseline-live 只胜空 baseline,或在 baseline-live 之间按同一稳定键较大者胜;永远不能覆盖真实 live/tombstone。所有候选都是 baseline-live 时仍可确定性初始化并收敛;全部为空 baseline 才安静 found=false。键、状态级别和源时间相同为 no-op。deployment ID 不参与上述比较,也不作为新字段保存。 -- Quota 使用现有解析结果的 JSON 编码作为比较键。Policy 在 Server 内生成确定性比较表示:既有解析器校验/去重后,对其**完整 JSON 树**的对象键和集合数组递归排序,统一覆盖 Statement、Action/NotAction、Resource/NotResource、Principal、Condition;保留数字类型和精度。比较键必须是已解析策略的纯函数,同一配置从两个站点分别解析也必须得到相同键。普通 `json.Marshal(BucketPolicy)` 不稳定,不能直接作键;不增加依赖不支持的 NotPrincipal 语法,不改 wire/schema。新状态比较仅使用这一套键:不把忽略 Sid 且对 Statement 顺序敏感的 `BucketPolicy.Equals` 再叠加为另一套判等规则,既有公开统计对 Equals 的使用保持不变。 -- XML 使用有效文档字节,保留大小写和实际内容;Versioning 先应用下述现有 Object Lock 约束。比较器不能依靠字节序方向来补偿保存阶段的隐式改写。 - -### 3. 先得到有效状态,再比较与提交 - -原始读取、类型处理、比较、修改及保存均在现有 `metadata.lock` 内。内部入口必须传递现有 lock context,避免 legacy migration 再次取锁。 - -- 提取并复用 `parseAllConfigs` 已有的 Object Lock→Enabled Versioning 归一规则,使比较视图和真正保存一致;不得扩大 suspend、prefix exclusion、retention 的限制。bulk 先确定实际接受的 Object Lock,再比较该约束下的 Versioning,并在最终提交前应用同一规则。 -- 以归一化后的有效 `(kind, key, time)` 判定变化;更新了时间也算变化。完全重复不保存、不通知;一次 bulk 校验失败不保存部分结果;成功至多保存一次,解锁后通知。 -- 可删除字段清空必须基于现有 parse=false 的新加载对象,不能在已经解析且仍持有旧 quota 的对象上执行 Update(nil)。bulk 保留原始读取再解析保存的方式。不要顺手修改 Quota getter 或所有 `parseAllConfigs` 空分支。 -- 保存函数必须让需要发送 hook 的调用方拿到**本次提交的最终快照**,不能先解锁再读取“最新”状态拼接旧时间。最小做法是让内部 `saveMetadata` 接收元数据指针并回写 Save 的归一化结果,机械更新现有少量调用;对外 `Update/Delete` 签名不变,新增内部提交结果仅供需要该快照的本地 handler/import 使用。不得原地修改已发布到缓存的引用字段。 -- 保留现有物理桶存在检查、删除锁序、迁移、后台通知上下文。真实历史桶 Created 为零时仅走少见的物理桶 Created 补齐路径;物理桶缺失返回现有错误;创建时间仍未知则不伪造到达时间,报告 indeterminate。 - -## 三个实现提交 - -### 提交 1:原子 apply、发送一致性与本地时间 - -主要文件:`cmd/bucket-metadata-sys.go`、`cmd/bucket-metadata.go`、`cmd/site-replication.go`、`cmd/admin-bucket-handlers.go`,以及实际需要提交快照的本地配置 handler。 - -1. 在现有 update/delete 内部路径增加源时间、状态比较和提交结果;公开签名不变。六个 peer handler 移除锁外 getter 判旧,锁内持久化原始源时间。保留 Object Lock 的 legacy Tags 字段载荷回退。 -2. bulk 对明确提供的六类字段在已有锁内逐字段比较,再原子保存;未提供字段不动,不能循环调用会重入锁的公开 handler。保留 CORS 独立分支与既有行为。 -3. 六类本地实际写入在锁内分配 `max(UTCNow(), Created+1ns, 当前字段时间+1ns)`。其他类型不变。`enablePeerBucketVersioning` 的实际变更也使用它,只有缺失配置的创建 bootstrap 继续 Created 默认值。 - 接管已有桶时保留原 Created,再执行现有 `SetCreatedAt`;如果 Created 改变,仅将这六类中原本为零或等于原 Created 的默认字段时间调整到新 Created,随后再做既有 versioning/lock bootstrap。原本晚于旧 Created 的真实修改/删除时间及其配置保持不变。不能仅凭 payload 为 nil 判断默认值。该小分支防止 Created 前移时默认值变成假墓碑、后移时历史初值被误判为无效,复用已有接管锁,不重做 #78 的配置保护。代码注释明确限定六类的原因;本轮不改新比较器未读取的其它配置时间。 -4. quota 本地 PUT 保留零值文档并原样表示该语义;Policy 空策略本地与 peer 一致走删除。需要归一化的本地 handler 从本次提交快照生成 hook;其他内容不发生归一变化的路径可保留既有编码,但必须满足三元组一致性。 -5. 导入在每桶最终提交锁内,为本次涉及的六类字段生成共同 commitAt,严格大于 Created 和这些字段当前时间且不早于锁内现在。该时间同时用于落盘和 bulk hook,不能沿用 ZIP 开始时间。bulk hook 从最终提交快照构建;若导入的空 Policy 已归一成删除,另外使用现有专用 Policy nil 事件表达它,不能因 `omitempty` 漏发。未导入字段不改,Object Lock 的既有派生 Versioning 修正保留原时间语义;CORS 继续独立时间/事件,其他字段不参与该上界。 - -完成条件:六类源时间落盘;旧事件不能越过锁覆盖新状态;四类删除不会被旧 PUT 复活;真实 wire、落盘和出站状态一致;重复无写入;bulk 与 import 没有绕过排序或静默遗漏删除。 - -### 提交 2:heal 选源与应用同规则 - -主要文件:`cmd/site-replication.go`。 - -1. 先过滤空 baseline、无效来源、严格早于自身 Created 的字段和 update-only 空配置,再按上述强弱排序选最大状态;无候选必须显式返回 found=false。保留历史 baseline-live 的初次同步和 heal 能力,消除六处“先 seed map 首项,再过滤默认值”的写法。 -2. 选源和目标遍历都跳过 `info.Sites` 中不存在的 deployment ID,包括不可达站点的空 ID 占位项;单一 peer 失败记录后继续其它目标,不因 map 顺序放弃健康站点。不改变状态计数或新建重试机制。 -3. 本地 heal 使用提交 1 的源时间 update/delete;远端仍用原有逐类型 RPC,全部携带源时间,补齐 Tag 的 UpdatedAt。 -4. 比较完整有效状态,去掉公开 mismatch/payload-only 对写入的门控;同内容较旧时间也同步。对已归一且相同的目标不写入、不发 RPC。Versioning 比较使用与该站点 Object Lock 一致的有效文档;全站点 Lock 状态补齐后不再因旧原始文档产生空转。 -5. 保留 #91 的计数与公开字段;创建世代冲突、无可用来源通过有限诊断解释。已知 baseline 且各站点无实质差异时安静 no-op。 - -完成条件:map 顺序不影响结果;默认空值不再删真配置;历史 baseline-live 可以初始化新站点、不能覆盖真实状态;同内容不同时间、同时间冲突最终一致;Tag 时间完整;Quota 删除后磁盘、缓存、重载一致;不可达占位项不阻断健康目标;完整状态已可见且稳定时,第二轮 heal 无写入/广播。 - -### 提交 3:新增删除传播与有界诊断 - -只增加一个启动开关,暂定 `MINIO_SITE_REPLICATION_METADATA_TOMBSTONES=off/on`,默认 off;实现沿用现有 env 开关写法,不做能力协商。 - -| 行为 | off:升级阶段 | on:所有参与节点修复后 | -| --- | --- | --- | -| 带时间 peer apply、锁内排序与 heal | 使用提交 1/2 | 同左 | -| 专用事件零时间 | 兼容应用并记录 legacy-zero | 同左,不新增协议拒绝 | -| Tags/SSE/Quota nil payload 时间导出 | 保留旧版条件导出 | 导出 `time > Created` 的真实删除时间 | -| Policy 时间导出 | 保留已有行为 | 保留已有行为 | -| 初次同步的真实删除 | 保留已有行为 | Policy/Tags/SSE/Quota 都发送专用 nil + source time 事件 | - -开关只控制**新增**删除信息的导出/初次发送,普通本地删除事件照常复制。**off 不等于禁止删除传播:Policy 墓碑原本已导出,修复后在 off 下也照常参与 heal;Tags/SSE/Quota 的新增墓碑信息才被门控。** off 期间这些字段的隐藏墓碑会使 heal 继续尝试过时 RPC,由接收端排序拒绝;这是状态不可见时的已知代价,不承诺第二轮零 RPC,也不为这种正常拒绝增加每轮日志。 - -开关不检测或证明远端能力。启用条件是所有参与站点的全部节点已经修复,同一站点配置一致,旧请求排空;旧节点仍在线时保持 off。此隔离有实证依据:旧版接到新增 Quota heal 墓碑会留下已解析缓存残留。 - -日志仅保留三个实际原因:legacy-zero、before-created、indeterminate(未知创建时间、缺失/不可达来源或有实际差异却无可用候选)。精确重复、正常旧事件和成功裁决的同时间冲突不记警告。复用 `LogOnceIf`,以稳定的桶/字段/原因作为 key,**错误正文也必须稳定**;变化的时间与 peer 详情放入日志 ReqInfo,沿用现有每小时清理,不新增限流框架、不输出完整策略。 - -Server 文档解释启用顺序和回滚:降级前所有修复节点先关开关,然后滚动降级;旧软件缺陷会恢复。点名旧版 Tag heal 无 UpdatedAt 的来源。旧版到达时间污染、legacy-zero 产生的新本地时间以及创建世代分歧无法自动还原;操作者查看状态后在权威站点重新提交需要纠正的配置/删除。历史世代冲突先处理桶身份,不能靠任意站点强刷绕过创建保护。 - -## 最小验收矩阵 - -| 组 | 必须覆盖 | 证据方式 | -| --- | --- | --- | -| T1 | 六类 PUT 源时间;四类 DELETE;旧事件不回退;重复无写入;零 quota 三元组一致;带合法 Version 的空 Policy PUT 成功、GET NotFound、专用删除事件与落盘一致 | 真实 admin/S3 路由、ErasureSD/Erasure16、磁盘重载、RPC 捕获 | -| T2 | 同时间两种到达顺序结果相同、删除优先;Policy 多集合及 NotAction/NotResource/Condition 反复编码与排列后,两站点独立解析得到相同键;Sid 差异及大整数不被错误合并 | 确定性比较器测试与代表性真实 handler | -| T3 | Versioning/Object Lock nil/空 no-op;legacy Tags 载荷回退;Object Lock + prefix exclusion/ExcludeFolders 在普通写、peer、bulk/import 后有效状态一致,第二轮 heal 无额外写入 | 原有 #76/#78 回归加针对性用例 | -| T4 | 锁前旧事件排队、较新写先提交后旧事件不得覆盖;不同字段并发均保留 | 复用已有 `lockBucketMetadataAcquireHook` / RMW 屏障、两种 ObjectLayer、目标 race | -| T5 | bulk 新/旧/缺省字段混合;真实 JSON 编解码的 nil、空 RawMessage、显式 null、空字符串、空策略、零 quota;非法字段不部分保存;只导入 tags 不修改 Policy/Quota | 真实 bulk 路由、缓存与磁盘 | -| T6 | 本地时间胜过已有未来时间;相邻提交不倒序;ZIP 导入期间插入写入,最终落盘和发出事件的状态与时间一致,空 Policy 删除不漏发 | 本地 API、import 路由与 RPC 捕获 | -| T7 | map 排列、空 baseline/全无候选;baseline-live 初始化与同级冲突收敛,但不能覆盖真实 live/tombstone;nil@Created 不删除配置;空 update-only;Tag 时间、Quota 清缓存、空 ID、世代冲突 | 确定性 heal 本地/远端用例 | -| T8 | 墓碑经保存/缓存失效/重启仍有效;缺桶、排队写入、零 Created;历史字段时间等于 Created 的桶经初次同步、一轮 heal 后一致,第二轮无写入/广播;接管 Created 前移/后移/不变时默认时间仍是 baseline,真实 PUT/DELETE 时间不变;on/off 与 legacy-zero,off 下 Policy 仍 heal、其它隐藏墓碑允许被拒 RPC 但无每轮警告 | ObjectLayer 与进程内旧版 wire/SRInfo 模拟;复用既有删除/迁移回归 | -| T9 | 修复版双站点短暂断线、漏发/重复/乱序后六类合法配置收敛;删除传播启用后第二轮稳定无写入;日志确实有界 | 隔离双站点实验,不把未知桶世代或零时间事件算成通过 | - -固定旧版与修复版混合进程仅作一次性升级冒烟,不新增为长期提交门槛。将已有审计用例改成正式确定性回归,不能把遍历 map 的概率复现直接提交。先记录未修复失败,再验证通过;复用原有 Object Lock、adoption、metadata-lock、计数、CORS 回归,并在实现后运行目标 race、仓库必需检查和完整 Go CI。方案审查、局部测试、双站点结果、合并与发布是不同证据。 - -## 作者复核结论 - -三个提交分别处理写入正确性、heal 收敛和新增删除信息的升级边界;每项对应已证实缺陷或本次修复直接触及的实际路径。缩减了拒绝零时间的新协议行为、混合版本长期测试门槛和新锁屏障;保留已有持久化、通知与缓存语义。 - -Opus 第二轮已确认首轮 R1/R2/R3 实质关闭,第三轮确认历史桶初始化边界,第四轮确认接管默认时间修正必要、充分且未扩大范围;最后两轮均为 0 阻断。四轮原文、版本快照、逐项处置与模型调用证据均保留。方案可以进入实现;当前只完成方案和诊断,仍须通过上述实现期验证,不能据此认定问题已修复或可关闭。 diff --git a/docs/investigations/issue-77.md b/docs/investigations/issue-77.md deleted file mode 100644 index 7ee9c1aa1..000000000 --- a/docs/investigations/issue-77.md +++ /dev/null @@ -1,85 +0,0 @@ -# #77:桶配置复制修复与验收归档 - -归档日期:2026-09-12。对象为 [SILO #77](https://github.com/pgsty/silo/issues/77)。问题真实;来源时间、删除状态和 heal 选源共同决定是否收敛,不能只补一个删除分支。最终实际 Claude Code Opus 5 Max 实现审查结论为 **GO_WITH_NONBLOCKING_NOTES,阻断 0**,完整 cmd 和最终 lint 随后通过。 - -本文固定研究与验收时的事实。合并状态以 Issue 关联 PR 为准;主干包含代码不等于镜像、软件包或生产部署已经更新。研究叙述见伴生站的[中文设计记录](https://github.com/pgsty/silo.pgsty.com/blob/main/content/blog/design/bucket-metadata-convergence.zh.md)和[英文设计记录](https://github.com/pgsty/silo.pgsty.com/blob/main/content/blog/design/bucket-metadata-convergence.md),操作契约见 [Server site-replication README](../site-replication/README.md)。 - -## 实现为什么最小、必要且足够 - -源端 10:00 PUT、10:01 DELETE,目标到 10:05 才收到 PUT。旧实现把字段时间写成 10:05,随后把源时间为 10:01 的删除当作旧事件跳过,返回成功但保留配置。删除后没有导出时间的字段,在漏发后还会被对端旧值恢复。另有锁外判旧、批量入口绕过排序、默认值抢占来源、Tags heal 丢失时间和同内容不更新时间等独立入口。 - -| 必要改动 | 少了它会发生什么 | 复用的边界 | -| --- | --- | --- | -| 在现有整桶锁内读取、比较并保存来源状态 | 锁外判旧仍可覆盖并发的新状态;到达时间继续污染排序 | 既有 `.metadata.bin`、`metadata.lock` 和物理桶存在检查 | -| Policy、Tags、SSE、Quota、Versioning、Object Lock 共用确定性比较 | 等时冲突仍依赖到达顺序;heal 与接收端可能选出不同结果 | 既有载荷、字段时间及 Created,不增加 wire 或持久化字段 | -| 专用事件、bulk、导入、本地写和 heal 都使用提交后的状态 | 只改一个 handler 会留下旁路;归一化后的 Versioning/Quota 与出站事件可能不同 | 既有解析、保存与复制钩子,bulk 仍是一次原子保存 | -| 删除导出开关默认关闭 | 直接对旧版节点导出新增删除状态有已复现的 Quota 缓存风险 | 全部参与节点升级后统一启用,不引入能力协商框架 | -| 物理 Created 恢复与初次同步传播 | 历史无创建时间的真实桶会失去六类配置写入能力 | 现有物理探测;目录 mtime 只是近似值,不推断真实桶世代 | -| Policy 状态键与 heal 一致;异常按原因去重 | 永久假 mismatch 无法修复;正常空基线噪声或无来源异常静默 | 既有每站点统计与 logger,不增加后台协调系统 | - -真实状态优先于创建基线;真实状态按来源时间、等时删除优先、规范内容键排序。历史 `live@Created` 可以初始化空目标,但空基线不能删除真实配置。本地纠正时间在锁内取 `max(now, Created+1ns, fieldTime+1ns)`;带时间 peer 事件保留来源时间。Versioning/Object Lock 的 nil 保持 no-op,Quota `null`/`{}` 保持零配额配置,空 Policy 归一为删除。 - -Policy 编码器保留在 GET/export/peer 是必要的:既有解析器可以保存的负集合策略必须能读回和复制。PUT/import 继续使用同一编码器,避免额外表示分支。没有为了本轮重写 CORS、Lifecycle、对象复制、IAM 或 #91 已完成的计数,也没有修改 SDK、模块依赖或升级协议。 - -充分性限定在同一桶世代、有效且可排序的来源状态、全部参与节点升级并开启删除导出的范围内。历史时间污染、零时间兼容事件和桶身份冲突不属于自动恢复承诺。 - -## 计划与实际对抗审查 - -- [实施前核验与失败复现](issue-77-current.md)、[最终 v4 计划](issue-77-plan.md)。这些文件保留历史状态,不能当成当前待办。 -- [四轮计划审查与意见处置](issue-77/review/final-review.md):前两轮 NO_GO,后两轮零阻断;保留各版计划、完整最终意见、调用元数据和必要探针。 -- 三轮实现审查均使用实际 Claude Code `claude-opus-5 --effort max`。模型身份取自实际 assistant 消息,effort 取自显式调用参数。源码在独立快照中审查,审查者读代码和执行结果,没有代为运行这些验收。 - -| 实现审查 | 固定源码 | 结论与处置 | -| --- | --- | --- | -| [首轮完整意见](issue-77/implementation-review/opus5-max-review.md) / [调用记录](issue-77/implementation-review/session.json) | `4089113e3` | GO_WITH_NONBLOCKING_NOTES,条件性阻断 F1;物理 Created、Policy 假 mismatch、诊断和证据缺口随后修复 | -| [第二轮意见](issue-77/implementation-review/round-2/opus5-max-review.md) / [调用记录](issue-77/implementation-review/round-2/session.json) | `62cf066ff` | 条件性和无条件阻断均为 0;修正首轮对 Policy 编码器的过宽质疑,进一步补齐无来源诊断和初次同步回归 | -| [最终定向意见](issue-77/implementation-review/round-3/opus5-max-review.md) / [调用记录](issue-77/implementation-review/round-3/session.json) | `fcbb93e89` | 阻断 0,确认后续 `461e9a721` 的测试改写等价;读取时尚在运行的 cmd/lint 后续均退出 0 | - -最初实现审查 SHA `4089113e3` 补签 DCO 后对应 `1ee64a8d8`,两者树完全相同。生产代码最终固定在 `fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025`;验收源码 HEAD 为 `461e9a721047c63e1a95f54ad4b533a6b89def30`,只在两个测试文件有格式和等价条件改写,见 [tree 对照](issue-77/implementation-review/round-3/final-tree-equivalence.json)与 [patch](issue-77/implementation-review/round-3/test-style.diff)。本次归档不改变生产代码或正式回归测试。 - -审查原文保留当时的判断,不将后续作者修正倒写成评审者已经观察到的结果。首轮误读 README 中既有的空 Policy / 零 Quota 说明,第二轮修正编码器必要性的判断;最终处置以本文和伴生站的逐项表为准。 - -## 验收证据 - -| 检查 | 执行对象与结果 | -| --- | --- | -| [完整 cmd](issue-77/implementation-review/round-3/final-cmd.log) / [命令及退出码](issue-77/implementation-review/round-3/final-cmd.json) | `fcbb93e89`,CGO=0,全部通过,包测试 492.776 秒 | -| [最终目标 race](issue-77/implementation-review/round-3/final-target-race.log) / [命令](issue-77/implementation-review/round-3/final-target-race.json) | `fcbb93e89`,真实创建时间、诊断、初次同步、接管、Policy 状态和全部 CORS 命名用例通过 | -| [测试改写后的 race](issue-77/implementation-review/round-3/final-style-target-race.log) | `461e9a721`,受影响用例通过 | -| [build](issue-77/implementation-review/round-3/final-build.json)、[vet](issue-77/implementation-review/round-3/final-vet.json)、[lint](issue-77/implementation-review/round-3/final-lint.json) | 最终生产树 build/vet 通过,`461e9a721` lint 零问题;可选 typos 未安装,按 Makefile 跳过 | -| internal、S3 Select race、生成文件、兼容检查、六平台编译 | `62cf066ff` 的 `ci-*-after.log` 作为较早阶段的补充证据,不冒充最终树在全部平台运行通过;归档中的空生成日志本身不证明退出状态 | -| [两个真实站点进程](issue-77/implementation-review/round-3/final-runtime.log) / [执行元数据](issue-77/implementation-review/round-3/final-runtime.json) | 干净 `fcbb93e89` 构建:六类历史配置、真实漏发恢复、乱序、四类删除跨重启、两次各 65 秒零 metadata RPC、异常去重、gate=off 与固定旧版的 PUT/DELETE 冒烟均通过 | -| [伴生站构建](issue-77/implementation-review/round-3/docs-check.log) | 提交为 `9fa6248` 的文章内容通过 Hugo 严格构建和站内链接检查,EN 1207 / ZH 1219 页;后续合并状态文案单独检查 | - -[归档清单](issue-77/archive-manifest.json)保存每份材料的原始和归档后 SHA-256,以及全部改动源码的 SHA-256。[二进制身份](issue-77/implementation-review/round-3/binary-identity.json)保留实际版本、Go 构建身份和摘要:最终运行二进制来自干净 `fcbb93e89`,SHA-256 为 `4825a801ce0ac48d636d9428ce4cd5c17a20b6cfec0b569de70a124c9c005049`;旧版来自干净基线 `5c5765816`。第三轮审查的 cmd/lint 条件由上述已完成的 JSON 关闭。 - -### 反向复现与排除的证据 - -- [confirmed-before.log](issue-77/implementation-review/round-2/confirmed-before.log):正式测试叠加 `1ee64a8d8` 的 `erasure-server-pool.go` / `site-replication.go`,真实 ObjectLayer 创建时间恢复及旧 Policy 顺序失败;[修复后](issue-77/implementation-review/round-2/confirmed-after.log)和 [race](issue-77/implementation-review/round-2/confirmed-after-race.log)通过。 -- [confirmed-diagnostics-before.log](issue-77/implementation-review/round-2/confirmed-diagnostics-before.log):叠加旧诊断路径,普通空基线误报。第一处断言已经终止测试,不能声称它同时证明后面的 Warning 级别和去重断言在旧码失败。 -- [no-source-before.log](issue-77/implementation-review/round-2/no-source-before.log):直接运行 `62cf066ff`,三种已有无效状态在没有有效来源时缺少诊断;这一次不是旧码 overlay。 -- [initial-sync-before.log](issue-77/implementation-review/round-2/initial-sync-before.log):保留物理恢复修复,仅叠加 `1ee64a8d8` 的 `site-replication.go`,首次同步出站仍传零 Created。该单测的 peer 只确认 RPC,不证明真实对端或本地 peer 分支已经落盘。 - -`findings-before.log` / `findings-after-1.log` 是早期夹具失败,不能证明产品缺陷;过渡阶段的旧 helper 遗留引用和测试格式失败也不能当成通过记录。这些草稿不进入本归档的验收证据。另一次 Codex 调用因使用限额失败,不计入完成审查次数。 - -### 重跑真实双站点验收 - -[独立 Go 驱动](issue-77/runtime/main.go)与原执行内容相同,仅添加 `ignore` 构建标记及说明,避免进入普通包测试。它只启动回环地址上的一次性实验实例,每站四个数据目录,使用文件内声明的专用实验凭据。给它一个全新目录和自行从固定提交构建的两个二进制: - -```sh -go run docs/investigations/issue-77/runtime/main.go \ - /absolute/new-lab-directory /absolute/silo-fixed /absolute/silo-before -``` - -固定构建分别为 `fcbb93e89` 和 `5c5765816`,可在独立干净 worktree 使用 `CGO_ENABLED=0 go build -trimpath`;记录 `--version`、`go version -m` 和 SHA-256。省略旧版二进制参数会跳过混合版本冒烟,不能将其报告为执行过。最终删除收敛快照见 [converged.json](issue-77/runtime/converged.json),数组顺序由驱动的 `states` 函数定义。 - -原执行环境为 `go1.27.1 darwin/arm64`。因宿主盘可用空间比例触发存储保留阈值,完整 cmd 和进程实验使用独立 16 GiB APFS 测试卷,没有降低生产阈值。临时卷、审查 worktree 和实例进程均已清理。上述是本地可复核的执行证据,不替代 GitHub Actions、Linux 多节点集群、发布制品或生产验证。 - -## 剩余边界 - -1. `MINIO_SITE_REPLICATION_METADATA_TOMBSTONES=off` 是默认值。全部站点全部节点升级、配置一致并排空旧请求后,统一开启并重启,才具有新增 Tags/SSE/Quota 删除的漏发自愈能力。普通删除事件与原有 Policy 删除导出仍保留。 -2. 到达时间污染、零时间旧事件和桶世代冲突无法凭现有数据重建。物理 Created 只是目录 mtime 的近似值;较早事件仍跳过,由操作者核对权威状态后重新提交纠正。 -3. 日志按每桶/字段/原因去重,仍可能随桶和有值字段数量增长。本轮不增加全站日志调度。 -4. 最终审查保留三项非阻断改进:解码失败诊断中的时间可能为零;初次同步单测没有证明本地 peer 分支落盘;未来并发后台日志可能需要更强的测试隔离。生产路径已复核,本轮不为这些建议新增 helper 或 hook。 - -归档保留最终报告与调用身份,不发布原始模型推理流、二进制、实验数据卷或无效夹具日志。工作站路径、历史文档链接与非必要 JSON 字段经过整理;哈希清单区分原始产物与归档副本,不能将整理后的报告哈希冒充原文件哈希。 diff --git a/docs/investigations/issue-77/archive-manifest.json b/docs/investigations/issue-77/archive-manifest.json deleted file mode 100644 index 9d17dbee8..000000000 --- a/docs/investigations/issue-77/archive-manifest.json +++ /dev/null @@ -1,437 +0,0 @@ -{ - "archived_at": "2026-09-12", - "issue": "https://github.com/pgsty/silo/issues/77", - "base": "5c576581631561c446f30ae5b566f0aa793adc1c", - "tested_source_head": "461e9a721047c63e1a95f54ad4b533a6b89def30", - "reviewed_production_head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", - "source_sha256": { - "buildscripts/rebrand-guard/compat-baseline.json": "7ea6485008dd998373144ebded82483fe2c33ae60712abcf2fba973a7506b3e1", - "cmd/admin-bucket-handlers.go": "3aa8fc80cb8f2c4725acb8fbec026d47cd71a60ad58d06f905aa013540b30db7", - "cmd/bucket-cors-site-replication_test.go": "2cff52d3857e912526aaae40736b077e6b8bd640e098934b8af0ec76354f3747", - "cmd/bucket-metadata-replication.go": "07730f6a19b78a9b51dd9d56696812299b3aedce1a779b23178643e2684ba686", - "cmd/bucket-metadata-sys.go": "6d0936f537f104bdae1c460554c5ef6f34e6f057d8eb16569a23f012a9498d60", - "cmd/bucket-metadata.go": "2b692e42df36373fb98a74792098e1251ae886f5e2759add91a00909b596d04c", - "cmd/bucket-policy-handlers.go": "1ac487ef97f63ab19358212f1d582659b4d6fbd17888f5bb21cd5e5028e783e9", - "cmd/bucket-versioning-handler.go": "23ec85d45bacf687ada30ea6a06cb5e6d5d5dd32a81865a24aa52a030759e3ce", - "cmd/common-main.go": "c06df6c3051ceb264f7ddbdddea33ebe38a59e0c113b5c4c44906c1c2e08a01b", - "cmd/erasure-server-pool.go": "1ebb616ac971a22bd736d1b381845c0011cbc95b18dc273e4a946eec8244c80d", - "cmd/site-replication-metadata-gate_test.go": "1f170b84d2d25d67a4386451ce26597a752f96c9215155f3156ac1fd140ead86", - "cmd/site-replication-metadata-heal_test.go": "37191261f643dae88fbe5be44da8f08b2c52455245d5c61a2df564ecb7c8bffd", - "cmd/site-replication-metadata.go": "5756a5495723a2d9456b1c3ed7875cec789298eefb8230ea57126f9de83c4287", - "cmd/site-replication-metadata_test.go": "2898c88d48d9693d1c974ee7fd6846b86fe354f378caa630eac2ad0d9b9edfe8", - "cmd/site-replication.go": "5015550411bd4d82119cdb96b7f663fd082e4906baee259b30aedbaa616354c8", - "cmd/site-replication_test.go": "d85dd2aa991a6cce1fccfa17205e4a478f4ced00e0585fbe2aee2dbf97884b8a", - "docs/site-replication/README.md": "abe39529bf5a864dc7c945fd368b647f3fa2121fb324cea1cf63373eff9683a7" - }, - "original_review_head": "4089113e3edbd21a29be8e6af74662462bcc22af", - "equivalent_signed_commit": "1ee64a8d895f0876580d5e27d0ce6facf41c347e", - "reviewed_tree": "0d18c940c6dec380dffc977cb5720b4e2384e46a", - "environment": "go version go1.27.1 darwin/arm64", - "scope": "Historical analysis and plan, final review reports and invocation metadata, selected executed test logs, source/binary identity, and rerunnable loopback lab. Excludes model reasoning streams, binaries, temporary volumes and invalid fixture logs. Paths, historical document links and trailing console whitespace were normalized; JSON billing fields and build-info dependency lists were omitted. Original and archived hashes distinguish these editorial transformations.", - "final_verdict": "GO_WITH_NONBLOCKING_NOTES", - "blocking_findings": 0, - "records": [ - { - "file": "issue-77-current.md", - "source_record": "issue-77-current.md", - "original_sha256": "702f9fe93703c8d4b79a3e2c4709e7f49614dc20211ed5221f3adbd5e3809e2d", - "archived_sha256": "b22b50e584de73cfe2f101d35bb912d314feb7f0e0ce48018e1b4a48dc4cf5c5" - }, - { - "file": "issue-77-plan.md", - "source_record": "issue-77-plan.md", - "original_sha256": "2ee214694f16e5949ed58290364770149aee3748faee3614122fee189ef83add", - "archived_sha256": "36ae4f99bbc22827cc9a1a437b413713e61f442a568347eeef836b4245c67081" - }, - { - "file": "issue-77/current-tests.log", - "source_record": "issue-77/current-tests.log", - "original_sha256": "d6ed0ef373a37f8254d387a0757a431c2785b6b9a30c4e06d0034688ca3a41e8", - "archived_sha256": "d6ed0ef373a37f8254d387a0757a431c2785b6b9a30c4e06d0034688ca3a41e8" - }, - { - "file": "issue-77/issue77_heal_review_test.go.txt", - "source_record": "issue-77/issue77_heal_review_test.go.txt", - "original_sha256": "f1b164afff783282253660b8fa5c131b0de946a078d370cdedd95c338fa73810", - "archived_sha256": "f1b164afff783282253660b8fa5c131b0de946a078d370cdedd95c338fa73810" - }, - { - "file": "issue-77/issue77_review_test.go.txt", - "source_record": "issue-77/issue77_review_test.go.txt", - "original_sha256": "77e557bd098fad1345085b6bca27ff8a733984092c68c6b26d0dc74e5fca8caa", - "archived_sha256": "77e557bd098fad1345085b6bca27ff8a733984092c68c6b26d0dc74e5fca8caa" - }, - { - "file": "issue-77/review/adoption-baseline-probe.go.txt", - "source_record": "issue-77/review/adoption-baseline-probe.go.txt", - "original_sha256": "4cf7aae6763f1ae736af087244dae01e32af1c546923fa782891759967981574", - "archived_sha256": "4cf7aae6763f1ae736af087244dae01e32af1c546923fa782891759967981574" - }, - { - "file": "issue-77/review/adoption-baseline-probe.log", - "source_record": "issue-77/review/adoption-baseline-probe.log", - "original_sha256": "d5ad1bfe7d4faafc7bfc6f55800dc47a7d60b7ec7124cc31ec9ed11b0154bde7", - "archived_sha256": "d5ad1bfe7d4faafc7bfc6f55800dc47a7d60b7ec7124cc31ec9ed11b0154bde7" - }, - { - "file": "issue-77/review/adoption-baseline-probe.metadata.json", - "source_record": "issue-77/review/adoption-baseline-probe.metadata.json", - "original_sha256": "618e81cb5852b08fbc3029d812647b9d2dbe3560349ee2e44b860356e12cffa9", - "archived_sha256": "a462ad6b10cb7e2737330eed5d5e0a5f98ac0d664aa6fcf40a1f5f2f073709f6" - }, - { - "file": "issue-77/review/author-followup-findings.md", - "source_record": "issue-77/review/author-followup-findings.md", - "original_sha256": "b6d60d813702db300313430325046aa89e45071d3cdc20083d82f061b75ab2c4", - "archived_sha256": "b6d60d813702db300313430325046aa89e45071d3cdc20083d82f061b75ab2c4" - }, - { - "file": "issue-77/review/decisions-v2.md", - "source_record": "issue-77/review/decisions-v2.md", - "original_sha256": "52c85f0b45073471e96abe86aa5a182858ff00a5dfe00f290f570f2aad86c5a1", - "archived_sha256": "52c85f0b45073471e96abe86aa5a182858ff00a5dfe00f290f570f2aad86c5a1" - }, - { - "file": "issue-77/review/decisions-v3.md", - "source_record": "issue-77/review/decisions-v3.md", - "original_sha256": "7325864c0a3e93689989612da0427b88f890ef8baf197f609c570636b16a2b85", - "archived_sha256": "7325864c0a3e93689989612da0427b88f890ef8baf197f609c570636b16a2b85" - }, - { - "file": "issue-77/review/decisions-v4.md", - "source_record": "issue-77/review/decisions-v4.md", - "original_sha256": "982b2e802aed883f2b5e211f0e1677388571ccde8e32af878a35b4192c1f4d16", - "archived_sha256": "982b2e802aed883f2b5e211f0e1677388571ccde8e32af878a35b4192c1f4d16" - }, - { - "file": "issue-77/review/final-review.md", - "source_record": "issue-77/review/final-review.md", - "original_sha256": "2410f7b92e2c74b235d54bc155b822675c6b5f9ab0b7226fccca42dc38b0a7ea", - "archived_sha256": "2410f7b92e2c74b235d54bc155b822675c6b5f9ab0b7226fccca42dc38b0a7ea" - }, - { - "file": "issue-77/review/opus5-max-v1.md", - "source_record": "issue-77/review/opus5-max-v1.md", - "original_sha256": "8a3b750a261ef520acbe658395d3303cea0835b07184b96022781a2a5d0d4dcf", - "archived_sha256": "8a3b750a261ef520acbe658395d3303cea0835b07184b96022781a2a5d0d4dcf" - }, - { - "file": "issue-77/review/opus5-max-v1.metadata.json", - "source_record": "issue-77/review/opus5-max-v1.metadata.json", - "original_sha256": "959a9eac3a8449ef27027fbc65aca55a32b1f46063175d7187922296ecd0a957", - "archived_sha256": "d5e3be5c5377adeb88fdcb444ad5b6ee338af91d095027e1c9502298eda51d6d" - }, - { - "file": "issue-77/review/opus5-max-v2.md", - "source_record": "issue-77/review/opus5-max-v2.md", - "original_sha256": "848a476f18ce88208b72d8bf6cb49a2316b2b14dcba64ad66e636fe0a3d70595", - "archived_sha256": "848a476f18ce88208b72d8bf6cb49a2316b2b14dcba64ad66e636fe0a3d70595" - }, - { - "file": "issue-77/review/opus5-max-v2.metadata.json", - "source_record": "issue-77/review/opus5-max-v2.metadata.json", - "original_sha256": "ff63843a993830ea48e1a44987390b94613dea99a796453bc91bebca2f4e119f", - "archived_sha256": "3cd83d43f461fb1e13bbc4f4163769fa6daf4d151d8442d8615a3e813da38853" - }, - { - "file": "issue-77/review/opus5-max-v3.md", - "source_record": "issue-77/review/opus5-max-v3.md", - "original_sha256": "557ef70288a9a3939842a2f76d63694e2c103a8bfcaa99b7c161ee89e1f58061", - "archived_sha256": "557ef70288a9a3939842a2f76d63694e2c103a8bfcaa99b7c161ee89e1f58061" - }, - { - "file": "issue-77/review/opus5-max-v3.metadata.json", - "source_record": "issue-77/review/opus5-max-v3.metadata.json", - "original_sha256": "ad488156f42e33e47ed5d0a267ee69ee82efff69881d0e6400e88cf88a09ef60", - "archived_sha256": "1c84da615bccc2f1d7a2aa917ef7af751b0943cc7e413b39f59164a65acc2c98" - }, - { - "file": "issue-77/review/opus5-max-v4.md", - "source_record": "issue-77/review/opus5-max-v4.md", - "original_sha256": "cf10b9d7963ff2b7d4c0fbb1fe744a0e853549eaf7ce675a94cc000f74fe6682", - "archived_sha256": "cf10b9d7963ff2b7d4c0fbb1fe744a0e853549eaf7ce675a94cc000f74fe6682" - }, - { - "file": "issue-77/review/opus5-max-v4.metadata.json", - "source_record": "issue-77/review/opus5-max-v4.metadata.json", - "original_sha256": "95bae4427322fc42974a9d94ed5dd6d272a46b6eb669f95669a2fe1ef6ee4d42", - "archived_sha256": "60d68b2db11557ee09d60b67748844a56d7b1f323803163ef03d420ad1cc8c42" - }, - { - "file": "issue-77/review/plan-v1.md", - "source_record": "issue-77/review/plan-v1.md", - "original_sha256": "0218a2ca59750acd6a24dc5ece1d55a19762a863f1a2de8edc6ffc9b4ab072e0", - "archived_sha256": "97d88bbd010cbd19744c2e2fee0fed8d3713ca553b5d0f06161299772ea8104b" - }, - { - "file": "issue-77/review/plan-v2.md", - "source_record": "issue-77/review/plan-v2.md", - "original_sha256": "2b2c712b17f757ca1a8501714c6c2472bd442a6be50b29d92f2d87e23c412ba8", - "archived_sha256": "b193ee6e47b24576f3f4d06608f1bcd0f7f2b0c27ef782ea4f74ee3706f7aa4d" - }, - { - "file": "issue-77/review/plan-v3.md", - "source_record": "issue-77/review/plan-v3.md", - "original_sha256": "8c4ab51a397246a07bb1cc967df3377d26ac084b7928191eb8fecacf6e7c3aed", - "archived_sha256": "fb3b37ab9dd2f4ab12b8050293603ed589645a730f1e23c81e2f7162ca46f7d2" - }, - { - "file": "issue-77/review/plan-v4.md", - "source_record": "issue-77/review/plan-v4.md", - "original_sha256": "fbfd37498ab32a589d06254f32182869d5d49fc97722416c46c10b41e5f77816", - "archived_sha256": "8624e9b86d3cb6daa497d43ac8cb50318ebabdd7eb4bb7c605e0720522a73bd9" - }, - { - "file": "issue-77/review/policy-encoding-probe.go.txt", - "source_record": "issue-77/review/policy-encoding-probe.go.txt", - "original_sha256": "4e07cba70bdc329a0003ca0b6711886a6de22323293e8de3625cdc838ccaf537", - "archived_sha256": "4e07cba70bdc329a0003ca0b6711886a6de22323293e8de3625cdc838ccaf537" - }, - { - "file": "issue-77/review/wire-state-probe.go.txt", - "source_record": "issue-77/review/wire-state-probe.go.txt", - "original_sha256": "cafc6e2d482cc8e178ae3e17651410e3070049cd60009f0edd30fbac29e1309c", - "archived_sha256": "cafc6e2d482cc8e178ae3e17651410e3070049cd60009f0edd30fbac29e1309c" - }, - { - "file": "issue-77/review/wire-state-probe.log", - "source_record": "issue-77/review/wire-state-probe.log", - "original_sha256": "5077cd264a50cc05fe9dd628a6e98a3b0aaf94e04b65b712563191e11e4ca9f1", - "archived_sha256": "5077cd264a50cc05fe9dd628a6e98a3b0aaf94e04b65b712563191e11e4ca9f1" - }, - { - "file": "issue-77/implementation-review/opus5-max-review.md", - "source_record": "implementation-review/opus5-max-review.md", - "original_sha256": "e40dad6e1969898e35c82b7fd24517a5ee714d603a54fe3598b9e69c20922118", - "archived_sha256": "e40dad6e1969898e35c82b7fd24517a5ee714d603a54fe3598b9e69c20922118" - }, - { - "file": "issue-77/implementation-review/session.json", - "source_record": "implementation-review/session.json", - "original_sha256": "c347259a0a501242eb787319d43fe16c117d021ea4e418ce74b09b6c9c33ad08", - "archived_sha256": "d26e9c3a7f3d0ba3d34de54e95c64e5939b8d5da1d190bb94d80423b6ca79205" - }, - { - "file": "issue-77/implementation-review/round-2/opus5-max-review.md", - "source_record": "implementation-review/round-2/opus5-max-review.md", - "original_sha256": "306a856010f7f9bd1cbec39aba83236978f5a811c98f7fc3f8a08217f72de111", - "archived_sha256": "306a856010f7f9bd1cbec39aba83236978f5a811c98f7fc3f8a08217f72de111" - }, - { - "file": "issue-77/implementation-review/round-2/session.json", - "source_record": "implementation-review/round-2/session.json", - "original_sha256": "64113634f4e8d0525ed17dd74d092c74419d172aa94b501187daae8ebc27e279", - "archived_sha256": "4f4dfe9c50b43b64f15fad830c39c116a296c302655d2c718475249521a68f19" - }, - { - "file": "issue-77/implementation-review/round-3/opus5-max-review.md", - "source_record": "implementation-review/round-3/opus5-max-review.md", - "original_sha256": "9106938a7a34cfbc6a11d372dd5ffb71ae7417a77a56d10bbed178f539de8fba", - "archived_sha256": "9106938a7a34cfbc6a11d372dd5ffb71ae7417a77a56d10bbed178f539de8fba" - }, - { - "file": "issue-77/implementation-review/round-3/session.json", - "source_record": "implementation-review/round-3/session.json", - "original_sha256": "28944efc7a7b2c5451beedf0a05202ffc23b29fad2d9bd04cc89fad3d72184b9", - "archived_sha256": "bf31d2dbd8009f0a966c0e820dfdbd5a2820e8b36e71e615f783932fd4ecc9de" - }, - { - "file": "issue-77/implementation-review/round-2/confirmed-before.log", - "source_record": "implementation-review/round-2/confirmed-before.log", - "original_sha256": "af2df1c296c3377edb6aae45caa9fafd5e620ffb5a09438c15ca50e3d2cb1c10", - "archived_sha256": "af2df1c296c3377edb6aae45caa9fafd5e620ffb5a09438c15ca50e3d2cb1c10" - }, - { - "file": "issue-77/implementation-review/round-2/confirmed-after.log", - "source_record": "implementation-review/round-2/confirmed-after.log", - "original_sha256": "0c8be7325817049afe2bd2d3ed668e61cbde2887a443b988b8defbf12304b5bc", - "archived_sha256": "0c8be7325817049afe2bd2d3ed668e61cbde2887a443b988b8defbf12304b5bc" - }, - { - "file": "issue-77/implementation-review/round-2/confirmed-after-race.log", - "source_record": "implementation-review/round-2/confirmed-after-race.log", - "original_sha256": "da2725133b6c1b41a50a92700ff6adaf6dae7e9ac9d6b5812844715fd2b70e55", - "archived_sha256": "da2725133b6c1b41a50a92700ff6adaf6dae7e9ac9d6b5812844715fd2b70e55" - }, - { - "file": "issue-77/implementation-review/round-2/confirmed-diagnostics-before.log", - "source_record": "implementation-review/round-2/confirmed-diagnostics-before.log", - "original_sha256": "3d2c1325f1612c9ce79172b148d1ae02cf5443087270e9caeb46046fec1ea2c8", - "archived_sha256": "3d2c1325f1612c9ce79172b148d1ae02cf5443087270e9caeb46046fec1ea2c8" - }, - { - "file": "issue-77/implementation-review/round-2/no-source-before.log", - "source_record": "implementation-review/round-2/no-source-before.log", - "original_sha256": "e6bac0d117eb195b0ff67ef55eaff302c5f7ee36abf236fdcb69486330b31185", - "archived_sha256": "e6bac0d117eb195b0ff67ef55eaff302c5f7ee36abf236fdcb69486330b31185" - }, - { - "file": "issue-77/implementation-review/round-2/initial-sync-before.log", - "source_record": "implementation-review/round-2/initial-sync-before.log", - "original_sha256": "f200f9b961baf20e48f22b50cebd82525dd4647ea42203179d140b5474a28427", - "archived_sha256": "f200f9b961baf20e48f22b50cebd82525dd4647ea42203179d140b5474a28427" - }, - { - "file": "issue-77/implementation-review/round-3/docs-check.json", - "source_record": "implementation-review/round-3/docs-check.json", - "original_sha256": "76747f4d197d250e6bd3aaa1eba5a7013e56b655f10d143b984faed9d22b503b", - "archived_sha256": "322f206ca48816c415ba29f9c67935c0eba038823eddfac8fb7e298ff50bda4a" - }, - { - "file": "issue-77/implementation-review/round-3/docs-check.log", - "source_record": "implementation-review/round-3/docs-check.log", - "original_sha256": "af8752f3d8a072954dc6de2b8c72a4054cffbe3583aeae225a8d6623fc95e9fa", - "archived_sha256": "a1d67bf392e45da04af9d819a5d76cec98006d1fc78c1a4cce486eb305bdc3ac" - }, - { - "file": "issue-77/implementation-review/round-3/final-build.json", - "source_record": "implementation-review/round-3/final-build.json", - "original_sha256": "924df2a034af81acfe8961489664fa213791096db637e962ced6b78ba7ea8388", - "archived_sha256": "c5de13eceee16b23886290fbe351a5269735a549723ed400ddb3e5f0bf7c88e5" - }, - { - "file": "issue-77/implementation-review/round-3/final-build.log", - "source_record": "implementation-review/round-3/final-build.log", - "original_sha256": "d5d78e4d1a5b0dc5bc6042ae94ad64aaf2c500ff3e9091ea1dc9dbdfff04b263", - "archived_sha256": "b212590ec72fc2489ea00c36458b3ce900b5b23543c7b141d211ac7b4b06f07b" - }, - { - "file": "issue-77/implementation-review/round-3/final-cmd.json", - "source_record": "implementation-review/round-3/final-cmd.json", - "original_sha256": "431f663b748e81b9c311fa1b931483fe0b1ed09dec796b0894bb5d4a37f5f6b9", - "archived_sha256": "39a02d590c9615721fbded8e95cc3b04ebacb32ba5c943d8e9a6f55d912c80c7" - }, - { - "file": "issue-77/implementation-review/round-3/final-cmd.log", - "source_record": "implementation-review/round-3/final-cmd.log", - "original_sha256": "681576adac6d627140a791a22085fc520b61f4a6e04172bc7fb5d0f3967e9c40", - "archived_sha256": "67723bedd3472d22a2e3f043e48fdab9bc5e8a212d932c513d44d61cc13eb09d" - }, - { - "file": "issue-77/implementation-review/round-3/final-lint.json", - "source_record": "implementation-review/round-3/final-lint.json", - "original_sha256": "1dc2b38de9c4f37983e45e11ad5f416810f0c96e2cfeeff64b25846544918751", - "archived_sha256": "35229aa6e418c54072abf141b4296be491a43cdba87c2f56f853076d78d66559" - }, - { - "file": "issue-77/implementation-review/round-3/final-lint.log", - "source_record": "implementation-review/round-3/final-lint.log", - "original_sha256": "40eb5ec64ae64b73489fc7b6e145aabfea6554e0c73fa22be7b4a8201bb63d18", - "archived_sha256": "d4b9455691ff7bcce76a0d886f603eda2179e7fa4e408b8e3bb987d693987c3a" - }, - { - "file": "issue-77/implementation-review/round-3/final-runtime.json", - "source_record": "implementation-review/round-3/final-runtime.json", - "original_sha256": "6f76d83dbf9749f9dde0d6386f0b8a6b52cda6677c17ef9d739f05ea9379c52f", - "archived_sha256": "b57c593e1023a0226e7950c142d5bd9a231832a9e7aefc7d9d584075b70b9aca" - }, - { - "file": "issue-77/implementation-review/round-3/final-runtime.log", - "source_record": "implementation-review/round-3/final-runtime.log", - "original_sha256": "4c0137bbe554426026e1d4c8baf48e28368e2450e886c506e0055e0591cd5e5e", - "archived_sha256": "2b6228f3c6003e896193be17a7c94970992c1fbcbaba013b1984348bf772bc1f" - }, - { - "file": "issue-77/implementation-review/round-3/final-style-target-race.json", - "source_record": "implementation-review/round-3/final-style-target-race.json", - "original_sha256": "09b77aeb0d720de59ea5b1c31f017e8bf32e89c8dc0d4ecc3f35704f21bc0863", - "archived_sha256": "075c8d7393164e5b43364d25c163f5da8675f5b69c67e7523a579943a710aaca" - }, - { - "file": "issue-77/implementation-review/round-3/final-style-target-race.log", - "source_record": "implementation-review/round-3/final-style-target-race.log", - "original_sha256": "3089ad9a1c7283a23853a137311728598a7e1ae3bdbbf16e5f000e1149a7e12f", - "archived_sha256": "a2609e9a889bcc68828e58c15967bb04820a92462c3f94de86ea584e33a6660a" - }, - { - "file": "issue-77/implementation-review/round-3/final-target-race.json", - "source_record": "implementation-review/round-3/final-target-race.json", - "original_sha256": "68dfcad5fc63d5981065f86fb00482cc466c46f275c4c340095c26ad719f079c", - "archived_sha256": "5fb25fa150e66478b2d170faeed3374d71325311f7e625996fe1653cfc957b66" - }, - { - "file": "issue-77/implementation-review/round-3/final-target-race.log", - "source_record": "implementation-review/round-3/final-target-race.log", - "original_sha256": "b437b206ebd12fff304f9cdb59b39f25fd0ecf54e9f510aca98c444b0b516c89", - "archived_sha256": "a5b6b6337ebbcf0eca02c2ca15bb1fa562d39ab6fe29f41cd304dd2979976f59" - }, - { - "file": "issue-77/implementation-review/round-3/final-tree-equivalence.json", - "source_record": "implementation-review/round-3/final-tree-equivalence.json", - "original_sha256": "f7d1563d2f675ae282b4bec0bcedcc9809b0a0c985332d862c61ff4b8c75a59f", - "archived_sha256": "f7d1563d2f675ae282b4bec0bcedcc9809b0a0c985332d862c61ff4b8c75a59f" - }, - { - "file": "issue-77/implementation-review/round-3/final-vet.json", - "source_record": "implementation-review/round-3/final-vet.json", - "original_sha256": "934ae2812aef8b8635517f838a07cc81dc4780603d621770f9a150bf00d43561", - "archived_sha256": "be1b653811da4f6503f329f7e7c8b321b3426107548f0079c8560b177bc0c37f" - }, - { - "file": "issue-77/implementation-review/round-3/final-vet.log", - "source_record": "implementation-review/round-3/final-vet.log", - "original_sha256": "f8166088e218c7c8afa25988c02cee77ec58b552f531121a07959b31bd97ebbb", - "archived_sha256": "012d45737551ed5ffdccc3b3535ce0be152a9e9f869f50779d5da04e965a9936" - }, - { - "file": "issue-77/implementation-review/round-3/test-style.diff", - "source_record": "implementation-review/round-3/test-style.diff", - "original_sha256": "69a7f7efbb17d8dcdd2a0d7f91098569a48d7f1aeeecb6d2aefe04b6047a9025", - "archived_sha256": "69a7f7efbb17d8dcdd2a0d7f91098569a48d7f1aeeecb6d2aefe04b6047a9025" - }, - { - "file": "issue-77/implementation-review/ci-crosscompile-after.log", - "source_record": "implementation-review/ci-crosscompile-after.log", - "original_sha256": "68ef16136fabb16babd8fa26d2e3fcf6ffddac30002f874f7b9dfdd61961e82f", - "archived_sha256": "68ef16136fabb16babd8fa26d2e3fcf6ffddac30002f874f7b9dfdd61961e82f" - }, - { - "file": "issue-77/implementation-review/ci-gen-after.log", - "source_record": "implementation-review/ci-gen-after.log", - "original_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "archived_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - }, - { - "file": "issue-77/implementation-review/ci-internal-after.log", - "source_record": "implementation-review/ci-internal-after.log", - "original_sha256": "775e5b2b35fda828ffd69f47902e065f58aa2e2ecd915ace9e50f4085aa2d0c4", - "archived_sha256": "775e5b2b35fda828ffd69f47902e065f58aa2e2ecd915ace9e50f4085aa2d0c4" - }, - { - "file": "issue-77/implementation-review/ci-lint-after.log", - "source_record": "implementation-review/ci-lint-after.log", - "original_sha256": "cd29668fa61e94079dda874e10920623279083c491798fa9cbdb903ebeee4d82", - "archived_sha256": "cd29668fa61e94079dda874e10920623279083c491798fa9cbdb903ebeee4d82" - }, - { - "file": "issue-77/implementation-review/ci-s3select-after.log", - "source_record": "implementation-review/ci-s3select-after.log", - "original_sha256": "30fc4888ba394ef0daa6c76fa497c28086b7307ec30d195fe4f2b725badc59c9", - "archived_sha256": "30fc4888ba394ef0daa6c76fa497c28086b7307ec30d195fe4f2b725badc59c9" - }, - { - "file": "issue-77/implementation-review/ci-verify-after.log", - "source_record": "implementation-review/ci-verify-after.log", - "original_sha256": "2b6860f20bdfd453bcc299f8b57a117cd698bab77d6a474913e31794c3937bd3", - "archived_sha256": "2b6860f20bdfd453bcc299f8b57a117cd698bab77d6a474913e31794c3937bd3" - }, - { - "file": "issue-77/implementation-review/round-3/binary-identity.json", - "source_record": "implementation-review/round-3/binary-identity.json", - "original_sha256": "ffdb768a1931388f174eaca0980cc840015a8e0995b5509ef68718d10044f5e5", - "archived_sha256": "d4ea7abc1f419e834746820b5b2d2f1b3264e42114161e7c6803f4e952fcf17d" - }, - { - "file": "issue-77/runtime/converged.json", - "source_record": "runtime-reviewed/converged.json", - "original_sha256": "d6d97bc01a12fd624af37432e9558356255b5cd9d4333eaa616e39998c2ce94f", - "archived_sha256": "d6d97bc01a12fd624af37432e9558356255b5cd9d4333eaa616e39998c2ce94f" - }, - { - "file": "issue-77/runtime/main.go", - "source_record": "twosite/main.go", - "original_sha256": "ec63a5e2553907a826727af416145057215adabfb9fa2839861f0c05b7cc2a8f", - "archived_sha256": "023459a3873753d5a373c2ba6c078d690c9a2a18c31efa98ecd5f4976ecaba4a" - } - ] -} diff --git a/docs/investigations/issue-77/current-tests.log b/docs/investigations/issue-77/current-tests.log deleted file mode 100644 index 51cfe5819..000000000 --- a/docs/investigations/issue-77/current-tests.log +++ /dev/null @@ -1,129 +0,0 @@ -=== RUN TestQueuedMetadataUpdateAfterDelete -=== RUN TestQueuedMetadataUpdateAfterDelete/expiry=false -=== RUN TestQueuedMetadataUpdateAfterDelete/expiry=true ---- PASS: TestQueuedMetadataUpdateAfterDelete (0.43s) - --- PASS: TestQueuedMetadataUpdateAfterDelete/expiry=false (0.22s) - --- PASS: TestQueuedMetadataUpdateAfterDelete/expiry=true (0.21s) -=== RUN TestIssue77CurrentHealInputs -=== RUN TestIssue77CurrentHealInputs/ErasureSD/quota-tombstone-cache - issue77_heal_review_test.go:58: QUOTA_CACHE: disk has no quota, cache still enforces 1024 -=== RUN TestIssue77CurrentHealInputs/ErasureSD/same-payload-time-barrier - issue77_heal_review_test.go:79: BARRIER_NOT_HEALED: same payload remains at 2026-09-12 06:59:55.50105 +0800 CST, latest is 2026-09-11 23:00:55.50105 +0000 UTC -=== RUN TestIssue77CurrentHealInputs/ErasureSD/creation-default-selection - issue77_heal_review_test.go:105: DEFAULT_SELECTED: creation-default erased valid policy in 5/32 heal rounds -=== RUN TestIssue77CurrentHealInputs/ErasureSD/tag-remote-source-time - issue77_heal_review_test.go:144: TAG_WIRE_TIME: sent 0001-01-01 00:00:00 +0000 UTC, want 2026-09-11 23:00:55.50105 +0000 UTC -=== RUN TestIssue77CurrentHealInputs/Erasure/quota-tombstone-cache - issue77_heal_review_test.go:58: QUOTA_CACHE: disk has no quota, cache still enforces 1024 -=== RUN TestIssue77CurrentHealInputs/Erasure/same-payload-time-barrier - issue77_heal_review_test.go:79: BARRIER_NOT_HEALED: same payload remains at 2026-09-12 06:59:55.71847 +0800 CST, latest is 2026-09-11 23:00:55.71847 +0000 UTC -=== RUN TestIssue77CurrentHealInputs/Erasure/creation-default-selection - issue77_heal_review_test.go:105: DEFAULT_SELECTED: creation-default erased valid policy in 2/32 heal rounds -=== RUN TestIssue77CurrentHealInputs/Erasure/tag-remote-source-time - issue77_heal_review_test.go:144: TAG_WIRE_TIME: sent 0001-01-01 00:00:00 +0000 UTC, want 2026-09-11 23:00:55.71847 +0000 UTC ---- FAIL: TestIssue77CurrentHealInputs (1.21s) - --- FAIL: TestIssue77CurrentHealInputs/ErasureSD/quota-tombstone-cache (0.01s) - --- FAIL: TestIssue77CurrentHealInputs/ErasureSD/same-payload-time-barrier (0.00s) - --- FAIL: TestIssue77CurrentHealInputs/ErasureSD/creation-default-selection (0.08s) - --- FAIL: TestIssue77CurrentHealInputs/ErasureSD/tag-remote-source-time (0.00s) - --- FAIL: TestIssue77CurrentHealInputs/Erasure/quota-tombstone-cache (0.04s) - --- FAIL: TestIssue77CurrentHealInputs/Erasure/same-payload-time-barrier (0.02s) - --- FAIL: TestIssue77CurrentHealInputs/Erasure/creation-default-selection (0.83s) - --- FAIL: TestIssue77CurrentHealInputs/Erasure/tag-remote-source-time (0.02s) -=== RUN TestIssue77CurrentPeerCheckBeforeLock -=== RUN TestIssue77CurrentPeerCheckBeforeLock/ErasureSD - issue77_heal_review_test.go:209: CHECK_OUTSIDE_LOCK: queued older event replaced newer committed tags with "keyolder" -=== RUN TestIssue77CurrentPeerCheckBeforeLock/Erasure - issue77_heal_review_test.go:209: CHECK_OUTSIDE_LOCK: queued older event replaced newer committed tags with "keyolder" ---- FAIL: TestIssue77CurrentPeerCheckBeforeLock (0.30s) - --- FAIL: TestIssue77CurrentPeerCheckBeforeLock/ErasureSD (0.01s) - --- FAIL: TestIssue77CurrentPeerCheckBeforeLock/Erasure (0.07s) -=== RUN TestIssue77CurrentSourceTimeAndDeletion -=== RUN TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/policy - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.017159 +0800 CST, want source 2026-09-11 22:59:57.012479 +0000 UTC - issue77_review_test.go:91: NEWER_DELETE: HTTP 200, but live config remained after newer source DELETE - issue77_review_test.go:110: STALE_RESURRECTION: older source PUT resurrected a locally deleted config -=== RUN TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/tags - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.029759 +0800 CST, want source 2026-09-11 22:59:57.012479 +0000 UTC - issue77_review_test.go:91: NEWER_DELETE: HTTP 200, but live config remained after newer source DELETE - issue77_review_test.go:105: TOMBSTONE_EXPORT: got 0001-01-01 00:00:00 +0000 UTC, want 2026-09-12 07:49:57.03401 +0800 CST - issue77_review_test.go:110: STALE_RESURRECTION: older source PUT resurrected a locally deleted config -=== RUN TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/sse - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.04395 +0800 CST, want source 2026-09-11 22:59:57.012479 +0000 UTC - issue77_review_test.go:91: NEWER_DELETE: HTTP 200, but live config remained after newer source DELETE - issue77_review_test.go:105: TOMBSTONE_EXPORT: got 0001-01-01 00:00:00 +0000 UTC, want 2026-09-12 07:49:57.048028 +0800 CST - issue77_review_test.go:110: STALE_RESURRECTION: older source PUT resurrected a locally deleted config -=== RUN TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/quota - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.057746 +0800 CST, want source 2026-09-11 22:59:57.012479 +0000 UTC - issue77_review_test.go:91: NEWER_DELETE: HTTP 200, but live config remained after newer source DELETE - issue77_review_test.go:105: TOMBSTONE_EXPORT: got 0001-01-01 00:00:00 +0000 UTC, want 2026-09-12 07:49:57.061453 +0800 CST - issue77_review_test.go:112: older source PUT did not resurrect config -=== RUN TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/versioning - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.069105 +0800 CST, want source 2026-09-11 22:59:57.012479 +0000 UTC - issue77_review_test.go:87: nil payload is correctly a no-op -=== RUN TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/objectlock - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.077601 +0800 CST, want source 2026-09-11 22:59:57.012479 +0000 UTC - issue77_review_test.go:87: nil payload is correctly a no-op -=== RUN TestIssue77CurrentSourceTimeAndDeletion/Erasure/policy - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.247526 +0800 CST, want source 2026-09-11 22:59:57.221624 +0000 UTC - issue77_review_test.go:91: NEWER_DELETE: HTTP 200, but live config remained after newer source DELETE - issue77_review_test.go:110: STALE_RESURRECTION: older source PUT resurrected a locally deleted config -=== RUN TestIssue77CurrentSourceTimeAndDeletion/Erasure/tags - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.369557 +0800 CST, want source 2026-09-11 22:59:57.221624 +0000 UTC - issue77_review_test.go:91: NEWER_DELETE: HTTP 200, but live config remained after newer source DELETE - issue77_review_test.go:105: TOMBSTONE_EXPORT: got 0001-01-01 00:00:00 +0000 UTC, want 2026-09-12 07:49:57.394147 +0800 CST - issue77_review_test.go:110: STALE_RESURRECTION: older source PUT resurrected a locally deleted config -=== RUN TestIssue77CurrentSourceTimeAndDeletion/Erasure/sse - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.466111 +0800 CST, want source 2026-09-11 22:59:57.221624 +0000 UTC - issue77_review_test.go:91: NEWER_DELETE: HTTP 200, but live config remained after newer source DELETE - issue77_review_test.go:105: TOMBSTONE_EXPORT: got 0001-01-01 00:00:00 +0000 UTC, want 2026-09-12 07:49:57.489771 +0800 CST - issue77_review_test.go:110: STALE_RESURRECTION: older source PUT resurrected a locally deleted config -=== RUN TestIssue77CurrentSourceTimeAndDeletion/Erasure/quota - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.562629 +0800 CST, want source 2026-09-11 22:59:57.221624 +0000 UTC - issue77_review_test.go:91: NEWER_DELETE: HTTP 200, but live config remained after newer source DELETE - issue77_review_test.go:105: TOMBSTONE_EXPORT: got 0001-01-01 00:00:00 +0000 UTC, want 2026-09-12 07:49:57.590096 +0800 CST - issue77_review_test.go:112: older source PUT did not resurrect config -=== RUN TestIssue77CurrentSourceTimeAndDeletion/Erasure/versioning - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.640941 +0800 CST, want source 2026-09-11 22:59:57.221624 +0000 UTC - issue77_review_test.go:87: nil payload is correctly a no-op -=== RUN TestIssue77CurrentSourceTimeAndDeletion/Erasure/objectlock - issue77_review_test.go:79: SOURCE_TIME: persisted 2026-09-12 07:49:57.690024 +0800 CST, want source 2026-09-11 22:59:57.221624 +0000 UTC - issue77_review_test.go:87: nil payload is correctly a no-op ---- FAIL: TestIssue77CurrentSourceTimeAndDeletion (0.78s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/policy (0.01s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/tags (0.01s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/sse (0.01s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/quota (0.01s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/versioning (0.01s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/ErasureSD/objectlock (0.01s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/Erasure/policy (0.12s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/Erasure/tags (0.10s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/Erasure/sse (0.10s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/Erasure/quota (0.08s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/Erasure/versioning (0.05s) - --- FAIL: TestIssue77CurrentSourceTimeAndDeletion/Erasure/objectlock (0.05s) -=== RUN TestIssue77CurrentBulkApplyOrdering -=== RUN TestIssue77CurrentBulkApplyOrdering/ErasureSD - issue77_review_test.go:143: BULK_STALE_OVERWRITE: older bulk event overwrote newer config, value="keyold" time=2026-09-12 08:48:57.792608 +0800 CST -=== RUN TestIssue77CurrentBulkApplyOrdering/Erasure - issue77_review_test.go:143: BULK_STALE_OVERWRITE: older bulk event overwrote newer config, value="keyold" time=2026-09-12 08:48:57.880379 +0800 CST ---- FAIL: TestIssue77CurrentBulkApplyOrdering (0.27s) - --- FAIL: TestIssue77CurrentBulkApplyOrdering/ErasureSD (0.01s) - --- FAIL: TestIssue77CurrentBulkApplyOrdering/Erasure (0.05s) -=== RUN TestPeerBucketAdoptionPreservesLockAndVersioningConfigs ---- PASS: TestPeerBucketAdoptionPreservesLockAndVersioningConfigs (0.30s) -=== RUN TestPeerBucketAdoptionBootstrapsMissingConfigs ---- PASS: TestPeerBucketAdoptionBootstrapsMissingConfigs (0.24s) -=== RUN TestPeerBucketAdoptionNormalizesVersioningWhenEnablingLock ---- PASS: TestPeerBucketAdoptionNormalizesVersioningWhenEnablingLock (0.28s) -=== RUN TestPeerBucketAdoptionEnablesSuspendedVersioning ---- PASS: TestPeerBucketAdoptionEnablesSuspendedVersioning (0.27s) -=== RUN TestPeerBucketObjectLockMetadataCurrentAndLegacyPayloads ---- PASS: TestPeerBucketObjectLockMetadataCurrentAndLegacyPayloads (0.30s) -=== RUN TestPeerBucketObjectLockMetadataWithoutLockEnabled ---- PASS: TestPeerBucketObjectLockMetadataWithoutLockEnabled (0.24s) -=== RUN TestSiteReplicationStatusAccountsPerSiteAndSurvivesMalformedConfig ---- PASS: TestSiteReplicationStatusAccountsPerSiteAndSurvivesMalformedConfig (0.52s) -FAIL -FAIL github.com/minio/minio/cmd 7.167s -FAIL diff --git a/docs/investigations/issue-77/implementation-review/ci-crosscompile-after.log b/docs/investigations/issue-77/implementation-review/ci-crosscompile-after.log deleted file mode 100644 index c4d4f5319..000000000 --- a/docs/investigations/issue-77/implementation-review/ci-crosscompile-after.log +++ /dev/null @@ -1,7 +0,0 @@ -Testing builds for OS/Arch: linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64 ---> linux/amd64:github.com/minio/minio ---> linux/arm64:github.com/minio/minio ---> darwin/amd64:github.com/minio/minio ---> darwin/arm64:github.com/minio/minio ---> windows/amd64:github.com/minio/minio ---> windows/arm64:github.com/minio/minio diff --git a/docs/investigations/issue-77/implementation-review/ci-gen-after.log b/docs/investigations/issue-77/implementation-review/ci-gen-after.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/issue-77/implementation-review/ci-internal-after.log b/docs/investigations/issue-77/implementation-review/ci-internal-after.log deleted file mode 100644 index e4d93c556..000000000 --- a/docs/investigations/issue-77/implementation-review/ci-internal-after.log +++ /dev/null @@ -1,78 +0,0 @@ -ok github.com/minio/minio/internal/amztime 5.149s -ok github.com/minio/minio/internal/arn 13.830s -ok github.com/minio/minio/internal/auth 5.472s -ok github.com/minio/minio/internal/bpool 13.244s -ok github.com/minio/minio/internal/bucket/bandwidth 12.671s -ok github.com/minio/minio/internal/bucket/cors 2.705s -ok github.com/minio/minio/internal/bucket/encryption 12.228s -ok github.com/minio/minio/internal/bucket/lifecycle 3.223s -ok github.com/minio/minio/internal/bucket/object/lock 11.599s -ok github.com/minio/minio/internal/bucket/replication 12.817s -ok github.com/minio/minio/internal/bucket/versioning 13.157s -ok github.com/minio/minio/internal/cachevalue 18.526s -? github.com/minio/minio/internal/color [no test files] -ok github.com/minio/minio/internal/config 14.365s -? github.com/minio/minio/internal/config/api [no test files] -? github.com/minio/minio/internal/config/batch [no test files] -? github.com/minio/minio/internal/config/browser [no test files] -? github.com/minio/minio/internal/config/callhome [no test files] -ok github.com/minio/minio/internal/config/compress 15.754s -ok github.com/minio/minio/internal/config/dns 17.351s -? github.com/minio/minio/internal/config/drive [no test files] -ok github.com/minio/minio/internal/config/etcd 18.460s -? github.com/minio/minio/internal/config/heal [no test files] -ok github.com/minio/minio/internal/config/identity/ldap 19.132s -ok github.com/minio/minio/internal/config/identity/openid 19.829s -? github.com/minio/minio/internal/config/identity/openid/provider [no test files] -? github.com/minio/minio/internal/config/identity/plugin [no test files] -? github.com/minio/minio/internal/config/identity/tls [no test files] -ok github.com/minio/minio/internal/config/ilm 19.256s -? github.com/minio/minio/internal/config/lambda [no test files] -ok github.com/minio/minio/internal/config/lambda/event 18.225s -? github.com/minio/minio/internal/config/lambda/target [no test files] -ok github.com/minio/minio/internal/config/notify 17.465s -? github.com/minio/minio/internal/config/policy/opa [no test files] -? github.com/minio/minio/internal/config/policy/plugin [no test files] -? github.com/minio/minio/internal/config/scanner [no test files] -ok github.com/minio/minio/internal/config/storageclass 17.289s -ok github.com/minio/minio/internal/config/subnet 17.168s -ok github.com/minio/minio/internal/crypto 18.081s -ok github.com/minio/minio/internal/deadlineconn 21.703s -ok github.com/minio/minio/internal/disk 17.514s -ok github.com/minio/minio/internal/dsync 154.956s -ok github.com/minio/minio/internal/etag 17.755s -ok github.com/minio/minio/internal/event 17.598s -ok github.com/minio/minio/internal/event/target 16.108s -ok github.com/minio/minio/internal/grid 22.027s -ok github.com/minio/minio/internal/handlers 14.030s -ok github.com/minio/minio/internal/hash 14.016s -? github.com/minio/minio/internal/hash/sha256 [no test files] -ok github.com/minio/minio/internal/http 13.661s -? github.com/minio/minio/internal/init [no test files] -ok github.com/minio/minio/internal/ioutil 14.637s -ok github.com/minio/minio/internal/jwt 13.120s -ok github.com/minio/minio/internal/kms 12.536s -ok github.com/minio/minio/internal/lock 13.029s -ok github.com/minio/minio/internal/logger 12.231s -? github.com/minio/minio/internal/logger/message/audit [no test files] -? github.com/minio/minio/internal/logger/target/console [no test files] -? github.com/minio/minio/internal/logger/target/http [no test files] -? github.com/minio/minio/internal/logger/target/kafka [no test files] -? github.com/minio/minio/internal/logger/target/loggertypes [no test files] -? github.com/minio/minio/internal/logger/target/testlogger [no test files] -ok github.com/minio/minio/internal/lsync 22.092s -? github.com/minio/minio/internal/mcontext [no test files] -? github.com/minio/minio/internal/mountinfo [no test files] -? github.com/minio/minio/internal/net [no test files] -? github.com/minio/minio/internal/once [no test files] -ok github.com/minio/minio/internal/pubsub 11.507s -ok github.com/minio/minio/internal/rest 10.318s -ok github.com/minio/minio/internal/ringbuffer 10.927s -ok github.com/minio/minio/internal/s3select 8.665s -ok github.com/minio/minio/internal/s3select/csv 8.862s -ok github.com/minio/minio/internal/s3select/json 8.547s -ok github.com/minio/minio/internal/s3select/jstream 8.936s -? github.com/minio/minio/internal/s3select/parquet [no test files] -? github.com/minio/minio/internal/s3select/simdj [no test files] -ok github.com/minio/minio/internal/s3select/sql 8.637s -ok github.com/minio/minio/internal/store 9.594s diff --git a/docs/investigations/issue-77/implementation-review/ci-lint-after.log b/docs/investigations/issue-77/implementation-review/ci-lint-after.log deleted file mode 100644 index 35e4a25b7..000000000 --- a/docs/investigations/issue-77/implementation-review/ci-lint-after.log +++ /dev/null @@ -1,13 +0,0 @@ -Installing golangci-lint v2.13.1 -golangci/golangci-lint info checking GitHub for tag 'v2.13.1' -golangci/golangci-lint info found version: 2.13.1 for v2.13.1/darwin/arm64 -golangci/golangci-lint info installed .bin/golangci/v2.13.1/golangci-lint -Running lint check -0 issues. -typos binary is not found.. skipping.. -LINT_EXIT=0 -compatibility manifest: imports=119 env=438 metrics=19 headers=87 routes=223 roots=1 grid=3 storage=15 policy=58 brand=181 sha256=db0d00c26360412a5388be1eb87c2dd763633ae243c558015f7b3ae86c80baaf -Silo rebrand compatibility baseline is unchanged -Silo delivery and runtime rebrand checks passed -docker entrypoint argv compatibility tests passed -REBRAND_EXIT=0 diff --git a/docs/investigations/issue-77/implementation-review/ci-s3select-after.log b/docs/investigations/issue-77/implementation-review/ci-s3select-after.log deleted file mode 100644 index 1c8ac99fd..000000000 --- a/docs/investigations/issue-77/implementation-review/ci-s3select-after.log +++ /dev/null @@ -1,7 +0,0 @@ -ok github.com/minio/minio/internal/s3select 2.150s -ok github.com/minio/minio/internal/s3select/csv 3.612s -ok github.com/minio/minio/internal/s3select/json 2.105s -ok github.com/minio/minio/internal/s3select/jstream 2.937s -? github.com/minio/minio/internal/s3select/parquet [no test files] -? github.com/minio/minio/internal/s3select/simdj [no test files] -ok github.com/minio/minio/internal/s3select/sql 2.620s diff --git a/docs/investigations/issue-77/implementation-review/ci-verify-after.log b/docs/investigations/issue-77/implementation-review/ci-verify-after.log deleted file mode 100644 index 3df87efa1..000000000 --- a/docs/investigations/issue-77/implementation-review/ci-verify-after.log +++ /dev/null @@ -1,2 +0,0 @@ -Silo delivery and runtime rebrand checks passed -docker entrypoint argv compatibility tests passed diff --git a/docs/investigations/issue-77/implementation-review/opus5-max-review.md b/docs/investigations/issue-77/implementation-review/opus5-max-review.md deleted file mode 100644 index dc0f48f49..000000000 --- a/docs/investigations/issue-77/implementation-review/opus5-max-review.md +++ /dev/null @@ -1,292 +0,0 @@ -# SILO #77 实现深度对抗性代码审查报告 - -审查对象 `4089113e3`(基线 `5c5765816`)。只读 worktree,全部结论来自直接阅读 `cmd/` 生产代码、`silo-pkg`/`madmin-go` 依赖源码与调用链,并与证据目录交叉核对。测试 PASS 仅当作"已覆盖的观察",不作正确性证明。 - -> 说明:本会话的 Write/ExitPlanMode 工具不可用,报告直接输出于此,未落盘。 - ---- - -## 0. Verdict - -**GO_WITH_NONBLOCKING_NOTES** - -- **无条件阻断项:0** -- **条件性阻断项:1**(F1)——条件:支持矩阵中存在 `.metadata.bin` 缺失或 `Created == 0` 的桶 -- 其余:F2 (S2)、F3 (S3)、F4 (S3)、F5(最小性)、F6(nit)、F7(范围说明)、F8(证据不足)、F9/F10(残留边界与覆盖缺口) - -我逐条读了六类配置在全部入口的生产路径,**没有**找到在"同一桶世代、Created 已知、合法带源时间事件"范围内会发生回退、删除复活、锁外判旧或重复广播的反例。核心机制是正确的。缺陷集中在两处边缘:零 Created 历史桶被新代码判为不可写(F1,且其自带测试用 stub 掩盖了生产行为),以及 Policy 规范编码排序与既有公开 mismatch 统计口径冲突(F2)。 - ---- - -## 1. Findings - -### F1 — 零 Created / 无 metadata.bin 的桶:六类配置写入全部硬失败;设计中的"物理桶 Created 补齐"在生产中不可达;配套测试用 stub 掩盖 - -**严重度 S1(条件性阻断)/ 否则 S2 · 已证实(源码级链路完整),需运行定量确认** - -位置: -- `cmd/bucket-metadata-replication.go:265-278` `ensureBucketMetadataCreated` -- `cmd/bucket-metadata-sys.go:181-185`(六类分支入口,失败即 `return err`) -- `cmd/erasure-server-pool.go:2280-2285` —— **`GetBucketInfo` 无条件用缓存 `meta.Created` 覆盖物理卷 Created** -- `cmd/admin-bucket-handlers.go:1092`(import)、`cmd/site-replication.go:1671`(bulk peer apply) -- `cmd/site-replication.go:2156-2166` + `cmd/site-replication-metadata.go:45-55`(初次同步静默跳过) -- `cmd/site-replication-metadata-gate_test.go:145-159`(stub) - -最短触发链: -1. 桶存在,但 `.minio.sys/buckets//.metadata.bin` 不存在且无 legacy 配置文件 → `loadBucketMetadataParse` 走 `errConfigNotFound` 分支并**返回 nil error 且 `Created == 0`**(`cmd/bucket-metadata.go:230-297`;注意 `defaultTimestamps()` 只在 `err == nil` 时调用)。 -2. 启动时 `concurrentLoad`(`cmd/bucket-metadata-sys.go:737-763`)把这个 `Created == 0` 的 meta 写进 `metadataMap`。 -3. `PUT ?tagging`(或 policy/sse/quota/versioning/object-lock)→ `updateAndParseMetadata` → `ensureBucketMetadataCreated` → `objAPI.GetBucketInfo(..., NoMetadata:true)` → `erasureServerPools.GetBucketInfo` 先取到物理卷时间,**随后被缓存里的 0 覆盖** → `info.Created.IsZero()` → `errors.New("bucket metadata creation time is unknown")`。 -4. 客户端收到错误。基线版本此处成功(直接赋值 + `saveMetadata`)。 - -注:`getAllLegacyConfigs` 在 `cmd/bucket-metadata.go:468` 会用 `info.ModTime` 填 `Created`,所以**有** legacy 配置文件的桶反而没事;**从未设置过任何桶配置**的老桶才是高发场景。 - -用户影响: -- 六类配置 PUT/DELETE 全部返回错误,**无运维恢复路径**;`mc admin bucket import` 对该桶整体失败(`rpt.SetStatus(bucket, "", err)`);peer bulk apply 返回错误导致该桶复制持续报错。 -- `syncToAllPeers` 对这类桶**静默跳过全部五类配置**(`state.candidate()` 要求 `valid`,而 `valid = !created.IsZero() && ...`),且**无任何诊断日志** —— 基线版本会发送。 - -最小修复(推荐 a): -- **(a)** `cmd/erasure-server-pool.go:2282` 改为 `if !meta.Created.IsZero() { bucketInfo.Created = meta.Created }`。一行;恢复计划中"物理桶 Created 补齐路径"的本意;顺带修掉"无 metadata.bin 的桶在 `mc ls` 里创建时间为零"这一既有瑕疵。需复核 `bucketExists`、`hasBucket = !bi.CreatedAt.IsZero()` 等消费点 —— Created 由零变非零对它们都是变好。 -- **(b)** 另外在 `syncToAllPeers`(`cmd/site-replication.go:2138-2156`)把已在手的 `bucketInfo.Created` 兜底进 `meta.Created`,或在"有内容但 send==false"时打一条 indeterminate,避免静默漏发。 - -应补验证: -- 把 `TestPeerBucketMetadataUnknownCreated` 的 `"physical-created"` 子用例**去掉 stub**:用真实 ObjectLayer,把 `newBucketMetadata(bucket)`(Created 为零)存盘并让缓存持有它,断言 `globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, tagXML)` 成功且 `Created` 被补齐为物理时间。**当前实现会失败。** -- 一个 `syncToAllPeers` 用例:`Created == 0` 且 Tags 非空的桶,断言初次同步仍发 Tags 事件(或至少有诊断)。 - ---- - -### F2 — Policy 规范编码对 `Statement` 数组排序,与既有公开统计的顺序敏感 `Equals` 冲突:永久假 mismatch,heal 永不修复 - -**严重度 S2(公开状态/可观测性,非数据损失)· 已证实(源码级),需运行确认** - -位置: -- `cmd/bucket-metadata-replication.go:110` —— `sort.Slice` 作用于**每个**数组,含顶层 `Statement` -- `cmd/bucket-metadata-replication.go:124-161` `canonicalBucketPolicy` -- `cmd/site-replication.go:3796` `isBktPolicyReplicated` → `prev.Equals(*p)`(`silo-pkg .../policy/bucket-policy.go:190-194` 按 `Statements[i]` **下标**逐一比较,顺序敏感) -- `cmd/site-replication-metadata.go:174` vs `:179` —— 本地 heal 写**源站原始字节**,远端 heal 经 `PeerBucketPolicyHandler` → `canonicalBucketPolicy` 写**规范字节** - -最短反例: -1. 站点 A 存在升级前写入的桶策略,statement 顺序不是规范字节序(极常见,例如 Deny 在 Allow 之前)。 -2. 加入新站点 B:`syncToAllPeers` → `initialBucketConfigReplicationEvent` 发送 **A 的原始字节**,`UpdatedAt = A.PolicyConfigUpdatedAt`。 -3. B 的 admin 入口解析后调 `PeerBucketPolicyHandler` → `canonicalBucketPolicy` → 存**排序后**字节。 -4. 此后 A/B 的比较键(canonical)完全相同、时间相同 → `compareBucketConfigStates == 0` → heal 永不写、永不发 RPC;而 `Equals` 因 statement 顺序不同返回 false → `mc admin replicate status` **永久**报 bucket policy mismatch,`ReplicatedBucketPolicies` 少计。 - -同机制第二条路径:同一轮 heal 中本地目标拿原始字节、远端目标拿规范字节,三站点集群会出现持久分叉。 - -为何是新问题:基线 `PeerBucketPolicyHandler` 用 `json.Marshal(policy)`,集合(Action/Resource/Principal)顺序随机但 **statement 切片顺序被 marshal/unmarshal 保持**,`Equals` 一直成立。 - -最小修复(三选一,推荐 a 或 b): -- (a) `canonicalBucketPolicyJSON` 不对顶层 `Statement` 数组排序(只排集合数组)——顺序在全链路被保留,两站点独立解析同一文档仍得相同键。 -- (b) `isBktPolicyReplicated` 改用 `canonicalBucketPolicy` 的键比较,而不是 `Equals`。 -- (c) `healBucketConfig` 本地分支改写 `incoming.data`(与远端同一编码)——只修本地/远端分叉,**不**修 A 与新站点的分叉。 - -应补验证:双站点用例——A 侧直接把 legacy 顺序的策略字节写盘(绕过 canonical 编码器),join B,跑两轮 heal,断言 `SiteReplicationStatus` 不报 policy mismatch。 - ---- - -### F3 — heal 诊断以 ERROR 级、按"桶 × 字段"输出;正常瞬态也报警,且单一 key 会吞掉真实 RPC 失败 - -**严重度 S3 · 已证实** - -位置:`cmd/site-replication-metadata.go:134-145`(两个诊断循环)、`:182-186`(peer 错误)、`cmd/logging.go:19-21`(`replLogOnceIf` 无 errKind ⇒ **ErrorKind**) - -1. 第二个循环对 `!state.valid` 的目标打 `indeterminate / unusable peer`。`valid` 要求对端 `CreatedAt != 0` —— 而**对端还没有这个桶**时 `CreatedAt` 就是 0(`cmd/site-replication.go:3096-3103` 给缺桶站点填零值 `SRBucketInfo`)。于是"桶刚建、尚未传播"这种完全正常的瞬态,对每个桶产生 6 条 **ERROR**。 -2. 第一个循环在对端 `RemoteTargetConnectionErr`(`cmd/site-replication.go:3020-3026` 填空 ID)时,对**每个本地桶 × 6 字段**打 `missing peer`。一个站点掉线 ≈ 每小时 6×桶数 条 ERROR。 -3. 四种完全不同的情况(缺 peer / peer 不可用 / peer RPC 失败 / Created 未知)共用同一个 key `"bucket-metadata///indeterminate"` **且错误正文相同**,而 `logOnceIf` 只按 key+正文去重(`internal/logger/logonce.go:100-119`)。结果:**真正的 heal RPC 失败可能被同桶同字段的"缺 peer"消息顶掉而完全不打印**;基线版本这些失败走 `replLogIf`,必然打印。 - -最小修复:`target.CreatedAt.IsZero()`(桶不在对端)与 `found == false` 时不打诊断;把 4 种情况拆成不同 reason(`unreachable`/`peer-error`/`unknown-created`)避免 key 撞车;给 `logBucketConfigReplication` 传 `logger.WarningKind`。 - ---- - -### F4 — 三处用户可见语义变更未写入文档 - -**严重度 S3(文档/兼容)· 已证实(逐条比对 `docs/site-replication/README.md:65-118`)** - -1. **空 Bucket Policy PUT 现在立即按删除处理**:`cmd/bucket-policy-handlers.go:102` + `cmd/bucket-metadata-replication.go:176-180` → `canonicalBucketPolicy` 对 `IsEmpty()` 返回 nil → 落盘 nil → `GetBucketPolicy` 从"200 + 空策略文档"变为 **404 NotFound**。计划 §1 明确写了"需要写入兼容说明",文档里没有。 -2. **零 Quota (`{}`) 在对端从"被删除"变为"保留 live 文档"**:`cmd/admin-bucket-handlers.go:79-96` 删除了出站 `bucketMeta.Quota = nil` 改写。这是正确的对齐(本地原本就保留 `{}`),但对端 `GetBucketQuotaConfig` 的结果会变。 -3. **`GET ?policy` 与 `mc admin bucket export` 的 JSON 形态改变**:`cmd/bucket-policy-handlers.go:202`、`cmd/admin-bucket-handlers.go:439` 改用 `canonicalBucketPolicy` → 对象键按字母序、集合数组与 **Statement 数组被排序**。语义等价(S3 策略求值与 statement 顺序无关),但字节级比对输出的工具会看到变化。 - ---- - -### F5 — 最小性:Policy 规范编码器接入 PUT / GET / export / peer 落盘,并非 #77 不变量所必需 - -**非缺陷,最小性判断 · 已证实** - -比较键在 `bucketConfigPayload`(`cmd/bucket-metadata-replication.go:172-180`)里**从已解析策略现算**,与落盘字节无关。即使 PUT/peer 仍用 `json.Marshal`(字节不确定),两站点的键依旧相同,收敛性完全不受影响。 - -因此这部分改动带来的是两件**额外**的事:(a) 让原本被 `ActionSet.MarshalJSON`(`silo-pkg .../policy/actionset.go:144-148`,空集合报错)挡掉的 `NotAction`/`NotResource` 策略首次可以写入 —— 这是**功能新增**;(b) 直接导致 F2。 - -判断:如果作者**有意**支持负集合策略,应作为独立特性声明并单独记录/测试(目前只有 `site-replication-metadata_test.go:530-541` 一个用例);如果只为 #77,最小做法是 `canonicalBucketPolicy` 只用于比较键。我**不**主张必须删除——它确实修掉了"能存不能读"的潜在坑——但必须承认这是范围外的行为扩张,且未在文档中声明。 - -其它可删复杂度(都很小,不影响不变量): -- `cmd/bucket-metadata-sys.go:151` 在零值结构体上用 `replicatedBucketConfig(&result.meta, configFile)` 做"是不是这六类"的判断,语义晦涩;一个 `isReplicatedBucketConfig(file) bool` 更清楚。 -- 修掉 F3 后,`healBucketConfig` 的第二个诊断循环(`:139-145`)可并入主循环。 - ---- - -### F6 — Nit - -- `isBucketMetadataEqual`(`cmd/site-replication.go:5164`)现在只被测试引用,生产死代码。 -- `cmd/bucket-metadata-replication.go:36-38` 注释"Object Lock is applied before Versioning"对 bulk apply 循环成立,但 `healBuckets`(`cmd/site-replication.go:4745-4746`)先 heal Versioning 再 heal Object Lock。我推演过仍收敛(最多 2 个周期、无写入环),但注释与 heal 顺序不一致,建议补一句。 - ---- - -### F7 — 范围说明(非缺陷,但必须进入决策) - -**默认 `MINIO_SITE_REPLICATION_METADATA_TOMBSTONES=off` 时,Tags / SSE / Quota 的"漏发删除"不会通过 heal 收敛。** 只有 Policy 墓碑默认导出(`cmd/site-replication.go:3953-3954` 无条件导出;另外三类在 `:3960 / :3980 / :3988` 被 gate 挡住)。 - -我完整推演了 off 模式,结论与文档一致:本地真实墓碑在锁内比较时**不会**被旧 PUT 复活(`applyBucketConfig` 用真实落盘状态比较);但持有旧数据的对端会每 30 秒发一次过期 RPC 被拒绝,**状态永不收敛**,直到运维在权威站点重新提交删除,或全站点升级后统一开启开关。 - -也就是说:**默认配置下交付的是"顺序正确 + 不复活 + Policy 删除可 heal",不是"四类删除都能自愈"。** 这是 v4 计划的既定取舍(计划 §提交 3 表格),实现与文档都如实写了;我在此只是确保批准时看到这一点。 - ---- - -### F8 — 证据不足项 - -1. **F2 对双站点实验不可见**:`twosite/main.go:141-144` 的所有策略都经 `SRPeerReplicateBucketMeta` → `canonicalBucketPolicy` 写入,两侧都是规范字节,`states()`(`:160-164`)的字节比较自然通过。**从未构造过"升级前旧编码字节"的策略。** -2. **F1 被 stub 掩盖**:`cmd/site-replication-metadata-gate_test.go:151-159` 自定义 `GetBucketInfo`,在 `opts.NoMetadata` 时直接返回物理 Created,绕过了 `erasureServerPools.GetBucketInfo` 的缓存覆盖。`"physical-created"` 子用例证明的是一个**生产中不会发生**的行为。 -3. **"稳态 0 metadata RPC"的边界**:`twosite/main.go:182-190` 只统计 `event.Bucket == bucket` 的单桶、双站点、**gate=on**。结论有效但窄;gate=off 的稳态不为零(文档已声明),多桶/多站点未观测。 -4. **二进制身份**:`manifest.json` 给了 `silo-final` 的 sha256,但目录里没有 `--version`/build-info 输出(驱动用 `--quiet --json` 启动),`runtime-final.log` 也没有版本行。验收自述"编译信息为 `c8f264f79 + dirty`"是**诚实的**,且日志里的 gate 行为(`gate=on/off`、`off exporter exposed new tombstone`)只能来自第三个提交的生产代码,所以**没有夸大**;但也**不能从证据目录内独立复核**。补一份 `silo-final --version` 或 `go build` 复现即可闭环。 -5. **baseline.log 行号与最终测试文件不一致**(日志 93/105/117 vs 现文件 96/97/109/121):说明"修复前复现"跑的是测试文件的早期版本。可接受,但严格讲不是同一份用例。 - ---- - -### F9 — 残留边界:桶世代冲突(计划内已声明,非本轮回归) - -各站点 `Created` 不同时,A 的 baseline-live(`at == A.Created`)在 B 上会被重算为 `real`(`newBucketConfigState` 用**目标**的 `created` 判定,`cmd/bucket-metadata-replication.go:215-219`),可能压过 B 的真实墓碑。计划明确排除在收敛承诺外,且 `AddPeerClusters`(`cmd/site-replication.go:458-466`,"only one cluster may have data")封死了最常见入口。剩余入口:分区期间两站点各自建同名桶,或 #78 接管。基线版本在同场景下是**不确定**的(seed 首个 map 项),所以不算回归。 - ---- - -### F10 — 覆盖缺口:接管时 `Created` 前移越过真实修改时间 - -`rebaseBucketConfigDefaults`(`cmd/bucket-metadata-replication.go:306-318`)只调整"零/等于旧 Created"的默认时间。若 `opts.CreatedAt` 晚于某个**真实**字段时间,该字段变成 `at < Created` ⇒ `valid == false`:作为 heal 源 `candidate()` 为 false;作为 heal 目标 `incoming.valid > current.valid` ⇒ **必被覆盖**,本地真实配置被丢弃。 - -在"远端世代胜出"的语义下可以论证这是对的,但 `TestPeerBucketAdoptionRebasesOnlyDefaults`(`cmd/site-replication-metadata_test.go:394-425`)用的 shift 是 ±1h 而真实时间在 `created+2h`,**恰好没有覆盖这一情形**。建议加一个 `shift = +3h` 的子用例,把期望行为固定下来。 - ---- - -## 2. 六类配置 × 各入口 覆盖判断 - -| 配置 | 本地写 | typed peer | bulk | import | initial sync | heal | 接管 | 结论 | -|---|---|---|---|---|---|---|---|---| -| Policy | ✅ `bucket-policy-handlers.go:108/152`,hook 用 `result.meta`+`result.updatedAt` | ✅ `site-replication.go:1716-1730` | ✅ `len(item.Policy)!=0` 判"已提供" | ✅ 共同 `commitAt` + 空策略另发专用 nil 事件(`admin-bucket-handlers.go:1150-1155`) | ✅ gate 控墓碑 | ✅ | ✅ rebase | 通过(F2/F4 为附带) | -| Tags | ✅ `bucket-handlers.go:1940/2021` | ✅ `:1733-1747` | ✅ `*string != nil` | ✅ | ✅ gate | ✅ 补齐 `UpdatedAt`(旧版 heal 缺此字段) | ✅ | 通过 | -| SSE | ✅ `bucket-encryption-handlers.go:106/199` | ✅ `:1787-1801` | ✅ | ✅ | ✅ gate | ✅ | ✅ | 通过 | -| Quota | ✅ `admin-bucket-handlers.go:85-99`(删掉零值改写) | ✅ `:2032-2047` | ✅ `len(item.Quota)!=0` | ✅ | ✅ gate | ✅ 含缓存清除(`parse=false` 修复) | ✅ | 通过 | -| Versioning | ✅ `bucket-versioning-handler.go:100-116`,广播用归一后的 `result.meta` | ✅ 空=no-op | ✅ 空=no-op | ✅ 归一后落盘并广播 | ⛔ 不发(由 MakeBucketHook bootstrap + heal 对齐,与基线一致) | ✅ 按**目标** Lock 状态归一后比较 | ✅ `enablePeerBucketVersioning` 用 `localBucketConfigUpdatedAt` | 通过 | -| Object Lock | ✅ `bucket-handlers.go:1841-1852` | ✅ 空=no-op,保留 `item.Tags` legacy 回退 | ✅ 空=no-op | ✅ | ✅ | ✅ | ✅ | 通过 | - -逐条核对结论: - -- **来源时间不会变成本地 now**:六个 typed handler 全部删除了锁外 `GetXConfig()` 判旧,统一走 `updateAndParseMetadata(..., &updatedAt)`;非零 `sourceTime` 直接 `sourceTime.UTC()`(`bucket-metadata-sys.go:191-193`)。只有 `sourceTime == nil`(本地写)或为零(legacy 兼容)才分配本地单调时间。✔ -- **本地写严格单调**:`localBucketConfigUpdatedAt`(`:255-263`)保证 `> Created` 且 `> 当前字段时间`,涵盖"已有未来时间"。✔ -- **重复/乱序不保存不广播**:`applyBucketConfig` 在 `compare <= 0` 返回 `changed=false`,据此跳过 `saveMetadata` 与 `LoadBucketMetadata`;bulk 用 `changed` 汇总后一次保存。✔ - 我特别核对了所有使用 `result.meta`/`result.updatedAt` 的本地 handler:在这些路径上 `changed` 恒为 true(`localBucketConfigUpdatedAt` 使 `incoming.at` 严格更大,且 `incoming.candidate()` 恒真),所以**不会**出现"零时间 + 空载荷"被当成删除广播出去。这是一个隐式依赖,建议加一行注释或断言固定住。 -- **整桶 `.metadata.bin` 读-比较-写全在既有分布式锁内**:`updateAndParseMetadata`、`PeerBucketMetadataUpdateHandler`、import 最终提交、`PeerBucketMakeWithVersioningHandler`、CORS 路径,我逐个确认锁的获取在读之前、释放在 `saveMetadata` 之后、fan-out 在释放之后。✔ -- **无关字段 / CORS / lifecycle 不被覆盖**:所有写路径都是锁内**重新加载**后只改目标字段;import 用 `applyImportedBucketMetadata` 只按 `fields` 拷贝并 `bytes.Clone`。✔ -- **缓存快照正确**:`saveMetadata` 改收 `*BucketMetadata`,`meta.Save` 内部 `parseAllConfigs` 回写归一化结果,`sys.Set(name, *meta)` 发布的是提交后快照;调用方拿到的 `result.meta` 是同一份值拷贝,不会原地修改已发布引用。✔ -- **`parse` 被强制为 false 对缓存无害**:`Save()` 先 `parseAllConfigs`,发布到 `metadataMap` 的解析字段是新的;而"删除后 quota 解析残留"正是靠 `parse=false` 的新加载对象修掉的(`parseAllConfigs` 对空 `QuotaConfigJSON` **不会**把 `quotaConfig` 置 nil,`bucket-metadata.go:400-405`)。✔ -- **锁/返回值/错误处理**:`unlock()` + `locked=false` 模式保留;`updateAndParse`、`Update`、`Delete` 对外签名不变。✔ - ---- - -## 3. 六字段 state 比较:传递性、交换序无关、幂等收敛 - -`compareBucketConfigStates`(`bucket-metadata-replication.go:227-253`)实际是按 `(valid, real, real?at:—, real?isTombstone:—, key)` 的字典序全序。 - -- **传递 / 反对称**:是。`real == false` 时不比较 `at`(只比 key),但空 baseline 的 key 为 nil,`bytes.Compare` 使其恒最小,效果等同"空 baseline 永不获胜"。✔ -- **交换顺序无关**:`latestBucketConfig` 取全序最大值,与 map 遍历顺序无关;`applyBucketConfig` 同样只依赖全序。`TestLatestBucketConfigCandidates` 跑了全部 6 种排列。✔ -- **幂等收敛**:写入后目标状态等于源状态 ⇒ 下一轮 `compare == 0` ⇒ 不写不发。我另外手工推演了 Object Lock/Versioning 的"Enabled vs Suspended 同时间"场景,归一后双侧都不再写,稳定无振荡。✔ -- **baseline Created 与真修改的区分**:`real = valid && at.After(created)`,零字段时间先回填为 `created`(与 `defaultTimestamps()` 一致)。✔ -- **同时间删除胜出**:`:244-250`,仅在 `a.real` 分支内生效,空 baseline 不会借此删配置。✔ -- **等时不同载荷的稳定键**: - - **Policy**:递归排序的规范 JSON + `json.RawMessage` 保留整数精度(测试 `:308-311` 验证 `9007199254740993` 不失真)。我对照了 `silo-pkg .../policy/bucket-policy-statement.go:27-36`,`BPStatement` 的 8 个字段(SID/Effect/Principal/Actions/NotActions/Resources/NotResources/Conditions)**全部覆盖,无字段静默丢失**;`ParseBucketPolicyConfig` 先做 `Validate` 保证 `Principal.MarshalJSON` 不会失败。✔ 但顶层 Statement 排序引出 F2。 - - **Quota**:`json.Marshal(parseBucketQuota(...))`。零 quota 仍是 live 文档(`candidate()` 靠 `real`,不靠 `len(data)`)。`{}` 与对端重编码字节不同但键相同,不产生 heal 环;`isBktQuotaCfgReplicated` 按解析值比较,不会误报。✔ - - **Tags / SSE / Object Lock**:有效文档字节,保留大小写与实际内容。✔ - - **Versioning**:先按**该站点**的 Object Lock 归一(`effectiveBucketVersioning`),键即归一后的文档,避免"保存阶段隐式改写"造成的空转。✔ -- **空策略**:`bucketConfigPayload` 对 `cfg.IsEmpty()` 返回 `(nil, nil, nil)`,与 peer 侧既有"空策略=删除"解释统一。✔(副作用见 F4-1) -- **Object Lock 强制 Versioning 后的持久化 vs 广播**:本地只广播 Object Lock 事件,派生的 Versioning 由对端在 `Save → parseAllConfigs` 自行推导,**内容一致、时间各自保留**;heal 用归一后的有效文档比较,最多一次写入即收敛。与计划一致。✔ - ---- - -## 4. Created / 时间的异常输入 - -| 输入 | 行为 | 判断 | -|---|---|---| -| Created 为零 / metadata.bin 缺失 | 六类写入报错;初次同步静默跳过 | **F1,回归** | -| 字段时间为零 | 回填为 Created ⇒ baseline | ✔ 与 `defaultTimestamps()` 一致 | -| 事件时间 == Created | 可更新仍为 baseline 的字段,时间保存为 Created;同时间 nil/空只算空 baseline,不删除 | ✔ 有用例 | -| 事件时间 < Created | `before-created`,跳过 + 单条日志;heal 对该目标 `continue` | ✔ | -| 未来时间 | 接受为真实状态;后续本地写用 `max(now, at+1ns)` 压过 | ✔ 有用例 | -| 各站点 Created 不同 | 见 F9(计划外,已声明) | 残留 | -| 空 deployment ID | `latestBucketConfig` / `healBucketConfig` 均 `id == "" \|\| !known` 跳过 | ✔ 有用例 | -| peer 报错 / 缺元数据 | `candidate()` 为 false ⇒ 不参与选源;作为目标 `CreatedAt.IsZero()` ⇒ `continue`。**不会被当成删除** | ✔ | -| 单个 peer 不可达 | 记录后继续其它目标(`site-replication-metadata.go:182-186`),不因 map 顺序放弃健康站点 | ✔ 有用例(`"broken"`) | - -**"已知历史不可恢复"与"新实现回归"的区分**: -- 历史不可恢复(可接受):旧版到达时间污染、legacy-zero 事件产生的新本地时间、桶世代分歧。 -- 新实现回归(应修):F1 的零 Created 硬失败与初次同步静默漏发;F2 的永久假 mismatch;F3 的 ERROR 噪声与日志互相顶替。 - ---- - -## 5. 兼容路径与开关 - -- **typed 零时间兼容**:`sourceTime != nil && IsZero()` ⇒ 分配本地时间 + 一条 `legacy-zero`(`bucket-metadata-sys.go:186-190`)。不受 gate 影响,不在源时间排序保证内。✔ -- **bulk 零时间拒绝**:`PeerBucketMetadataUpdateHandler:1610-1612` ⇒ `errInvalidArgument`,与现状一致;import hook 恒带非零 `commitAt`。✔ -- **省略 vs 显式 null / 空串**:`madmin.SRBucketMeta` 全字段 `omitempty`(`madmin-go v3.0.110 cluster-commands.go:501-538`),因此 - - `json.RawMessage`(Policy/Quota):省略 ⇒ nil;显式 `null` ⇒ `[]byte("null")`,走 `len(...)!=0` 判为"已提供",再交既有解析器(Policy `null` ⇒ 语义空 ⇒ 删除;Quota `null` ⇒ 零值文档 ⇒ live)。✔ 与计划 §1 完全一致。 - - `*string`:省略/`null` ⇒ nil ⇒ 不动;空串 ⇒ 已提供且内容为空 ⇒ 四类删除、两类 no-op。✔ - - 这些由 `TestPeerBucketMetadataWireAtomicity` 通过**真实 admin 路由 + 真实 JSON 编解码**覆盖(`applySRBucketMetaViaAdmin` 走 `registerAdminRouter`),不是结构体直传。这是这批测试里质量最高的一块。 -- **direct hook 与 heal 的删除传播**:普通 DELETE 事件在 gate off 下照常复制;heal 依赖导出可见性 ⇒ 见 F7。 -- **初次同步**:保留原五类范围(不含 Versioning),gate 控制是否发送真实墓碑。✔ -- **gate 覆盖点是否遗漏**:我检查了所有导出/初次发送点 —— `SiteReplicationMetaInfo` 的 Tags/Quota/SSE 三处(`:3960/:3980/:3988`)+ `initialBucketConfigReplicationEvent:51`。Policy 墓碑**故意**不受 gate 控制(原本已导出);Versioning/Object Lock 无墓碑概念。**未发现遗漏的开关覆盖点。** ✔ -- **滚动升级/降级**:off 时新增墓碑信息不导出,旧节点不会收到它无法正确处理的 Quota heal 墓碑(文档点名了"旧版会留下已解析 quota 残留"这一实证依据)。✔ 但 off **不等于**与旧版同构:修复端的**接收**行为已经变了(锁内排序、空 baseline 不删除、重复不写),这对旧版发来的事件是更安全的方向,混版冒烟也验证了普通 PUT/DELETE 互通。降级路径文档要求先关开关再滚降;唯一可补的一句是"墓碑时间字段本来就在 schema 内,旧版只是不导出,降级不会产生解析错误"。 - ---- - -## 6. 三个独立判断 - -**最小?—— 基本是,有一处可争议的扩张。** -生产 Go 代码净增约 37 行,六份重复的 heal / peer apply 被一套 helper 替代;没有新 schema、没有新 wire 字段、没有新锁、没有能力协商、没有迁移系统。唯一超出必要的是 **Policy 规范编码器接入写/读/导出路径**(F5)——它不是收敛所必需的,并且直接导致 F2。其余(`bucketMetadataUpdate` 提交快照、`ensureBucketMetadataCreated`、`rebaseBucketConfigDefaults`、gate)我都能各自对应到一个已证实的缺陷或本次修复直接触及的路径,**没有**可以无损删除的部分。 - -**充分?—— 对"已声明的范围"是;对"缺陷标题"不是。** -在同一桶世代、Created 已知、gate=on 的前提下,六类配置的源时间、锁范围、删除状态参与 heal、等时冲突裁决、重复/乱序抑制都成立。但两个口子要明说:(1) 默认 gate=off ⇒ Tags/SSE/Quota 的漏发删除不收敛(F7,计划内取舍);(2) Created 未知的桶从"能写"变成"不能写"(F1,计划外回归)。 - -**必要?—— 是。** -每一项改动都能对应到 `baseline.log` 里的实测失败(SOURCE_TIME / NEWER_DELETE / STALE_RESURRECTION / BULK_STALE_OVERWRITE / CHECK_OUTSIDE_LOCK)或计划中论证过的路径。`rebaseBucketConfigDefaults` 这种看起来最"多余"的小分支,实际是 `Created` 一旦参与 baseline/tombstone 判定后的必然补丁(不加则接管时默认值会变成假墓碑)。 - ---- - -## 7. 新增设计记录应准确保留的内容 - -**关键决策** -1. baseline / live / tombstone 三态由 `(Created, 字段时间, 载荷空否)` 推导,**不新增 schema 字段**;`at == Created` 定义为 baseline。代价:必须知道 Created(⇒ F1 的根因)。 -2. 全序:`valid > real > at > 同时间墓碑胜 > 稳定内容键`。deployment ID **不**参与比较,也不落盘。 -3. 比较键是"已解析配置的纯函数":Policy 走递归排序的规范 JSON,Quota 走解析后 `json.Marshal`,XML 走有效文档字节,Versioning 先按本站点 Object Lock 归一。 -4. Versioning / Object Lock 为 update-only:空事件恒为 no-op,空值不是候选。 -5. 读-比较-写整体在**既有** `metadata.lock` 内;`saveMetadata` 收指针以便调用方拿到提交后快照,保证"落盘状态 / 出站事件 / 源时间"三元组一致。 -6. 新增删除信息的**导出**用启动开关 gate,默认 off;开关不探测远端能力,启用条件是全站点全节点已升级且旧请求排空。 -7. 专用 peer 事件的零时间保留兼容例外(分配本地时间 + 限频 `legacy-zero`);bulk 零时间仍按现状拒绝。 - -**被拒绝的方案**(记录以免复议) -- 新增 HLC / 向量时钟 / 新 wire 字段 / 能力协商 / 通用复制框架 / 新锁或重试系统。 -- 用 `len(payload)==0 ⇒ 删除` 统一处理 bulk(会把"省略"误判为删除)。 -- 把 `BucketPolicy.Equals`(忽略 Sid、对 Statement 顺序敏感)叠加为第二套判等规则。 -- 把混版长期测试作为提交门槛。 -- 把未修改上游 MinIO 的兼容性当作必需门禁(AGENTS.md:正式支持 PGSTY 栈,上游兼容为尽力而为)。 - -**限制** -- gate=off 期间 Tags/SSE/Quota 墓碑不可见,不承诺删除收敛,且存在被拒绝的周期性 RPC。 -- 旧版到达时间污染、legacy-zero 产生的新本地时间、桶创建世代分歧**无法自动反推**,需运维在权威站点重新提交。 -- `Created` 未知的桶不在收敛承诺内(并且按当前实现直接不可写,见 F1)。 -- "比较键相同但落盘字节不同"(Quota 原始 JSON、Policy 旧编码)是允许的稳定状态,公开 mismatch 统计必须与比较键口径一致(见 F2)。 - ---- - -## 8. 建议处置顺序 - -1. 判定 F1 的条件(支持矩阵里有没有 `Created == 0` / 无 `.metadata.bin` 的桶)。有 ⇒ 先修再合。 -2. 修 F2(推荐:不排序顶层 `Statement`,或让 `isBktPolicyReplicated` 改用规范键)。 -3. 修 F3(ERROR→Warning、缺桶不报、reason 拆分)。 -4. 补 F4 的三条文档。 -5. 补测试:F10 的接管用例、F8-1 的 legacy 策略字节双站点用例、F8-2 去 stub 的 Created 用例、F8-4 的二进制身份记录。 -6. F5 / F6 由作者判断,可留作后续。 diff --git a/docs/investigations/issue-77/implementation-review/round-2/confirmed-after-race.log b/docs/investigations/issue-77/implementation-review/round-2/confirmed-after-race.log deleted file mode 100644 index da9907b66..000000000 --- a/docs/investigations/issue-77/implementation-review/round-2/confirmed-after-race.log +++ /dev/null @@ -1 +0,0 @@ -ok github.com/minio/minio/cmd 6.864s diff --git a/docs/investigations/issue-77/implementation-review/round-2/confirmed-after.log b/docs/investigations/issue-77/implementation-review/round-2/confirmed-after.log deleted file mode 100644 index 96eff9cb0..000000000 --- a/docs/investigations/issue-77/implementation-review/round-2/confirmed-after.log +++ /dev/null @@ -1 +0,0 @@ -ok github.com/minio/minio/cmd 20.694s diff --git a/docs/investigations/issue-77/implementation-review/round-2/confirmed-before.log b/docs/investigations/issue-77/implementation-review/round-2/confirmed-before.log deleted file mode 100644 index 59e27bb00..000000000 --- a/docs/investigations/issue-77/implementation-review/round-2/confirmed-before.log +++ /dev/null @@ -1,57 +0,0 @@ ---- FAIL: TestBucketMetadataPhysicalCreatedRecovery (0.57s) - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/object-lock.xml/missing=false (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update object-lock.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/versioning.xml/missing=false (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update versioning.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/policy.json/missing=false (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update policy.json: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/tagging.xml/missing=false (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update tagging.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/bucket-encryption.xml/missing=false (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update bucket-encryption.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/quota.json/missing=false (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update quota.json: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/object-lock.xml/missing=true (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update object-lock.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/versioning.xml/missing=true (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update versioning.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/policy.json/missing=true (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update policy.json: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/tagging.xml/missing=true (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update tagging.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/bucket-encryption.xml/missing=true (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update bucket-encryption.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/ErasureSD/quota.json/missing=true (0.00s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update quota.json: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/object-lock.xml/missing=false (0.02s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update object-lock.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/versioning.xml/missing=false (0.02s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update versioning.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/policy.json/missing=false (0.02s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update policy.json: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/tagging.xml/missing=false (0.02s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update tagging.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/bucket-encryption.xml/missing=false (0.06s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update bucket-encryption.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/quota.json/missing=false (0.02s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update quota.json: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/object-lock.xml/missing=true (0.04s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update object-lock.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/versioning.xml/missing=true (0.02s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update versioning.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/policy.json/missing=true (0.02s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update policy.json: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/tagging.xml/missing=true (0.01s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update tagging.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/bucket-encryption.xml/missing=true (0.02s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update bucket-encryption.xml: bucket metadata creation time is unknown - --- FAIL: TestBucketMetadataPhysicalCreatedRecovery/Erasure/quota.json/missing=true (0.02s) - site-replication-metadata-gate_test.go:227: bucket without a recorded creation time cannot update quota.json: bucket metadata creation time is unknown ---- FAIL: TestBucketPolicyReplicationStatusLegacyOrder (0.31s) - --- FAIL: TestBucketPolicyReplicationStatusLegacyOrder/ErasureSD (0.01s) - site-replication-metadata_test.go:348: equivalent legacy and received policy reported as permanently mismatched - --- FAIL: TestBucketPolicyReplicationStatusLegacyOrder/Erasure (0.06s) - site-replication-metadata_test.go:348: equivalent legacy and received policy reported as permanently mismatched -FAIL -FAIL github.com/minio/minio/cmd 2.940s -FAIL diff --git a/docs/investigations/issue-77/implementation-review/round-2/confirmed-diagnostics-before.log b/docs/investigations/issue-77/implementation-review/round-2/confirmed-diagnostics-before.log deleted file mode 100644 index 64db824f0..000000000 --- a/docs/investigations/issue-77/implementation-review/round-2/confirmed-diagnostics-before.log +++ /dev/null @@ -1,8 +0,0 @@ ---- FAIL: TestHealBucketConfigDiagnostics (0.29s) - --- FAIL: TestHealBucketConfigDiagnostics/ErasureSD (0.00s) - site-replication-metadata-heal_test.go:80: empty baselines produced diagnostics - --- FAIL: TestHealBucketConfigDiagnostics/Erasure (0.01s) - site-replication-metadata-heal_test.go:80: empty baselines produced diagnostics -FAIL -FAIL github.com/minio/minio/cmd 2.721s -FAIL diff --git a/docs/investigations/issue-77/implementation-review/round-2/initial-sync-before.log b/docs/investigations/issue-77/implementation-review/round-2/initial-sync-before.log deleted file mode 100644 index 8ff175164..000000000 --- a/docs/investigations/issue-77/implementation-review/round-2/initial-sync-before.log +++ /dev/null @@ -1,8 +0,0 @@ ---- FAIL: TestBucketMetadataInitialSyncPhysicalCreated (0.47s) - --- FAIL: TestBucketMetadataInitialSyncPhysicalCreated/ErasureSD (0.04s) - site-replication-metadata-gate_test.go:289: peer creation time "0001-01-01T00:00:00Z", want physical time 2026-09-12 06:03:09 +0000 UTC - --- FAIL: TestBucketMetadataInitialSyncPhysicalCreated/Erasure (0.08s) - site-replication-metadata-gate_test.go:289: peer creation time "0001-01-01T00:00:00Z", want physical time 2026-09-12 06:03:09 +0000 UTC -FAIL -FAIL github.com/minio/minio/cmd 2.576s -FAIL diff --git a/docs/investigations/issue-77/implementation-review/round-2/no-source-before.log b/docs/investigations/issue-77/implementation-review/round-2/no-source-before.log deleted file mode 100644 index dc965a221..000000000 --- a/docs/investigations/issue-77/implementation-review/round-2/no-source-before.log +++ /dev/null @@ -1,18 +0,0 @@ ---- FAIL: TestHealBucketConfigWithoutSourceDiagnostics (0.36s) - --- FAIL: TestHealBucketConfigWithoutSourceDiagnostics/ErasureSD (0.00s) - --- FAIL: TestHealBucketConfigWithoutSourceDiagnostics/ErasureSD/unknown-created (0.00s) - site-replication-metadata-heal_test.go:155: got 0 diagnostics, want 1: [] - --- FAIL: TestHealBucketConfigWithoutSourceDiagnostics/ErasureSD/malformed (0.00s) - site-replication-metadata-heal_test.go:155: got 0 diagnostics, want 1: [] - --- FAIL: TestHealBucketConfigWithoutSourceDiagnostics/ErasureSD/before-created (0.00s) - site-replication-metadata-heal_test.go:155: got 0 diagnostics, want 1: [] - --- FAIL: TestHealBucketConfigWithoutSourceDiagnostics/Erasure (0.01s) - --- FAIL: TestHealBucketConfigWithoutSourceDiagnostics/Erasure/unknown-created (0.00s) - site-replication-metadata-heal_test.go:155: got 0 diagnostics, want 1: [] - --- FAIL: TestHealBucketConfigWithoutSourceDiagnostics/Erasure/malformed (0.00s) - site-replication-metadata-heal_test.go:155: got 0 diagnostics, want 1: [] - --- FAIL: TestHealBucketConfigWithoutSourceDiagnostics/Erasure/before-created (0.00s) - site-replication-metadata-heal_test.go:155: got 0 diagnostics, want 1: [] -FAIL -FAIL github.com/minio/minio/cmd 2.430s -FAIL diff --git a/docs/investigations/issue-77/implementation-review/round-2/opus5-max-review.md b/docs/investigations/issue-77/implementation-review/round-2/opus5-max-review.md deleted file mode 100644 index 9afb1a34e..000000000 --- a/docs/investigations/issue-77/implementation-review/round-2/opus5-max-review.md +++ /dev/null @@ -1,174 +0,0 @@ -# SILO #77 修正提交独立复审(固定 `62cf066ff529c7d281703daa365f555cebba717a`) - -审查方式:只读。以 worktree 生产源码为准(不只读 diff),交叉核对 `silo-pkg v3.13.4-0.20260910091716` 依赖源码、证据目录与设计记录。上一轮报告只作为待验证的命题,不作为已确认结论。 - ---- - -## 0. 结论 - -**GO_WITH_NONBLOCKING_NOTES** - -- **阻断项:0**(无条件 0,条件性 0) -- 上一轮的条件性阻断 F1 与 S2 级 F2 均已在生产路径上真正关闭,并有"最终测试 + 旧生产代码覆盖"的复现证据(`round-2/confirmed-before.log`)。 -- 新增 8 项非阻断发现(NB-1 ~ NB-8),其中 3 项是行为残留(S3/S4),5 项是证据与文档表述准确性问题。 -- 一处需要纠正上一轮的**判断**(不是纠正实现):F5 中"Policy 规范编码器接入 GET/export/peer 属于超出必要的扩张"这一说法不成立——这三个调用点持有的是**已解析策略**,必须有编码器,而旧编码器对解析器已支持的负集合策略**必然报错**。真正"可选"的只有 PUT/import 两处,且因 `DisallowUnknownFields` 而无损。 - ---- - -## 1. F1–F10 关闭状态 - -| 项 | 上轮判定 | 本轮核实 | 状态 | -|---|---|---|---| -| F1 零 Created 桶六类配置写入硬失败、初次同步静默漏发 | S1 条件阻断 | 生产路径已修复并有真实 ObjectLayer + 旧码覆盖复现 | **关闭**(残留见 NB-1) | -| F2 Policy 状态口径与收敛键冲突导致永久假 mismatch | S2 | `isBktPolicyReplicated` 改用同一 canonical 键,per-site presence 不变,无 SID/ID/Condition/未知值回归 | **关闭** | -| F3 heal 诊断 ERROR 级、key 互相顶替、正常瞬态误报 | S3 | ERROR→Warning、四类原因各自持 key、缺桶不报 = 已关闭;"全无有效来源即完全静默"与"按桶×字段扇出"未处理 | **部分关闭**(NB-2、NB-3) | -| F4 三处语义变更未文档化 | S3 | 上轮前两项系误读(原提交 README 已有,见 diff 中为 context 行);第三项已补入 `docs/site-replication/README.md:129-134` | **关闭 + 原误读已纠正** | -| F5 Policy 规范编码器接入面 | 最小性争议 | 判断更新:GET/export/peer 非可选;PUT/import 可选但无损。建议保留现状 | **关闭(判断修正)** | -| F6 死代码与过时注释 | nit | 注释已修(`bucket-metadata-replication.go:35-37`);`isBucketMetadataEqual` 仍为测试专用 | **关闭(保留 nit,见 NB-5)** | -| F7 gate=off 不承诺四类删除自愈 | 范围说明 | README:75-89、设计记录 §rollout/§limits 均如实声明,未说成已自动修复 | **保持(如实)** | -| F8 证据缺口(stub / 旧顺序策略 / 二进制身份) | 证据不足 | stub 已去除、旧顺序用例已补、二进制身份已闭环(`--version` 含 commit-id + 对照基线二进制) | **关闭**(但 NB-4:两份旧日志不可用作证据) | -| F9 桶世代冲突 | 范围外 | 未被声称已修;设计记录 §limits-3 明确 | **保持(如实)** | -| F10 接管 Created 越过真实字段时间 | 覆盖缺口 | 补 `shift=3h` 子用例,钉住"旧世代状态不得升级为有效来源" | **关闭(半边,见 NB-7)** | - ---- - -## 2. 本轮重点逐项核实 - -### F1 —— 真正修好了,机制比上轮建议更完整 - -**`NoMetadata=true` 返回物理探测**(`cmd/erasure-server-pool.go:2279-2283`)。我枚举了全仓库 `NoMetadata` 的全部生产调用点,只有三处: - -- `cmd/bucket-metadata-sys.go:272`(`saveMetadata` 存在性重查,只看 error) -- `cmd/bucket-metadata.go:306`(迁移锁内存在性重查,只看 error) -- `cmd/bucket-metadata-replication.go:281`(新增的 Created 恢复,正是要物理值) - -其余全部是 `BucketOptions{}`(`NoMetadata=false`),行为不变。被跳过的 `globalBucketMetadataSys.Get()`(`bucket-metadata-sys.go:410-424`)是**纯缓存读、无副作用、不触发加载**,因此跳过它不会改变任何缓存预热或懒加载行为。`Versioning`/`ObjectLocking` 两个字段也只在非 NoMetadata 分支填,与 `ListBuckets`(`:2402/2422` 只在 `!NoMetadata` 时覆盖 Created)语义一致。**无调用者回归。** - -**世代一致性链路完整**(这一点比上轮提出的方案更强,值得记录): -`syncToAllPeers`(`site-replication.go:2145-2153`)恢复 `meta.Created` → 传入 `MakeBucketOptions.CreatedAt` → `MakeBucketHook`(`:821-826`)在缓存值为零时回落到 `opts.CreatedAt`(此时本地磁盘仍是 0,所以这个回落是必需的)→ **本地**分支 `PeerBucketMakeWithVersioningHandler`(`:943-966`)在 `metadata.lock` 内 `SetCreatedAt(opts.CreatedAt)` + `rebaseBucketConfigDefaults` + `saveMetadata`,**远端**分支通过 `optsMap["createdAt"]` 收到同一个值。也就是说本地并非"只在内存里补一下",而是与全部对端落盘同一个世代值。这条链路 diff 上看不出来,必须读 `MakeBucketHook` 的 `concDo` 第一个闭包才能确认。 - -四个恢复点的锁位置也确认无误:写路径(`bucket-metadata-sys.go:186`,锁内)、bulk(`site-replication.go:1674`,锁内)、import(`admin-bucket-handlers.go:1092`,锁内)、初次同步(锁外探测但不落盘,落盘由上面的钩子在锁内完成)。 - -**"无法恢复时显式错误"是否正确:正确。** 静默发明 `UTCNow()` 会造出一个不可撤销、且会向全站点传播的假世代;而显式失败保留原数据、可重试、并留一条有界 `indeterminate`。`syncToAllPeers` 因此让整个 `AddPeerClusters` 失败——这在真实文件系统上基本不可达(`StatVol` 用目录 `ModTime`,存在即非零),且 `AddPeerClusters` 是可重试的交互式管理操作,代价可接受。 - -**真实用例是否覆盖生产路径:是。** `TestBucketMetadataPhysicalCreatedRecovery`(`site-replication-metadata-gate_test.go:206-237`)通过 `globalBucketMetadataSys.save` 让**缓存持有 Created=0**,再走 `Update` → `updateAndParseMetadata` → `ensureBucketMetadataCreated` → 真实 `erasureServerPools.GetBucketInfo`;`ExecObjectLayerAPITest`(`test-utils_test.go:1766/1795`)两个后端都是真实 `erasureServerPools`,因此缓存覆盖这一段是真跑到的。`setPhysicalBucketCreated` 直接 `os.Chtimes` 每块盘的桶目录,正是 `xl-storage.go:1022-1028` 取值的来源。六类配置 × `missing=true/false` 全覆盖。 - -### F2 —— 关闭,且口径现在六类全部自洽 - -`site-replication.go:3782-3809`。状态侧的 `policies[i]` 来自 `:3436` 对**各站点原始字节的解析**,与 heal 的 `bucketConfigPayload`→`canonicalBucketPolicy`(`bucket-metadata-replication.go:191`)是同一个纯函数,因此 peer 重排 Statement 后的永久假 mismatch 被彻底关闭。`numPolicies != total` 的 per-site presence 计数原样保留(`:3783-3792`),并有断言覆盖。 - -回归面逐条查过: - -- **SID**:旧 `BPStatement.Equals`(silo-pkg `bucket-policy-statement.go:148-171`)**不比较 SID**,新键包含 `Sid`。这是方向正确的收紧——heal 的收敛键同样含 SID,所以状态报出的 mismatch 是**真实且会被 heal 自动消除**的,不是永久噪声。用例已钉住。 -- **ID / Version**:旧 `Equals` 比较二者,新键在 `ID != ""` 时包含、`Version` 始终包含,等价。 -- **Condition**:两侧都走同一 `condition.Functions` 编码再递归排序,顺序无关。 -- **未知值**:`ParseBucketPolicyConfig` 用 `decoder.DisallowUnknownFields()`(`bucket-policy.go:173`),未知字段**根本无法通过解析**,因此不存在"键把未建模字段静默吃掉"的风险。 -- `isIAMPolicyReplicated`(`:3665-3681`)仍用 `Equals`,那是 IAM 策略、不同子系统,**不应**一并改动。正确地没动。 - -新旧行为是"收紧而非放宽",与收敛契约一致,判断合理。 - -### F3 —— 关闭了会吃日志的部分,但静默面仍偏大 - -已关闭:四种原因各自持 key(`site-replication-metadata.go:145/158/164/189`),真实 heal RPC 失败不再被"缺 peer"顶掉;`logger.WarningKind`(`:42`);对端"尚未拥有该桶"(`CreatedAt=0` 且载荷空、时间零)不再报。`siteReplicationStatus` 对不可达 peer 填 `DeploymentID=""`(`:3029`)落进 `BucketStats[bucket][""]`,而循环 1 按 `info.Sites` 的真实 ID 判缺失、循环 2 跳过 `id==""`,两条路径正交,语义正确。README:112-119 与实现完全一致。 - -未关闭的两点见 NB-2、NB-3。另外,"仅报告确实存在、但仍无法排序的状态"(`:157`)这个条件写得准确:`!current.valid && (len(data)!=0 || !at.IsZero())` 恰好把 F9/F10 的世代冲突暴露出来,这是意外的正收益。 - -### F5 —— 上一轮的最小性判断需要修正 - -`canonicalBucketPolicy` 的六个生产接入点应分成三类,而不是笼统的"超出必要": - -| 接入点 | 性质 | -|---|---| -| `bucket-metadata-replication.go:191`(比较键) | **必需**,#77 的核心 | -| `site-replication.go:3802`(状态键) | **必需**,F2 的修复本体 | -| `bucket-policy-handlers.go:202`(GET)、`admin-bucket-handlers.go:438`(export)、`site-replication.go:1723`(peer apply) | **非可选**:这三处拿到的都是 `*policy.BucketPolicy`(`globalPolicySys.Get` / `GetBucketPolicy` / `admin-handlers-site-replication.go:228` 解析后传入),必须有编码器;而旧的 `json.Marshal` 对 `NotAction`/`NotResource` 语句**必然失败**——`BPStatement.Actions` 无 `omitempty`(`bucket-policy-statement.go:31`)+ `ActionSet.MarshalJSON` 空集报错(`actionset.go:144-148`)。bulk/import/peer 都能把这类策略落盘,所以缩回任何一处都会重新制造"写得进、读不出/复制不出" | -| `bucket-policy-handlers.go:102`(PUT)、`admin-bucket-handlers.go:922`(import) | **可选的规范化**:比较键不依赖落盘字节。但因为解析器 `DisallowUnknownFields`,规范化**无信息丢失**(唯一差别是重复语句在盘上也被去重,语义等价),而所有读路径都已重新编码,用户观察不到"原样字节"。删掉它只换来与客户端提交字节的一致性,且会让本地写与 peer 写产生两种盘上形态 | - -**结论:不建议缩回任何一处。** 真正可删的只有 PUT/import 两处,收益为零、改动风险非零。上一轮把这部分列为"范围外扩张"的说法应当被本轮判断取代;它是一个被同一改动暴露出来的真实读回缺陷的修复,且已在 README:129-134 声明。 - ---- - -## 3. 新发现(全部非阻断) - -### NB-1(S3)恢复出的物理 Created 不确定,且可能晚于对端事件的来源时间 - -- **位置**:`cmd/bucket-metadata-replication.go:277-290`;取值链 `cmd/peer-s3-client.go:324-328`(取第一个不报错的 peer)→ `cmd/peer-s3-server.go:207-258`(`cloneDrives` 遍历 **Go map**,取第一个不报错的盘)→ `cmd/xl-storage.go:1022-1028`(`Created = st.ModTime()`)。 -- **最短触发**:桶无 `.metadata.bin`;其顶层目录 mtime 因近期对象写入被推进到接近 now;对端发来源时间早于该 mtime 的事件 → `bucket-metadata-sys.go:198-201` 判 `before-created` → **返回 nil、不写、不报错**,仅一条每小时去重的 warning,直到有人在源站重新提交。 -- **影响**:(a) 恢复值不是桶的稳定属性(目录 mtime 随顶层条目增删前进,且逐盘不同);(b) 两个站点各自独立恢复时会落进不同世代。二者都落在已声明的 F9 限制内,且相对基线(基线给每个站点各自 `UTCNow()`)是改善;本地侧因 `metadata.lock` 串行化 + 首写落盘,站内不会分裂。真正新的只有"首个跨站事件可能被静默跳过"。 -- **最小修复(可选)**:让 `ensureBucketMetadataCreated` 返回"本次是否为恢复值",当 `sourceTime` 非零且早于恢复值时取 `min(recovered, sourceTime)`——"在时刻 T 已持有配置的桶必然在 T 已存在",约 4 行,不新增字段。或退一步:只在 README 里把恢复值明确为"物理近似值,可能晚于真实创建"。 -- **需补证据**:一个用例——缓存 `Created=0`、`os.Chtimes` 把桶目录设为 now、通过 admin 路由投递 `UpdatedAt = now-1h` 的 peer 事件,断言期望行为(写入或明确跳过),把当前语义钉死。 - -### NB-2(S3)`!found` 时对畸形/不可排序状态完全静默 - -- **位置**:`cmd/site-replication-metadata.go:134-139`。 -- **最短触发**:全部站点的同一字段都无法成为候选(例如各站点都存着一份 base64 可解但内容非法的 XML,或全部世代未知)。`latestBucketConfig`(`:117-120`)对 `err != nil` 静默 `continue`,`healBucketConfig` 直接 `return nil`——**没有任何诊断**。而状态导出侧的 `logInvalid`(`site-replication.go:3375-3378`)只覆盖 base64 失败和 policy/quota/replication 的解析失败,XML 类内容非法不在其中,于是这种状态在两条路径上都无声。 -- **影响**:字段永久不可 heal 且无信号。范围窄(要求所有站点同时不可用),但属于"必要诊断"缺口。 -- **最小修复**:把每目标的 `bucketConfigStateFromInfo` 结果算一遍并缓存(当前每周期每目标实际解码两次:`latestBucketConfig` 一次、主循环一次),把 `currentErr != nil` 的 `indeterminate` 移到 `if !found` 之前。既补诊断又减一次解码,不引入新框架。 -- **需补证据**:`healBucketConfig` 单测——两个站点都给非法载荷,断言返回 nil 且产生一条 `indeterminate`(可用现有 `srStatusInfo` 夹具,参照 `site-replication-metadata-heal_test.go:136-143`)。 - -### NB-3(S4)`unreachable` 诊断按"桶 × 字段"扇出 - -- **位置**:`cmd/site-replication-metadata.go:143-147`,被 `cmd/site-replication.go:4750-4760` 每桶调用六次。 -- **最短触发**:一个站点掉线。每桶产生 6 个不同 key → 10k 桶集群每小时约 6 万条 warning。 -- **影响**:只是噪声(`logOnceIf` 按小时清理,仍算"有界"),但一个站点级事实被放大成桶×字段级输出。 -- **最小修复**:把循环 1 上提到 `healBuckets` 里、每桶调用一次(key 去掉 `file` 段),或干脆每周期一次。改动 <15 行。 -- **需补证据**:不需要新证据,行数/键数可直接核对。 - -### NB-4(S4,证据)两份旧日志是草稿测试产物,不能作为产品失败证据;设计记录"三个回归测试"应为两个 - -- **位置**:`findings-before.log:3`、`findings-after-1.log:3` 均为 `site-replication-metadata-gate_test.go:230: real physical creation time unavailable`。该消息在最终测试文件中**不存在**,最终断言在 **227** 行、消息是 `bucket without a recorded creation time cannot update %s: %v`。同理 `findings-before.log:52` 失败在 SID 断言(`:353`),而最终文件 SID 断言在 `:355`、`:353` 是 `t.Fatal(err)`。 -- **判断**:这两份日志跑的是早期草稿;其中 "real physical creation time unavailable" 更像**测试自身的前置检查**失败,不能用来证明产品缺陷。真正成立的是 `round-2/confirmed-before.log`:它用**最终测试**叠加 `before-erasure-server-pool.go` / `before-site-replication.go`(我已核对这两份确实是修正前版本:无 `NoMetadata` 早返回、`MakeBucketHook` 无回落、`syncToAllPeers` 仍用 `bucketInfo.Created`、`isBktPolicyReplicated` 仍是 `prev.Equals`),行号 227/348 与最终文件精确吻合,失败文本是真实产品错误 `bucket metadata creation time is unknown` 与 `equivalent legacy and received policy reported as permanently mismatched`,两后端 × 六类 × missing 双态全覆盖;`confirmed-after.log` 通过。 -- **另一项**:设计记录第 166 行"三个回归测试在未修复代码上失败、修复后通过"不准确。仓库中只有**两个**这样的测试;F3(heal 诊断)**没有任何自动化回归**(我在全部 `*_test.go` 中检索 `unreachable`/`peer-error`/`unusable peer`/`logBucketConfigReplication`,无命中)。F3 的唯一证据是 `runtime-after-review.log:20` 的驱动自述"40 exceptional events produced exactly one log per reason"。 -- **最小修复**:把该行改为"两个回归测试 + 一次运行时观察",并在证据索引里把 `findings-before.log` / `findings-after-1.log` 标注为 superseded 草稿。 -- **需补证据(可选)**:给 F3 补一个日志断言用例(注入 logger target 或计数 hook),否则 F3 的修复在回归套件里是无保护的。 - -### NB-5(S4)不要为死代码创造兼容理由 - -`isBucketMetadataEqual`(`cmd/site-replication.go:5172-5181`)仅被 `site-replication_test.go:156`、`bucket-cors-site-replication_test.go:484` 引用。代码里没有编造理由(很好),但设计记录第 186 行写"作为上游血缘保留"是一个不成立的说法。**最小修复**:改成"目前仅测试引用,暂不删除以缩小 diff",或连同两处测试一并删除。 - -### NB-6(S4)README 对恢复发生位置的描述偏保守 - -`docs/site-replication/README.md:106-110` 说"That recovery happens on the write path … 第一次配置写入(本地或复制而来)会记录物理时间"。实际上 `AddPeerClusters` 的初次同步(`site-replication.go:2145` → `MakeBucketHook` → 本地 `PeerBucketMakeWithVersioningHandler`)也会在本地落盘该时间。这是**低估**不是夸大,但会误导运维判断"何时脱离跳过状态"。**最小修复**:补半句"或在把该桶纳入站点复制时"。 - -### NB-7(S4)F10 只钉住了"来源"半边 - -`cmd/site-replication-metadata_test.go:473-483` 断言接管把 Created 调晚后,越过的真实字段**不能成为来源**(`!state.candidate()`)。但上一轮指出的另一半——该字段作为 **heal 目标**时,`current.valid=false` 使任何合法 incoming 无条件胜出(`site-replication-metadata.go:174` + `bucket-metadata-replication.go:239-245`)——没有钉住。我确认这半边行为存在且在"远端世代胜出"语义下是自洽的,本地用户 PUT 也总能覆盖回来(`localBucketConfigUpdatedAt` 保证 `> Created`),**不是缺陷**,但期望值应当被固定。**最小修复**:同一子用例再加一次 `applyBucketConfig(&got, file, <远端载荷>, created+shift+1m)`,断言返回 `changed=true`。 - -### NB-8(S4)恢复失败时的诊断丢掉了事件来源时间 - -`cmd/bucket-metadata-sys.go:187` 传 `time.Time{}` 作为 `at`,即使本次是携带 `sourceTime` 的 peer 事件,日志里也显示 `0001-01-01`。**最小修复**:`sourceTime != nil` 时传 `*sourceTime`(一行)。 - ---- - -## 4. 证据分级 - -| 证据 | 评价 | -|---|---| -| `round-2/confirmed-before.log` + `confirmed-after.log` + `before-*.go` 覆盖文件 | **足够**。最终测试 × 旧生产代码,行号与失败文本可逐条核对,F1/F2 为已证实的产品失败 | -| `binary-identity-after-review.json` | **足够**。`silo-final --version` 含 `commit-id=62cf066ff…`,并附基线 `5c5765816` 对照二进制与双方 sha256,上一轮 F8-4 的缺口已闭合 | -| `final-cmd-after-review.log`(cmd 全包 622s, EXIT=0)、`ci-lint-after.log`(0 issues, 品牌基线未变)、`ci-verify-after.log`、`ci-gen/internal/s3select/crosscompile-after.log` | **足够**作为"未引入回归"的门禁证据(只截尾行,可接受) | -| `findings-before.log`、`findings-after-1.log` | **不可用**。草稿测试产物,行号与失败文本均与最终测试不符(见 NB-4) | -| `findings-after.log` | 仅"ok",无信息量,被 `confirmed-after.log` 覆盖 | -| `runtime-after-review.log` | **作者自述为主**。日志由作者的双站点驱动产生,断言("six field states equal""zero metadata RPCs""exactly one log per reason")都由驱动自己判定。时间戳 06:01:50 与最终二进制构建时间 06-01-35Z 吻合,身份链成立;但它是端到端观察,不是独立验证。F3 目前**只有**这一条证据 | - ---- - -## 5. 设计记录(`bucket-metadata-convergence.zh.md`)的准确性 - -总体克制,未发现把限制说成已修复的地方:§rollout(gate=off 不承诺四类删除收敛)、§limits-1/2/3(历史污染、零时间事件、桶世代冲突)都如实写成限制;第 20 行明确"不能用来证明现有下载包或线上实例已具备这些能力";第 161 行主动声明交叉编译 ≠ 六平台运行验收。需要修正的只有三处,均已在上面列出: - -1. 第 166 行"三个回归测试" → 两个回归测试 + 一次运行时观察(NB-4)。 -2. 第 186 行 `isBucketMetadataEqual`"上游血缘保留"是编造的理由(NB-5)。 -3. 第 181 行"与 `ListBuckets` 一致"成立(`ListBuckets` 在 `NoMetadata` 时同样不覆盖 Created),但第 185 行把 F5 记为"有意扩张,不是缺陷"仍沿用了上一轮的定性——按本轮判断,GET/export/peer 三处属于**必需**,建议改写,否则会给未来的人留下"这块可以缩回"的错误暗示。 - ---- - -## 6. 最小性 / 充分性 / 必要性(独立评价) - -**最小性:是。** 本轮修正净增很小,且每一处都能对应到一个已复现的失败或一次被证实的误读:`NoMetadata` 早返回 5 行、`MakeBucketHook` 回落 3 行、`syncToAllPeers` 恢复 4 行、`isBktPolicyReplicated` 换键约 10 行、heal 诊断重排约 15 行、`isReplicatedBucketConfig` 具名化(消除"用一次性零值结构体探测类型"的晦涩写法)、README 两段。没有新 schema、新 wire 字段、新锁、新框架。我找不到可以无损删除的部分——唯一"可删"的 `canonicalBucketPolicy` PUT/import 接入是上一轮遗留,删掉反而制造两种盘上形态。 - -**充分性:对已声明契约充分,对两个边缘不充分且已如实声明。** 在"同一桶世代、Created 已知或可恢复、gate=on"范围内,六类配置的来源时间、锁范围、删除参与 heal、等时裁决、重复/乱序抑制、状态口径,我没有找到反例。不充分的两处都写进了文档:gate=off 下 Tags/SSE/Quota 漏发删除不收敛(F7);跨站点桶世代分歧不自动合并(F9,NB-1 属于它的子集)。F3 的静默面(NB-2)是本轮唯一一个"文档声明了、但我认为声明本身偏宽"的地方——"没有任何站点可供传播的字段就不产生诊断"对畸形配置而言不该成立。 - -**必要性:是。** F1 与 F2 都有"最终测试 + 修正前生产代码"的复现(`confirmed-before.log`),是确证的产品失败,不是理论推演;F3 的必要性来自 `logOnceIf` 按 key+正文去重这一可验证机制,虽无自动化回归但代码级推理成立;F4/F8/F10 是文档与证据补齐,成本近零。F5 的编码器接入经本轮重新论证后,从"可争议扩张"变为"其中三处必需、两处无损",必要性判断上调。 - -**处置建议(均非阻断,可合并后处理)**:NB-4 与 NB-5 是文字修正,建议合并前一并改掉(它们会影响后人对证据强度的判断);NB-2、NB-8 可合成一个约 20 行的提交;NB-1、NB-3、NB-6、NB-7 可进 backlog。 diff --git a/docs/investigations/issue-77/implementation-review/round-2/session.json b/docs/investigations/issue-77/implementation-review/round-2/session.json deleted file mode 100644 index 65abff9b0..000000000 --- a/docs/investigations/issue-77/implementation-review/round-2/session.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "requested_model": "claude-opus-5", - "requested_effort": "max", - "head": "62cf066ff529c7d281703daa365f555cebba717a", - "prior_review_head": "4089113e3edbd21a29be8e6af74662462bcc22af", - "started_at": "2026-09-12T08:46:55.400524+00:00", - "status": "completed", - "completed_at": "2026-09-12T09:07:16.313535+00:00", - "subtype": "success", - "is_error": false, - "duration_ms": 973153, - "num_turns": 71, - "assistant_models": [ - "claude-opus-5" - ], - "used_tools": { - "Read": 41, - "Grep": 24, - "Glob": 5 - }, - "stream_sha256": "63046ccfab9fb158178f43b09c75a9809a13ed1ff2da5a626e9e3c3e96c13d37", - "review_sha256": "306a856010f7f9bd1cbec39aba83236978f5a811c98f7fc3f8a08217f72de111", - "auxiliary_model_note": "CLI model_usage may include auxiliary calls. assistant_models identifies the actual review message model. max was explicitly passed in the recorded command.", - "command": [ - "/opt/homebrew/bin/claude", - "--print", - "--model", - "claude-opus-5", - "--effort", - "max", - "--safe-mode", - "--permission-mode", - "plan", - "--tools", - "Read,Grep,Glob", - "--strict-mcp-config", - "--no-session-persistence", - "--add-dir", - "$LOCAL_EVIDENCE", - "--add-dir", - "$DOCS_WORKTREE/content/blog/design", - "--output-format", - "stream-json", - "--verbose" - ], - "command_note": "Same explicit CLI model/effort/read-only options as recorded in the tool invocation; round-2 had its own pinned worktree and prompt.", - "session_id": "8743aaa3-5c06-43db-8eba-08302d504c4e", - "archive_note": "Workstation paths normalized; billing data and raw model traces omitted. Original hashes are retained in archive-manifest.json." -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/binary-identity.json b/docs/investigations/issue-77/implementation-review/round-3/binary-identity.json deleted file mode 100644 index 87c82429b..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/binary-identity.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", - "source_clean": true, - "built_at": "2026-09-12T09:12:38.105913+00:00", - "binaries": { - "silo-reviewed": { - "sha256": "4825a801ce0ac48d636d9428ce4cd5c17a20b6cfec0b569de70a124c9c005049", - "version": "silo-reviewed version DEVELOPMENT.2026-09-12T09-09-34Z (commit-id=fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025)\nRuntime: go1.27.1 darwin/arm64\nLicense: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html\nCopyright: 2015-2025 MinIO, Inc.\nModifications: Copyright 2025-2026 PGSTY\nSource compatibility: based on MinIO technology", - "build_info_sha256": "91778f5f2844ada1234ed6a97d97fce7d87dc6334fdd9ef839fbeb7ebdb995e3", - "build_info_identity_lines": [ - "\tpath\tgithub.com/minio/minio", - "\tmod\tgithub.com/minio/minio\tv0.0.0-20260912090934-fcbb93e89572\t", - "\tbuild\t-buildmode=exe", - "\tbuild\t-compiler=gc", - "\tbuild\t-trimpath=true", - "\tbuild\tCGO_ENABLED=0", - "\tbuild\tGOARCH=arm64", - "\tbuild\tGOOS=darwin", - "\tbuild\tGOARM64=v8.0", - "\tbuild\tvcs=git", - "\tbuild\tvcs.revision=fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", - "\tbuild\tvcs.time=2026-09-12T09:09:34Z", - "\tbuild\tvcs.modified=false" - ] - }, - "silo-before": { - "sha256": "3b07b9604f2ca9f68f33b69f7807f57e0cc9b1efb0310bbf0aa7b3997e4483b9", - "version": "silo-before version DEVELOPMENT.2026-09-11T18-00-57Z (commit-id=5c576581631561c446f30ae5b566f0aa793adc1c)\nRuntime: go1.27.1 darwin/arm64\nLicense: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html\nCopyright: 2015-2025 MinIO, Inc.\nModifications: Copyright 2025-2026 PGSTY\nSource compatibility: based on MinIO technology", - "build_info_sha256": "84fb4540758d7dd56367069e3843a97d1e2c3528905db11d490b9654a2d6205d", - "build_info_identity_lines": [ - "\tpath\tgithub.com/minio/minio", - "\tmod\tgithub.com/minio/minio\tv0.0.0-20260911180057-5c5765816315\t", - "\tbuild\t-buildmode=exe", - "\tbuild\t-compiler=gc", - "\tbuild\t-tags=kqueue", - "\tbuild\t-trimpath=true", - "\tbuild\tCGO_ENABLED=0", - "\tbuild\tGOARCH=arm64", - "\tbuild\tGOOS=darwin", - "\tbuild\tGOARM64=v8.0", - "\tbuild\tvcs=git", - "\tbuild\tvcs.revision=5c576581631561c446f30ae5b566f0aa793adc1c", - "\tbuild\tvcs.time=2026-09-11T18:00:57Z", - "\tbuild\tvcs.modified=false" - ] - } - }, - "archive_note": "Dependency listing and machine paths omitted. Actual version, module identity, build settings and original build-info hash retained. Binaries are not included." -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/docs-check.json b/docs/investigations/issue-77/implementation-review/round-3/docs-check.json deleted file mode 100644 index d26a6a248..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/docs-check.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "command": [ - "make", - "check" - ], - "cwd": "$DOCS_WORKTREE", - "exit_code": 0, - "checked_content_commit": "9fa6248a0323838df5aed678b42c4679a7a6f8a4", - "committed_after_check": true, - "recorded_at": "2026-09-12T09:30:37.223220+00:00", - "files_sha256": { - "content/blog/design/bucket-metadata-convergence.zh.md": "26807bf8db555c3f0feef5c933b3d5a2da48fc7fd5a65da00d2c9b93ce7b4b21", - "content/blog/design/bucket-metadata-convergence.md": "36bc5c7f2310d96447b8130db0f34a99e900d8f7fc6f33d90630b89085769eec" - }, - "rendered_pages": { - "en": 1207, - "zh": 1219 - }, - "checks": [ - "go mod verify", - "strict Hugo build", - "internal links" - ] -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/docs-check.log b/docs/investigations/issue-77/implementation-review/round-3/docs-check.log deleted file mode 100644 index b750ca341..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/docs-check.log +++ /dev/null @@ -1,20 +0,0 @@ -go mod verify -all modules verified -hugo build --minify --cleanDestinationDir --printPathWarnings --printI18nWarnings --panicOnWarning -Start building sites … -hugo v0.165.0+extended+withdeploy darwin/arm64 BuildDate=2026-08-12T14:26:28Z VendorInfo=Homebrew - - - │ EN │ ZH -──────────────────┼──────┼────── - Pages │ 1207 │ 1219 - Paginator pages │ 2 │ 2 - Non-page files │ 26 │ 0 - Static files │ 740 │ 740 - Processed images │ 8 │ 0 - Aliases │ 45 │ 45 - Cleaned │ 0 │ 0 - -Total in 19619 ms -python3 bin/check_internal_links.py public -internal link check passed: 485471 rendered internal references across 1429 HTML files diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-build.json b/docs/investigations/issue-77/implementation-review/round-3/final-build.json deleted file mode 100644 index 8afa00430..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-build.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "command": [ - "go", - "build", - "./..." - ], - "cwd": "$SILO_WORKTREE", - "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", - "started_at": "2026-09-12T09:11:30.915932+00:00", - "environment": { - "TMPDIR": "$TMPDIR", - "CGO_ENABLED": "0" - }, - "exit_code": 0, - "duration_seconds": 24.193 -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-build.log b/docs/investigations/issue-77/implementation-review/round-3/final-build.log deleted file mode 100644 index 730877411..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-build.log +++ /dev/null @@ -1,3 +0,0 @@ -{"command": ["go", "build", "./..."], "cwd": "$SILO_WORKTREE", "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", "started_at": "2026-09-12T09:11:30.915932+00:00", "environment": {"TMPDIR": "$TMPDIR", "CGO_ENABLED": "0"}} - -EXIT=0 diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-cmd.json b/docs/investigations/issue-77/implementation-review/round-3/final-cmd.json deleted file mode 100644 index 995dd285c..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-cmd.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "command": [ - "go", - "test", - "./cmd/", - "-count=1", - "-timeout", - "30m" - ], - "cwd": "$SILO_WORKTREE", - "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", - "started_at": "2026-09-12T09:11:30.892660+00:00", - "environment": { - "TMPDIR": "$LAB_VOLUME/tmp", - "CGO_ENABLED": "0", - "MINIO_API_REQUESTS_MAX": "10000" - }, - "exit_code": 0, - "duration_seconds": 518.124 -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-cmd.log b/docs/investigations/issue-77/implementation-review/round-3/final-cmd.log deleted file mode 100644 index 0809947d7..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-cmd.log +++ /dev/null @@ -1,4 +0,0 @@ -{"command": ["go", "test", "./cmd/", "-count=1", "-timeout", "30m"], "cwd": "$SILO_WORKTREE", "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", "started_at": "2026-09-12T09:11:30.892660+00:00", "environment": {"TMPDIR": "$LAB_VOLUME/tmp", "CGO_ENABLED": "0", "MINIO_API_REQUESTS_MAX": "10000"}} -ok github.com/minio/minio/cmd 492.776s - -EXIT=0 diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-lint.json b/docs/investigations/issue-77/implementation-review/round-3/final-lint.json deleted file mode 100644 index 3aa2f7b4e..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-lint.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "command": [ - "make", - "lint" - ], - "cwd": "$SILO_WORKTREE", - "head": "461e9a721047c63e1a95f54ad4b533a6b89def30", - "started_at": "2026-09-12T09:17:19.334284+00:00", - "environment": { - "TMPDIR": "$TMPDIR" - }, - "exit_code": 0, - "duration_seconds": 79.064 -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-lint.log b/docs/investigations/issue-77/implementation-review/round-3/final-lint.log deleted file mode 100644 index 380862df5..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-lint.log +++ /dev/null @@ -1,6 +0,0 @@ -{"command": ["make", "lint"], "cwd": "$SILO_WORKTREE", "head": "461e9a721047c63e1a95f54ad4b533a6b89def30", "started_at": "2026-09-12T09:17:19.334284+00:00", "environment": {"TMPDIR": "$TMPDIR"}} -Running lint check -0 issues. -typos binary is not found.. skipping.. - -EXIT=0 diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-runtime.json b/docs/investigations/issue-77/implementation-review/round-3/final-runtime.json deleted file mode 100644 index 3a81a6bfb..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-runtime.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "command": [ - "go", - "run", - ".issue77-evidence/twosite/main.go", - "$LAB_VOLUME/runtime-reviewed", - ".issue77-evidence/silo-reviewed", - ".issue77-evidence/silo-before" - ], - "cwd": "$SILO_WORKTREE", - "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", - "started_at": "2026-09-12T09:12:41.365111+00:00", - "environment": { - "TMPDIR": "$LAB_VOLUME/tmp" - }, - "exit_code": 0, - "duration_seconds": 190.476 -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-runtime.log b/docs/investigations/issue-77/implementation-review/round-3/final-runtime.log deleted file mode 100644 index 08274f707..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-runtime.log +++ /dev/null @@ -1,28 +0,0 @@ -{"command": ["go", "run", ".issue77-evidence/twosite/main.go", "$LAB_VOLUME/runtime-reviewed", ".issue77-evidence/silo-reviewed", ".issue77-evidence/silo-before"], "cwd": "$SILO_WORKTREE", "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", "started_at": "2026-09-12T09:12:41.365111+00:00", "environment": {"TMPDIR": "$LAB_VOLUME/tmp"}} -2026-09-12T09:12:42Z site-a ready gate=on -2026-09-12T09:12:43Z site-b ready gate=on -2026-09-12T09:12:44Z historical bucket prepared: six live fields at Created -2026-09-12T09:12:44Z baseline-initial-sync: waiting for ordinary 30-second heal -2026-09-12T09:12:44Z baseline-initial-sync: six field states equal -2026-09-12T09:12:44Z initial sync preserved six historical Created timestamps -2026-09-12T09:12:44Z real local PUT outgoing replication RPC dropped by partition -2026-09-12T09:12:45Z live-reconnect: waiting for ordinary 30-second heal -2026-09-12T09:13:13Z live-reconnect: six field states equal -2026-09-12T09:13:13Z live-steady: observing two heal intervals -2026-09-12T09:14:18Z live-steady: zero metadata RPCs across 65 seconds -2026-09-12T09:14:18Z six duplicate/out-of-order events rejected without clock changes -2026-09-12T09:14:19Z real local DELETE outgoing replication RPC dropped by partition -2026-09-12T09:14:20Z site-a ready gate=on -2026-09-12T09:14:20Z four tombstones survived process restart -2026-09-12T09:14:20Z delete-reconnect: waiting for ordinary 30-second heal -2026-09-12T09:14:44Z delete-reconnect: six field states equal -2026-09-12T09:14:44Z delete-steady: observing two heal intervals -2026-09-12T09:15:49Z delete-steady: zero metadata RPCs across 65 seconds -2026-09-12T09:15:49Z 40 exceptional events produced exactly one log per reason -2026-09-12T09:15:49Z starting fixed/previous-server rolling-upgrade smoke with gate off -2026-09-12T09:15:50Z site-a ready gate=off -2026-09-12T09:15:51Z site-b ready gate=off -2026-09-12T09:15:51Z mixed-version ordinary PUT/DELETE and off tombstone visibility passed -2026-09-12T09:15:51Z PASS: isolated two-site implementation acceptance - -EXIT=0 diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-style-target-race.json b/docs/investigations/issue-77/implementation-review/round-3/final-style-target-race.json deleted file mode 100644 index 6c006ad15..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-style-target-race.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "command": [ - "go", - "test", - "-race", - "./cmd/", - "-run", - "^(TestBucketMetadataInitialSyncPhysicalCreated|TestHealBucketConfigDiagnostics|TestHealBucketConfigWithoutSourceDiagnostics)$", - "-count=1", - "-timeout", - "10m" - ], - "cwd": "$SILO_WORKTREE", - "head": "461e9a721047c63e1a95f54ad4b533a6b89def30", - "started_at": "2026-09-12T09:18:02.407600+00:00", - "environment": { - "TMPDIR": "$LAB_VOLUME/tmp", - "CGO_ENABLED": "1" - }, - "exit_code": 0, - "duration_seconds": 45.681 -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-style-target-race.log b/docs/investigations/issue-77/implementation-review/round-3/final-style-target-race.log deleted file mode 100644 index 9f4ac4ed5..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-style-target-race.log +++ /dev/null @@ -1,4 +0,0 @@ -{"command": ["go", "test", "-race", "./cmd/", "-run", "^(TestBucketMetadataInitialSyncPhysicalCreated|TestHealBucketConfigDiagnostics|TestHealBucketConfigWithoutSourceDiagnostics)$", "-count=1", "-timeout", "10m"], "cwd": "$SILO_WORKTREE", "head": "461e9a721047c63e1a95f54ad4b533a6b89def30", "started_at": "2026-09-12T09:18:02.407600+00:00", "environment": {"TMPDIR": "$LAB_VOLUME/tmp", "CGO_ENABLED": "1"}} -ok github.com/minio/minio/cmd 4.540s - -EXIT=0 diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-target-race.json b/docs/investigations/issue-77/implementation-review/round-3/final-target-race.json deleted file mode 100644 index c1c51d15e..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-target-race.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "command": [ - "go", - "test", - "-race", - "./cmd/", - "-run", - "^(TestBucketMetadataInitialSyncPhysicalCreated|TestPeerBucketMetadataPhysicalCreatedBoundary|TestPeerBucketMetadataUnknownCreated|TestPeerBucketAdoptionRebasesOnlyDefaults|TestHealBucketConfigDiagnostics|TestHealBucketConfigWithoutSourceDiagnostics|TestBucketMetadataPhysicalCreatedRecovery|TestBucketPolicyReplicationStatusLegacyOrder|TestHealBucketConfigSourceAndQuiescence|TestLatestBucketConfigCandidates|Test.*Cors.*|Test.*CORS.*)$", - "-count=1", - "-timeout", - "15m" - ], - "cwd": "$SILO_WORKTREE", - "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", - "started_at": "2026-09-12T09:11:30.873027+00:00", - "environment": { - "TMPDIR": "$LAB_VOLUME/tmp", - "CGO_ENABLED": "1" - }, - "exit_code": 0, - "duration_seconds": 90.202 -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-target-race.log b/docs/investigations/issue-77/implementation-review/round-3/final-target-race.log deleted file mode 100644 index 121837f13..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-target-race.log +++ /dev/null @@ -1,4 +0,0 @@ -{"command": ["go", "test", "-race", "./cmd/", "-run", "^(TestBucketMetadataInitialSyncPhysicalCreated|TestPeerBucketMetadataPhysicalCreatedBoundary|TestPeerBucketMetadataUnknownCreated|TestPeerBucketAdoptionRebasesOnlyDefaults|TestHealBucketConfigDiagnostics|TestHealBucketConfigWithoutSourceDiagnostics|TestBucketMetadataPhysicalCreatedRecovery|TestBucketPolicyReplicationStatusLegacyOrder|TestHealBucketConfigSourceAndQuiescence|TestLatestBucketConfigCandidates|Test.*Cors.*|Test.*CORS.*)$", "-count=1", "-timeout", "15m"], "cwd": "$SILO_WORKTREE", "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", "started_at": "2026-09-12T09:11:30.873027+00:00", "environment": {"TMPDIR": "$LAB_VOLUME/tmp", "CGO_ENABLED": "1"}} -ok github.com/minio/minio/cmd 20.903s - -EXIT=0 diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-tree-equivalence.json b/docs/investigations/issue-77/implementation-review/round-3/final-tree-equivalence.json deleted file mode 100644 index d66daf8ae..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-tree-equivalence.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "reviewed_production_head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", - "final_head": "461e9a721047c63e1a95f54ad4b533a6b89def30", - "changed_files": [ - "cmd/site-replication-metadata-gate_test.go", - "cmd/site-replication-metadata-heal_test.go" - ], - "difference": "Test-only gofumpt formatting and equivalent if-chain to switch; no production or server documentation changes", - "patch": "test-style.diff" -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-vet.json b/docs/investigations/issue-77/implementation-review/round-3/final-vet.json deleted file mode 100644 index 61bc18876..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-vet.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "command": [ - "go", - "vet", - "./..." - ], - "cwd": "$SILO_WORKTREE", - "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", - "started_at": "2026-09-12T09:11:55.197305+00:00", - "environment": { - "TMPDIR": "$TMPDIR", - "CGO_ENABLED": "0" - }, - "exit_code": 0, - "duration_seconds": 4.024 -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/final-vet.log b/docs/investigations/issue-77/implementation-review/round-3/final-vet.log deleted file mode 100644 index b36016764..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/final-vet.log +++ /dev/null @@ -1,3 +0,0 @@ -{"command": ["go", "vet", "./..."], "cwd": "$SILO_WORKTREE", "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", "started_at": "2026-09-12T09:11:55.197305+00:00", "environment": {"TMPDIR": "$TMPDIR", "CGO_ENABLED": "0"}} - -EXIT=0 diff --git a/docs/investigations/issue-77/implementation-review/round-3/opus5-max-review.md b/docs/investigations/issue-77/implementation-review/round-3/opus5-max-review.md deleted file mode 100644 index 5d71c50d7..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/opus5-max-review.md +++ /dev/null @@ -1,111 +0,0 @@ -已完成定向核对。以下为最终报告。 - ---- - -# SILO #77 第三轮独立对抗性验收(固定 `fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025`) - -**结论:GO_WITH_NONBLOCKING_NOTES — 阻断数 0** - -范围限定本轮差异(`round-3/change.diff`:2 个生产文件共约 12 行、5 个测试文件、1 个 README 段落)。未重复前两轮已完成的全量审查。 - ---- - -## 一、逐条 NB 判定 - -### NB-2 — 关闭(三个边界经代码级核对成立) - -生产改动在 `cmd/site-replication-metadata.go:134-160`。逐边界挑战结果: - -1. **`unreachable` 仅在存在候选时输出 —— 行为字节级不变**。改前 `if !found { return nil }` 就在 unreachable 循环之前,因此该日志从来只在 `found==true` 时产生;把它包进 `if found {}`(`:138-144`)是纯重构,没有新增也没有删除任何一条 unreachable。这一点上一轮报告与提交说明都没讲清,值得记录:**这不是行为修改**。 -2. **空基线/缺桶安静 —— 由 `newBucketConfigState` 的取值规则保证**(`cmd/bucket-metadata-replication.go:224-227`):`at.IsZero() → at = created`。因此"字段时间为零但 `CreatedAt` 非零"的合法基线必然 `valid=true`,不触发 `:154`;缺桶(全零)`data` 空且 `at` 零,也不触发。只有三类会响:`created==0 且 at/data 非空`、`at < created`、解码/解析报错。与新测试四个子用例一一对应,无第四种漏网情形。 -3. **无有效来源不发 RPC、不落盘 —— 成立**。`!found` 在 `:159` 即 `continue`,位于 `:177`(本地 `updateAndParseMetadata`)与 `:182`(`SRPeerReplicateBucketMeta`)之前。测试用 `len(events()) != 0` 从对端侧反证(`heal_test.go:162-164`)。 -4. **去重 —— 成立**。`logOnceIf`(`internal/logger/logonce.go:95-120`)按 `id` **且**错误正文相同才抑制;key 为 `bucket-metadata///`,正文只含 reason,时间/peer 全在 ReqInfo 属性里(`site-replication-metadata.go:33-43`)。所以多 peer 同因塌缩为一条,跨因不互相顶替。测试的 `for range 2` 钉住了这一点。 - -**必要性证据成立**:`round-2/no-source-before.log` 用同一份最终测试叠加旧生产码,`unknown-created`/`malformed`/`before-created` 三个子用例在 `heal_test.go:155` 报 `got 0 diagnostics, want 1`,`empty-baseline` 两侧都安静。行号与当前文件精确吻合,是真实的前后对照,不是草稿产物。 - -**接受的边界(非阻断,已成文)**:在全站 `Created==0` 的遗留桶上(滚动升级的典型形态),每个"有值字段"现在每小时多出一条 `indeterminate`,此前完全静默。这正是 NB-2 要的可行动信号,README:123-125 与设计记录 §limits-5 都写明了。量级与 NB-3 已接受的 `unreachable` 同阶。 - -**性能挑战结果:不构成问题**。`!found` 时主循环多做一遍 `bucketConfigStateFromInfo`,但空载荷在 `site-replication-metadata.go:75` 与 `bucket-metadata-replication.go:178-180` 两处短路,连 base64 都不解——而空载荷恰好是 `!found` 的主体人群。上一轮建议的"缓存解码结果"因此确实可以不做。 - -### NB-8 — 关闭,nil/zero/local 三分支均无错误时间与 panic - -`cmd/bucket-metadata-sys.go:186-192`: - -- `sourceTime == nil` → `at` 为零值,打印 `0001-01-01`,与改前一致,无解引用; -- `sourceTime != nil && IsZero()` → 同上,不会把零值当作有效来源时间; -- `sourceTime` 非零且为本地时区 → `logBucketConfigReplication:38` 统一 `at.UTC().Format(...)`,不产生偏移时间; -- `meta.Created`:该分支下必为零(`ensureBucketMetadataCreated:278-280` 在非零时提前返回 nil),所以 `created` 属性如实; -- 去重不受影响:错误正文未变(见上)。 - -**一处同类残留(本轮之前既有,非本轮引入)**:`site-replication-metadata.go:155` 在 `currentErr != nil` 时 `current` 是零值结构体,于是"解析失败"的日志同样丢掉了对端上报的真实 `at`。最小修复需要一个不经解码就取 `(bucket,file) → at` 的取值函数,约 10 行。可选,不必本轮做。 - -### NB-4 / NB-5 — 关闭;全仓无漏引用,编译成立;有一项必须如实指出的门禁事实 - -- **漏引用**:`isBucketMetadataEqual` 在 Go 代码中零引用,仅 `docs/site-replication/CORS-LWW-DESIGN.md:84` 提及,且位于 "Confirmed Failures in the **Pre-Fix** Candidate" 小节,是对既往缺陷的历史陈述,不是对现有代码的断言,无需改。 -- **编译**:两个被删用例所在文件的 `encoding/base64` 导入仍在被使用(`site-replication_test.go:21` → `:80/:106`;`bucket-cors-site-replication_test.go:22` → 20 余处),不存在第二次漏删。`round-3/final-build.log`、`final-vet.log` 在本轮固定 SHA 上 `go build ./...` / `go vet ./...` 均 `EXIT=0`。**当前树完整编译成立**。 -- **真实 CORS 路径保留**:被删的是纯 helper 单测;同一性质(base64 必须按字节严格比较)现由 `TestPeerBucketCorsRejectsNonCanonicalBase64`(`bucket-cors-site-replication_test.go:782-815`)经真实 `PeerBucketCorsConfigHandler` 与 legacy bulk 两条生产路径覆盖,并断言被拒后元数据未变;heal 侧用 `corsReplicationStateFromInfo`/`equalCORSReplicationStates`(`site-replication.go:4903-4904`)而非被删函数。**无覆盖损失。** -- **logger 全局状态与 race**:`testlogger.T` 是进程级单例但用 atomic(`testlogger.go:49,78-85`),本身无 race;`logger.DisableLog` 是普通全局 bool(`logger.go:407` 读、`test-utils_test.go:105` 默认置 true),两个新用例都 `defer` 还原。`round-3/final-target-race.log` 在本轮固定 SHA 上对包含全部新用例的集合 `-race` 跑通(20.9s,EXIT=0)。**已验证,非阻断。** -- **两项潜在脆弱点(无阻断、目前未触发,仅备案)**:(a) `DisableLog=false` 期间全局 sink 会捕获进程内**任何**系统日志,而 `heal_test.go:97` 对任何非预期行直接 `Fatalf`——若窗口内有后台 goroutine 输出日志即失败。最小加固:分类前先 `strings.Contains(line, bucket)` 过滤外来日志。(b) `logOnce.IDMap` 是进程级、每小时才清(`logonce.go:123-131`),这两个用例之所以安全,是因为 `ExecObjectLayerAPITest` 对两种后端各自 `getRandomBucketName()`(`test-utils_test.go:1556`,分别在 `:1771`/`:1801` 调用)使日志 key 天然唯一——这是隐式依赖,值得在注释里点一句。 -- **证据精度**:`round-2/confirmed-diagnostics-before.log` 在 `heal_test.go:80`(**第一条**断言)就 Fatal,因此它只证明了 F3 的"空基线必须安静"这一半;WARNING 级别与按因去重(`:99-105`)在旧码上并未被执行到,**没有**对应的失败演示。应如实这么写,不能说成"F3 已被完整回归保护"。 - -- **必须指出的门禁事实**:`round-3/pre-style-lint.log` 显示本轮固定提交 `fcbb93e8` 的 `make lint` **失败(EXIT=2,3 issues)**,全部落在本轮新增的两个测试文件:`cmd/site-replication-metadata-heal_test.go:92:5`(gocritic ifElseChain)、`cmd/site-replication-metadata-gate_test.go:282:1` 与 `heal_test.go:138:1`(gofumpt)。我通读了修复补丁 `test-style.diff`:switch 分支顺序与函数体同 if-else 完全一致,另两处仅为结构体字面量换行,**语义等价、仅测试文件**,已在后续提交 `461e9a72` 修掉(该提交的 lint 复跑在证据中仍在进行)。结论:不影响行为验收,但"`fcbb93e8` 本身通过全部仓库门禁"这句话不成立,不应这么写。 - -### NB-6 — 关闭为**出站 wire 证明**;不得称作双站点证明(已如实区分) - -`TestBucketMetadataInitialSyncPhysicalCreated`(`gate_test.go:245-303`)确实驱动真实源 ObjectLayer 走完整出站序列:`syncToAllPeers`(`site-replication.go:2141-2153`)→ `ensureBucketMetadataCreated` 从真实盘目录 mtime 恢复 → `MakeBucketHook`(`:821-826` 缓存零值回落)→ wire 上的 `createdAt` 参数;再经 `initialBucketConfigReplicationEvent` 发出 Tags 事件。断言 `createdAt == physical`、Tags 载荷为 base64(XML) 且 `UpdatedAt == physical`(历史字段以 Created 作源时间)。前后对照成立:`round-2/initial-sync-before.log` 在 `gate_test.go:289` 报 `"0001-01-01T00:00:00Z"`,行号与最终文件精确吻合。 - -**必须如实标注的范围限制(我独立核出,不在提交说明里)**:该用例的 `c.state.Peers` 只含 `"initial-peer"`(`gate_test.go:282-283`),而 `concDo`(`site-replication.go:2497-2515`)只对 `depID == globalDeploymentID()` 的条目执行 `selfActionFn`。该条目不存在,因此真正把 `Created` 落到**本地盘**的 `PeerBucketMakeWithVersioningHandler`(`site-replication.go:954-966` 的 `SetCreatedAt` + `saveMetadata`)**在本用例中根本没有执行**。也就是说: - -- README:110-114 "Recovery happens … during initial site sync … records the physical time" 在生产路径上成立(经 `concDo` 自身分支),但**不被这个测试覆盖**; -- 本用例证明的是出站内容,不是任何一侧的落盘,更不是双站点收敛。 - -设计记录 §205 已经把这个区分写对了("它证明出站内容…二者不能混称为同一验收"),README 也没有声称测试覆盖,因此**不构成不实陈述**。若要补齐本地落盘那一半:在该 Peers map 中加入 `globalDeploymentID(): {}` 并在末尾加一次 `readBucketMetadata` 断言 `Created==physical`,约 3 行。 - -### NB-7 — 关闭,两半都钉住了 - -`site-replication-metadata_test.go:482-490`。沿 `applyBucketConfig`(`bucket-metadata-replication.go:294-316`)+ `compareBucketConfigStates`(`:239-245`)核对:`incoming.valid=true` 对 `local.valid=false` 直接返回 1,因此 `changed=true`,随后断言指针别名上的 `*at == incomingAt`、`*data == incoming`——载荷与时间都断言了。对 `bucketTaggingConfig` 这是"活标签覆盖被保留的旧世代删除记录",语义与注释一致;对 `bucketSSEConfig` 载荷恰好与原值相同,那里真正承载证明的是时间戳断言。两点说明:断言层级是 `applyBucketConfig`(即写路径 `bucket-metadata-sys.go:206` 调用的同一决策函数),属单元级钉桩而非端到端,这是可接受的取舍。 - -### NB-1 — 判定为**如实保留边界**,本轮不存在必须修的回归 - -本轮对 NB-1 **没有任何生产代码改动**(仅 README:106-114 + 新用例),因此按构造不可能引入回归。我独立复核了被保留的行为与被拒方案: - -- 保留跳过:`bucket-metadata-sys.go:202-205` 在 `updatedAt.Before(meta.Created)` 时记 `before-created` 并 `return nil`(不写、不报错);heal 侧同因在 `site-replication-metadata.go:162-164`。 -- 拒绝 `min(recovered, sourceTime)` 的理由我认为**成立**:单个事件的时间戳无法区分"本世代的较早事件"与"上一个已删除世代的事件",凭一个事件下调 `Created` 会让删除前的状态复活。这里选安全而非活性是对的,且具备自愈性(下一次 ≥ physical 的写入即确立身份)与可观测性(有界 warning)。 -- 新用例 `TestPeerBucketMetadataPhysicalCreatedBoundary`(`gate_test.go:305-335`)用真实盘 mtime 同时钉住两侧:`physical-1h` 的 peer 事件 `err==nil` 且 `bucketConfigWriteCounter` 计数为 **0**(是跳过不是失败);随后本地 `Update` 使盘上 `Created==physical` 且 `at > physical`。断言选得准确。 -- 残留(已成文):唯一持有者的源时间早于恢复值的字段,不经人工介入不会收敛;且跳过路径**不落盘恢复值**(`:204` 早于 `saveMetadata`),每次事件都重新探测 mtime——README:112-113 用 "first **successful** configuration write" 措辞正确地表达了这一点。 - -### NB-3 — 接受现状,不要求扩展 - -`site-replication-metadata.go:138-144` 仍按桶×字段×原因输出,`healBuckets` 每桶调用六次(`site-replication.go:4753-4760` → `:4851-4935`)。设计记录 §limits-5 已明确声明该量级会随桶与有值字段增长。未构成阻断,也未破坏任何既定契约,按约定不要求做站点级聚合/限流改造。 - -**一处文档精度小瑕**:§limits-5 把"日志量随桶与字段增长"这句只挂在"不可达站点"上;本轮之后,遗留零 `Created` 集群里的 `indeterminate` 同样按桶×字段增长。加半句即可,非阻断。 - -### F5 — 确认无外溢 - -本轮 diff 中没有任何 `canonicalBucketPolicy` 接入点的增删;只有设计记录 F5 一行表述更新(`bucket-metadata-convergence.md:182` / `.zh.md:185`),与"上轮已修正原判断、不再改代码"一致。 - ---- - -## 二、确证问题清单(均非阻断) - -| # | 位置 | 触发条件 | 最小修复 | -|---|---|---|---| -| 1 | `cmd/site-replication-metadata-heal_test.go:92`、`:138`;`cmd/site-replication-metadata-gate_test.go:282` | 在固定提交 `fcbb93e8` 上执行 `make lint` | 已在 `461e9a72` 修掉(语义等价、仅测试文件);本轮 SHA 需注明 lint 未过 | -| 2 | `cmd/site-replication-metadata.go:155` | heal 遇到解码/解析失败的对端状态 | 日志属性中 `sourceTime` 恒为 `0001-01-01`;需一个免解码的 `(bucket,file)→at` 取值函数,约 10 行。可选 | -| 3 | `cmd/site-replication-metadata-heal_test.go:97` | 日志窗口内任一后台 goroutine 输出日志 | 分类前加 `strings.Contains(line, bucket)` 过滤,1 行 | -| 4 | `cmd/site-replication-metadata-gate_test.go:282-283` | —(覆盖缺口,非缺陷) | 若要覆盖 README 的"初次同步落盘 Created"一半:Peers 加入 `globalDeploymentID()` 并补一次 `readBucketMetadata` 断言,约 3 行 | -| 5 | 设计记录 §limits-5 | — | "随桶与字段增长"补上 `indeterminate` 这一类,半句 | - -**证据状态如实说明**:`round-3/final-cmd.log` 只有头行,**完整 `go test ./cmd/` 尚未产生结果**,不可称作已通过;已完成且可复核的是 `final-build`/`final-vet`(EXIT=0)、`final-target-race`(EXIT=0)、`pre-style-lint`(EXIT=2,见上)。`461e9a72` 的 `make lint` 复跑同样在进行中。 - ---- - -## 三、最小性 / 充分性 / 必要性(独立评价) - -**最小性:是,且是本系列迄今最克制的一轮。** 生产侧净变化只有两处共约 12 行:`found` 条件位移(未引入解码缓存、未新增结构体、未新增日志原因),以及诊断里一个已有指针的解引用。没有新 schema、新 wire 字段、新锁、新框架。我找不到可以无损删除的部分:删掉 `!found` 分支就退回 NB-2 的静默,删掉 `at` 就退回 NB-8。测试删除项(`isBucketMetadataEqual` 及其两个专属用例)经全仓核对确认无生产调用者、无覆盖损失。 - -**充分性:对本轮声称的目标充分;两处覆盖如实低于文字。** 新增三个用例都有真实前后对照或真实后端驱动,`-race` 通过。低于文字的两处均已在上文点名:F3 的级别/去重断言在旧码上未被演示失败;初次同步的本地落盘半边未被测试覆盖(生产路径本身成立)。二者都是证据强度问题,不是行为问题,且设计记录没有把它们说过头。 - -**必要性:是。** NB-2 与 NB-6 各有"最终测试 + 旧生产码"的复现日志且行号可逐条核对(`no-source-before.log`、`initial-sync-before.log`);NB-8 是一行可读性缺陷的直接消除;NB-5 是删除无调用者代码;NB-7 是补一侧期望值;NB-1/NB-3 明确不改代码,只做文档与钉桩。没有一项属于"顺手扩张"。 - -**放行建议**:以 `461e9a72`(lint 修复后的等价树)为合并基准,并在合入前等 `go test ./cmd/` 与 `make lint` 两条复跑各自出结果;上表 5 项可全部进 backlog。 diff --git a/docs/investigations/issue-77/implementation-review/round-3/session.json b/docs/investigations/issue-77/implementation-review/round-3/session.json deleted file mode 100644 index 50a298555..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/session.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "requested_model": "claude-opus-5", - "requested_effort": "max", - "head": "fcbb93e8957275bfa7ad4e6154e93f4d7b0a7025", - "base": "62cf066ff529c7d281703daa365f555cebba717a", - "status": "completed", - "started_at": "2026-09-12T09:10:48.496423+00:00", - "command": [ - "/opt/homebrew/bin/claude", - "--print", - "--model", - "claude-opus-5", - "--effort", - "max", - "--safe-mode", - "--permission-mode", - "plan", - "--tools", - "Read,Grep,Glob", - "--strict-mcp-config", - "--no-session-persistence", - "--add-dir", - "$LOCAL_EVIDENCE", - "--add-dir", - "$DOCS_WORKTREE/content/blog/design", - "--output-format", - "stream-json", - "--verbose" - ], - "completed_at": "2026-09-12T09:25:59.617894+00:00", - "subtype": "success", - "is_error": false, - "duration_ms": 777403, - "num_turns": 61, - "assistant_models": [ - "claude-opus-5" - ], - "used_tools": { - "Read": 33, - "Grep": 23, - "Glob": 4 - }, - "stream_sha256": "a9a8784005abe5f02c1847e6d2a42890a309dbf34604a3756c456f1fc29c612a", - "review_sha256": "9106938a7a34cfbc6a11d372dd5ffb71ae7417a77a56d10bbed178f539de8fba", - "auxiliary_model_note": "CLI model_usage may include auxiliary calls. assistant_models identifies the actual review message model. max was explicitly passed in the recorded command.", - "archive_note": "Workstation paths normalized; billing data and raw model traces omitted. Original hashes are retained in archive-manifest.json.", - "session_id": "3b2ce46c-226b-4c52-b004-c727303c4a9b", - "session_id_source": "system init event in the stream identified by stream_sha256" -} diff --git a/docs/investigations/issue-77/implementation-review/round-3/test-style.diff b/docs/investigations/issue-77/implementation-review/round-3/test-style.diff deleted file mode 100644 index 70e5fa43b..000000000 --- a/docs/investigations/issue-77/implementation-review/round-3/test-style.diff +++ /dev/null @@ -1,53 +0,0 @@ -diff --git a/cmd/site-replication-metadata-gate_test.go b/cmd/site-replication-metadata-gate_test.go -index 49818b548..de9a37bb6 100644 ---- a/cmd/site-replication-metadata-gate_test.go -+++ b/cmd/site-replication-metadata-gate_test.go -@@ -279,8 +279,10 @@ func TestBucketMetadataInitialSyncPhysicalCreated(t *testing.T) { - defer peer.Close() - // Exercise the complete outgoing sync sequence with real source - // storage. This peer acknowledges RPCs; it is not a second ObjectLayer. -- c := &SiteReplicationSys{enabled: true, state: srState{ServiceAccountAccessKey: serviceCred.AccessKey, -- Peers: map[string]madmin.PeerInfo{"initial-peer": {DeploymentID: "initial-peer", Endpoint: peer.URL}}}} -+ c := &SiteReplicationSys{enabled: true, state: srState{ -+ ServiceAccountAccessKey: serviceCred.AccessKey, -+ Peers: map[string]madmin.PeerInfo{"initial-peer": {DeploymentID: "initial-peer", Endpoint: peer.URL}}, -+ }} - if err := c.syncToAllPeers(ctx, madmin.SRAddOptions{}); err != nil { - t.Fatal(err) - } -diff --git a/cmd/site-replication-metadata-heal_test.go b/cmd/site-replication-metadata-heal_test.go -index bedb3ea5e..6c00fb8c1 100644 ---- a/cmd/site-replication-metadata-heal_test.go -+++ b/cmd/site-replication-metadata-heal_test.go -@@ -89,11 +89,12 @@ func TestHealBucketConfigDiagnostics(t *testing.T) { - defer capture.mu.Unlock() - var unreachable, peerError int - for _, line := range capture.lines { -- if strings.Contains(line, "bucket metadata replication: unreachable") { -+ switch { -+ case strings.Contains(line, "bucket metadata replication: unreachable"): - unreachable++ -- } else if strings.Contains(line, "bucket metadata replication: peer-error") { -+ case strings.Contains(line, "bucket metadata replication: peer-error"): - peerError++ -- } else { -+ default: - t.Fatalf("unexpected diagnostic: %s", line) - } - if !strings.HasPrefix(line, "WARNING:") { -@@ -135,11 +136,13 @@ func TestHealBucketConfigWithoutSourceDiagnostics(t *testing.T) { - // and neither local storage nor the recording peer may be written. - name := bucket + "-" + tc.name - local := globalDeploymentID() -- info := srStatusInfo{Sites: map[string]madmin.PeerInfo{local: {}, "metadata-peer": {}, "unreachable": {}}, -+ info := srStatusInfo{ -+ Sites: map[string]madmin.PeerInfo{local: {}, "metadata-peer": {}, "unreachable": {}}, - BucketStats: map[string]map[string]srBucketStatsSummary{name: { - local: bucketConfigTestInfo(name, bucketTaggingConfig, tc.data, tc.at, tc.created), - "metadata-peer": {}, -- }}} -+ }}, -+ } - for range 2 { - if err := globalSiteReplicationSys.healBucketConfig(t.Context(), name, bucketTaggingConfig, info); err != nil { - t.Fatal(err) diff --git a/docs/investigations/issue-77/implementation-review/session.json b/docs/investigations/issue-77/implementation-review/session.json deleted file mode 100644 index 6f1d9f115..000000000 --- a/docs/investigations/issue-77/implementation-review/session.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "requested_model": "claude-opus-5", - "requested_effort": "max", - "cli_version": "2.1.258", - "base": "5c576581631561c446f30ae5b566f0aa793adc1c", - "head": "4089113e3edbd21a29be8e6af74662462bcc22af", - "started_at": "2026-09-12T04:02:26.329601+00:00", - "status": "completed", - "observed_model": "claude-opus-5", - "session_id": "86133939-ee59-4e4f-ad98-61f825c91899", - "permission_mode": "plan", - "tools": [ - "Glob", - "Grep", - "Read" - ], - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --add-dir $LOCAL_EVIDENCE --output-format stream-json --verbose", - "prompt_sha256": "09cea2c1356a2261e8cb9b0d4f2c947d0b683d00386267ebb340d487bdc66b4d", - "diff_sha256": "58703d70978fa25ea6b23eb6735ec94a6d369df3a776e5169a13c847f0d6b0eb", - "completed_at": "2026-09-12T04:35:09.936197+00:00", - "subtype": "success", - "is_error": false, - "duration_ms": 1841224, - "num_turns": 93, - "assistant_models": [ - "claude-opus-5" - ], - "used_tools": { - "Read": 55, - "Grep": 31, - "Glob": 5, - "Write": 1 - }, - "stream_sha256": "90a262f5cc189575b2e8b458c8e5cd17274fb493261c35135d199bee4895de24", - "review_sha256": "e40dad6e1969898e35c82b7fd24517a5ee714d603a54fe3598b9e69c20922118", - "auxiliary_model_note": "CLI model_usage may include auxiliary calls. assistant_models identifies the actual review message model. max was explicitly passed in the recorded command.", - "archive_note": "Workstation paths normalized; billing data and raw model traces omitted. Original hashes are retained in archive-manifest.json.", - "source_tree_equivalent_commit": "1ee64a8d8", - "tool_scope_note": "Configured Read/Grep/Glob. The CLI also attempted a Write to its own plan file; no production source was edited by the reviewer." -} diff --git a/docs/investigations/issue-77/issue77_heal_review_test.go.txt b/docs/investigations/issue-77/issue77_heal_review_test.go.txt deleted file mode 100644 index dbba44208..000000000 --- a/docs/investigations/issue-77/issue77_heal_review_test.go.txt +++ /dev/null @@ -1,213 +0,0 @@ -package cmd - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/minio/madmin-go/v3" - "github.com/minio/minio/internal/auth" -) - -func TestIssue77CurrentHealInputs(t *testing.T) { - ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: review77HealInputs}) -} - -func review77HealInputs(obj ObjectLayer, backend, bucket string, _ http.Handler, cred auth.Credentials, t *testing.T) { - ctx := t.Context() - localID, remoteID := globalDeploymentID(), "issue77-remote" - created := UTCNow().Add(-time.Hour) - at := created.Add(10 * time.Minute) - newerAt := at.Add(time.Minute) - enc := func(b []byte) *string { s := base64.StdEncoding.EncodeToString(b); return &s } - base := newBucketMetadata(bucket) - base.SetCreatedAt(created) - base.defaultTimestamps() - c := &SiteReplicationSys{enabled: true} - status := func(local, remote madmin.SRBucketInfo, mismatch madmin.SRBucketStatsSummary) srStatusInfo { - local.Bucket, remote.Bucket = bucket, bucket - return srStatusInfo{Sites: map[string]madmin.PeerInfo{localID: {Name: "local"}, remoteID: {Name: "remote"}}, BucketStats: map[string]map[string]srBucketStatsSummary{bucket: {localID: {SRBucketStatsSummary: mismatch, meta: srBucketMetaInfo{SRBucketInfo: local, DeploymentID: localID}}, remoteID: {meta: srBucketMetaInfo{SRBucketInfo: remote, DeploymentID: remoteID}}}}} - } - t.Run(backend+"/quota-tombstone-cache", func(t *testing.T) { - quota, _ := json.Marshal(madmin.BucketQuota{Quota: 1024, Type: madmin.HardQuota}) - meta := base - meta.QuotaConfigJSON, meta.QuotaConfigUpdatedAt = quota, at - if err := globalBucketMetadataSys.save(ctx, meta); err != nil { - t.Fatal(err) - } - s := status(madmin.SRBucketInfo{CreatedAt: created, QuotaConfig: enc(quota), QuotaConfigUpdatedAt: at}, madmin.SRBucketInfo{CreatedAt: created, QuotaConfigUpdatedAt: newerAt}, madmin.SRBucketStatsSummary{QuotaCfgMismatch: true}) - if err := c.healBucketQuotaConfig(ctx, obj, bucket, s); err != nil { - t.Fatal(err) - } - cached, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucket) - if err != nil { - t.Fatal(err) - } - disk, err := loadBucketMetadata(ctx, obj, bucket) - if err != nil { - t.Fatal(err) - } - if len(disk.QuotaConfigJSON) != 0 { - t.Fatal("quota tombstone not written to disk") - } - if cached != nil && cached.Quota != 0 { - t.Errorf("QUOTA_CACHE: disk has no quota, cache still enforces %d", cached.Quota) - } - }) - t.Run(backend+"/same-payload-time-barrier", func(t *testing.T) { - xml := []byte(`keysame`) - meta := base - meta.TaggingConfigXML, meta.TaggingConfigUpdatedAt = xml, at - if err := globalBucketMetadataSys.save(ctx, meta); err != nil { - t.Fatal(err) - } - // Even forcing mismatch=true cannot make the existing heal advance a - // timestamp when the live payload bytes already match. - s := status(madmin.SRBucketInfo{CreatedAt: created, Tags: enc(xml), TagConfigUpdatedAt: at}, madmin.SRBucketInfo{CreatedAt: created, Tags: enc(xml), TagConfigUpdatedAt: newerAt}, madmin.SRBucketStatsSummary{TagMismatch: true}) - if err := c.healTagMetadata(ctx, obj, bucket, s); err != nil { - t.Fatal(err) - } - after, err := loadBucketMetadata(ctx, obj, bucket) - if err != nil { - t.Fatal(err) - } - if !after.TaggingConfigUpdatedAt.Equal(newerAt) { - t.Errorf("BARRIER_NOT_HEALED: same payload remains at %s, latest is %s", after.TaggingConfigUpdatedAt, newerAt) - } - }) - t.Run(backend+"/creation-default-selection", func(t *testing.T) { - policy := []byte(fmt.Sprintf(`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":"*","Action":"s3:GetObject","Resource":"arn:aws:s3:::%s/*"}]}`, bucket)) - meta := base - meta.PolicyConfigJSON, meta.PolicyConfigUpdatedAt = policy, at - baselineAt := newerAt.Add(time.Hour) - s := status(madmin.SRBucketInfo{CreatedAt: created, Policy: policy, PolicyUpdatedAt: at}, madmin.SRBucketInfo{CreatedAt: baselineAt, PolicyUpdatedAt: baselineAt}, madmin.SRBucketStatsSummary{PolicyMismatch: true}) - wrong := 0 - for i := 0; i < 32; i++ { - if err := globalBucketMetadataSys.save(ctx, meta); err != nil { - t.Fatal(err) - } - if err := c.healBucketPolicies(ctx, obj, bucket, s); err != nil { - t.Fatal(err) - } - after, err := loadBucketMetadata(ctx, obj, bucket) - if err != nil { - t.Fatal(err) - } - if len(after.PolicyConfigJSON) == 0 { - wrong++ - } - } - if wrong > 0 { - t.Errorf("DEFAULT_SELECTED: creation-default erased valid policy in %d/32 heal rounds", wrong) - } - }) - t.Run(backend+"/tag-remote-source-time", func(t *testing.T) { - var got []madmin.SRBucketMeta - remote := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var item madmin.SRBucketMeta - if err := json.NewDecoder(r.Body).Decode(&item); err != nil { - t.Error(err) - w.WriteHeader(http.StatusBadRequest) - return - } - got = append(got, item) - w.WriteHeader(http.StatusOK) - })) - defer remote.Close() - svc, err := auth.CreateCredentials("issue77-heal-service", "issue77-heal-service-secret") - if err != nil { - t.Fatal(err) - } - svc.ParentUser = cred.AccessKey - if _, err = globalIAMSys.store.AddServiceAccount(ctx, svc); err != nil { - t.Fatal(err) - } - defer globalIAMSys.DeleteServiceAccount(ctx, svc.AccessKey, false) - peers := map[string]madmin.PeerInfo{localID: {Name: "local", DeploymentID: localID}, remoteID: {Name: "remote", DeploymentID: remoteID, Endpoint: remote.URL}} - remoteClient := &SiteReplicationSys{enabled: true, state: srState{ServiceAccountAccessKey: svc.AccessKey, Peers: peers}} - xml := []byte(`keysame`) - s := status(madmin.SRBucketInfo{CreatedAt: created, Tags: enc(xml), TagConfigUpdatedAt: newerAt}, madmin.SRBucketInfo{CreatedAt: created}, madmin.SRBucketStatsSummary{}) - v := s.BucketStats[bucket][remoteID] - v.TagMismatch = true - s.BucketStats[bucket][remoteID] = v - if err := remoteClient.healTagMetadata(ctx, obj, bucket, s); err != nil { - t.Fatal(err) - } - if len(got) != 1 { - t.Fatalf("remote received %d events", len(got)) - } - if !got[0].UpdatedAt.Equal(newerAt) { - t.Errorf("TAG_WIRE_TIME: sent %s, want %s", got[0].UpdatedAt, newerAt) - } - }) -} - -func TestIssue77CurrentPeerCheckBeforeLock(t *testing.T) { - ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: func(obj ObjectLayer, backend, bucket string, _ http.Handler, _ auth.Credentials, t *testing.T) { - t.Run(backend, func(t *testing.T) { - ctx := t.Context() - meta, err := loadBucketMetadata(ctx, obj, bucket) - if err != nil { - t.Fatal(err) - } - oldAt, newAt := meta.Created.Add(time.Hour), meta.Created.Add(2*time.Hour) - oldXML := base64.StdEncoding.EncodeToString([]byte(`keyolder`)) - newXML := []byte(`keynewest`) - lockCtx, unlock, err := lockBucketMetadata(ctx, obj, bucket) - if err != nil { - t.Fatal(err) - } - locked := true - defer func() { - if locked { - unlock() - } - }() - ready := make(chan struct{}, 1) - hook := func(name string) { - if name == bucket { - select { - case ready <- struct{}{}: - default: - } - } - } - lockBucketMetadataAcquireHook.Store(&hook) - defer lockBucketMetadataAcquireHook.Store(nil) - done := make(chan error, 1) - go func() { done <- globalSiteReplicationSys.PeerBucketTaggingHandler(ctx, bucket, &oldXML, oldAt) }() - select { - case <-ready: - case <-time.After(5 * time.Second): - t.Fatal("older event never reached metadata lock") - } - // A newer writer commits while holding the existing metadata lock. - // The old peer event has already checked the pre-commit cache. - meta.TaggingConfigXML, meta.TaggingConfigUpdatedAt = newXML, newAt - if err := globalBucketMetadataSys.saveMetadata(lockCtx, obj, meta); err != nil { - t.Fatal(err) - } - unlock() - locked = false - select { - case err := <-done: - if err != nil { - t.Fatal(err) - } - case <-time.After(5 * time.Second): - t.Fatal("older event did not finish") - } - after, err := loadBucketMetadata(ctx, obj, bucket) - if err != nil { - t.Fatal(err) - } - if string(after.TaggingConfigXML) != string(newXML) { - t.Errorf("CHECK_OUTSIDE_LOCK: queued older event replaced newer committed tags with %q", after.TaggingConfigXML) - } - }) - }}) -} diff --git a/docs/investigations/issue-77/issue77_review_test.go.txt b/docs/investigations/issue-77/issue77_review_test.go.txt deleted file mode 100644 index fd1a0a041..000000000 --- a/docs/investigations/issue-77/issue77_review_test.go.txt +++ /dev/null @@ -1,147 +0,0 @@ -package cmd - -// Review-only tests against the unchanged 2026-09-12 main. Failures are evidence, -// not a production regression-test submission. -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "net/http" - "testing" - "time" - - "github.com/minio/madmin-go/v3" - "github.com/minio/minio/internal/auth" -) - -func TestIssue77CurrentSourceTimeAndDeletion(t *testing.T) { - ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: review77SourceTimeAndDeletion}) -} - -func review77SourceTimeAndDeletion(obj ObjectLayer, backend, bucket string, _ http.Handler, cred auth.Credentials, t *testing.T) { - ctx := t.Context() - created := UTCNow().Add(-time.Hour) - putAt := created.Add(10 * time.Minute) - delAt := putAt.Add(time.Minute) - enc := func(s string) *string { v := base64.StdEncoding.EncodeToString([]byte(s)); return &v } - base := newBucketMetadata(bucket) - base.SetCreatedAt(created) - base.defaultTimestamps() - quotaJSON, err := json.Marshal(madmin.BucketQuota{Quota: 1024, Type: madmin.HardQuota}) - if err != nil { - t.Fatal(err) - } - cases := []struct { - name, file string - put madmin.SRBucketMeta - value func(BucketMetadata) []byte - stamp func(BucketMetadata) time.Time - exported func(madmin.SRBucketInfo) time.Time - deletable bool - }{ - {"policy", bucketPolicyConfig, madmin.SRBucketMeta{Type: madmin.SRBucketMetaTypePolicy, Policy: []byte(fmt.Sprintf(`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":"*","Action":"s3:GetObject","Resource":"arn:aws:s3:::%s/*"}]}`, bucket))}, func(m BucketMetadata) []byte { return m.PolicyConfigJSON }, func(m BucketMetadata) time.Time { return m.PolicyConfigUpdatedAt }, func(m madmin.SRBucketInfo) time.Time { return m.PolicyUpdatedAt }, true}, - {"tags", bucketTaggingConfig, madmin.SRBucketMeta{Type: madmin.SRBucketMetaTypeTags, Tags: enc(`keyold`)}, func(m BucketMetadata) []byte { return m.TaggingConfigXML }, func(m BucketMetadata) time.Time { return m.TaggingConfigUpdatedAt }, func(m madmin.SRBucketInfo) time.Time { return m.TagConfigUpdatedAt }, true}, - {"sse", bucketSSEConfig, madmin.SRBucketMeta{Type: madmin.SRBucketMetaTypeSSEConfig, SSEConfig: enc(`AES256`)}, func(m BucketMetadata) []byte { return m.EncryptionConfigXML }, func(m BucketMetadata) time.Time { return m.EncryptionConfigUpdatedAt }, func(m madmin.SRBucketInfo) time.Time { return m.SSEConfigUpdatedAt }, true}, - {"quota", bucketQuotaConfigFile, madmin.SRBucketMeta{Type: madmin.SRBucketMetaTypeQuotaConfig, Quota: quotaJSON}, func(m BucketMetadata) []byte { return m.QuotaConfigJSON }, func(m BucketMetadata) time.Time { return m.QuotaConfigUpdatedAt }, func(m madmin.SRBucketInfo) time.Time { return m.QuotaConfigUpdatedAt }, true}, - {"versioning", bucketVersioningConfig, madmin.SRBucketMeta{Type: madmin.SRBucketMetaTypeVersionConfig, Versioning: enc(`Enabled`)}, func(m BucketMetadata) []byte { return m.VersioningConfigXML }, func(m BucketMetadata) time.Time { return m.VersioningConfigUpdatedAt }, nil, false}, - {"objectlock", objectLockConfig, newSRBucketObjectLockMeta(bucket, enc(`EnabledGOVERNANCE30`), putAt), func(m BucketMetadata) []byte { return m.ObjectLockConfigXML }, func(m BucketMetadata) time.Time { return m.ObjectLockConfigUpdatedAt }, nil, false}, - } - exporter := &SiteReplicationSys{enabled: true} - for _, tc := range cases { - t.Run(backend+"/"+tc.name, func(t *testing.T) { - if err := globalBucketMetadataSys.save(ctx, base); err != nil { - t.Fatal(err) - } - item := tc.put - item.Bucket, item.UpdatedAt = bucket, putAt - apply := func(item madmin.SRBucketMeta) { - t.Helper() - rec := applySRBucketMetaViaAdmin(t, cred, item) - if rec.Code != http.StatusOK { - t.Fatalf("admin apply returned %d: %s", rec.Code, rec.Body.String()) - } - } - read := func() BucketMetadata { - t.Helper() - m, err := loadBucketMetadata(ctx, obj, bucket) - if err != nil { - t.Fatal(err) - } - return m - } - apply(item) - put := read() - if len(tc.value(put)) == 0 { - t.Fatal("PUT did not establish live config") - } - if !tc.stamp(put).Equal(putAt) { - t.Errorf("SOURCE_TIME: persisted %s, want source %s", tc.stamp(put), putAt) - } - apply(madmin.SRBucketMeta{Type: item.Type, Bucket: bucket, UpdatedAt: delAt}) - after := read() - if !tc.deletable { - if !bytes.Equal(tc.value(put), tc.value(after)) || !tc.stamp(put).Equal(tc.stamp(after)) { - t.Error("NIL_NOOP: update-only config changed") - } - t.Log("nil payload is correctly a no-op") - return - } - if len(tc.value(after)) != 0 { - t.Error("NEWER_DELETE: HTTP 200, but live config remained after newer source DELETE") - } - if _, err := globalBucketMetadataSys.Delete(ctx, bucket, tc.file); err != nil { - t.Fatal(err) - } - tombstone := read() - if len(tc.value(tombstone)) != 0 { - t.Fatal("local DELETE failed to establish tombstone") - } - info, err := exporter.SiteReplicationMetaInfo(ctx, obj, madmin.SRStatusOptions{Entity: madmin.SRBucketEntity, EntityValue: bucket}) - if err != nil { - t.Fatal(err) - } - if !tc.exported(info.Buckets[bucket]).Equal(tc.stamp(tombstone)) { - t.Errorf("TOMBSTONE_EXPORT: got %s, want %s", tc.exported(info.Buckets[bucket]), tc.stamp(tombstone)) - } - apply(item) - after = read() - if len(tc.value(after)) != 0 { - t.Error("STALE_RESURRECTION: older source PUT resurrected a locally deleted config") - } else { - t.Log("older source PUT did not resurrect config") - } - }) - } -} - -func TestIssue77CurrentBulkApplyOrdering(t *testing.T) { - ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: func(obj ObjectLayer, backend, bucket string, _ http.Handler, cred auth.Credentials, t *testing.T) { - t.Run(backend, func(t *testing.T) { - ctx := t.Context() - meta, err := loadBucketMetadata(ctx, obj, bucket) - if err != nil { - t.Fatal(err) - } - newAt := meta.Created.Add(time.Hour) - newXML := `keynew` - meta.TaggingConfigXML, meta.TaggingConfigUpdatedAt = []byte(newXML), newAt - if err := globalBucketMetadataSys.save(ctx, meta); err != nil { - t.Fatal(err) - } - oldXML := base64.StdEncoding.EncodeToString([]byte(`keyold`)) - item := madmin.SRBucketMeta{Bucket: bucket, Tags: &oldXML, UpdatedAt: newAt.Add(-time.Minute)} - rec := applySRBucketMetaViaAdmin(t, cred, item) - if rec.Code != http.StatusOK { - t.Fatalf("bulk apply returned %d: %s", rec.Code, rec.Body.String()) - } - after, err := loadBucketMetadata(ctx, obj, bucket) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(after.TaggingConfigXML, []byte(newXML)) || !after.TaggingConfigUpdatedAt.Equal(newAt) { - t.Errorf("BULK_STALE_OVERWRITE: older bulk event overwrote newer config, value=%q time=%s", after.TaggingConfigXML, after.TaggingConfigUpdatedAt) - } - }) - }}) -} diff --git a/docs/investigations/issue-77/review/adoption-baseline-probe.go.txt b/docs/investigations/issue-77/review/adoption-baseline-probe.go.txt deleted file mode 100644 index fb2df8493..000000000 --- a/docs/investigations/issue-77/review/adoption-baseline-probe.go.txt +++ /dev/null @@ -1,20 +0,0 @@ -package cmd -import ( - "net/http" - "testing" - "time" - "github.com/minio/minio/internal/auth" -) -func TestIssue77PlanAdoptionBaseline(t *testing.T) { - defer DetectTestLeak(t)() - ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t:t,objAPITest:func(_ ObjectLayer, instanceType,bucket string,_ http.Handler,_ auth.Credentials,t *testing.T){ - before,err:=globalBucketMetadataSys.GetConfigFromDisk(t.Context(),bucket);if err!=nil{t.Fatal(err)} - if len(before.PolicyConfigJSON)!=0 || !before.PolicyConfigUpdatedAt.Equal(before.Created) {t.Fatalf("unexpected initial state: %#v",before)} - olderCreated:=before.Created.Add(-time.Hour) - if err:=globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(t.Context(),bucket,MakeBucketOptions{CreatedAt:olderCreated});err!=nil{t.Fatal(err)} - after,err:=globalBucketMetadataSys.GetConfigFromDisk(t.Context(),bucket);if err!=nil{t.Fatal(err)} - falseTombstone:=len(after.PolicyConfigJSON)==0 && after.PolicyConfigUpdatedAt.After(after.Created) - t.Logf("%s ADOPTION_BASELINE: previous Created=%s field=%s; after Created=%s field=%s; nil with field>Created=%v",instanceType,before.Created.Format(time.RFC3339Nano),before.PolicyConfigUpdatedAt.Format(time.RFC3339Nano),after.Created.Format(time.RFC3339Nano),after.PolicyConfigUpdatedAt.Format(time.RFC3339Nano),falseTombstone) - if !falseTombstone {t.Fatal("expected to reproduce default timestamp becoming an apparent tombstone")} - }}) -} diff --git a/docs/investigations/issue-77/review/adoption-baseline-probe.log b/docs/investigations/issue-77/review/adoption-baseline-probe.log deleted file mode 100644 index a0c8e1551..000000000 --- a/docs/investigations/issue-77/review/adoption-baseline-probe.log +++ /dev/null @@ -1,6 +0,0 @@ -=== RUN TestIssue77PlanAdoptionBaseline - issue77_plan_adoption_test.go:17: ErasureSD ADOPTION_BASELINE: previous Created=2026-09-12T09:49:46.959131+08:00 field=2026-09-12T09:49:46.959131+08:00; after Created=2026-09-12T08:49:46.959131+08:00 field=2026-09-12T09:49:46.959131+08:00; nil with field>Created=true - issue77_plan_adoption_test.go:17: Erasure ADOPTION_BASELINE: previous Created=2026-09-12T09:49:47.012956+08:00 field=2026-09-12T09:49:47.012956+08:00; after Created=2026-09-12T08:49:47.012956+08:00 field=2026-09-12T09:49:47.012956+08:00; nil with field>Created=true ---- PASS: TestIssue77PlanAdoptionBaseline (0.13s) -PASS -ok github.com/minio/minio/cmd 1.458s diff --git a/docs/investigations/issue-77/review/adoption-baseline-probe.metadata.json b/docs/investigations/issue-77/review/adoption-baseline-probe.metadata.json deleted file mode 100644 index c58157569..000000000 --- a/docs/investigations/issue-77/review/adoption-baseline-probe.metadata.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "worktree": "$TMPDIR", - "source_commit": "5c576581631561c446f30ae5b566f0aa793adc1c", - "scope": "diagnostic of existing code only; no product implementation", - "command": "GOWORK=off go test ./cmd -run '^TestIssue77PlanAdoptionBaseline$' -count=1 -v", - "exit_code": 0, - "outcome": "Both ObjectLayers reproduced a nil default state becoming an apparent tombstone after Created moved earlier; no product fix implemented.", - "temporary_worktree_removed": true, - "archive_note": "Workstation paths normalized; billing data and raw model traces omitted. Original hashes are retained in archive-manifest.json." -} diff --git a/docs/investigations/issue-77/review/author-followup-findings.md b/docs/investigations/issue-77/review/author-followup-findings.md deleted file mode 100644 index e2d14da05..000000000 --- a/docs/investigations/issue-77/review/author-followup-findings.md +++ /dev/null @@ -1,9 +0,0 @@ -# 作者补充核验 - -S1:`internal/logger/logonce.go:95` 按错误正文而非只按 key 去重。动态时间进入正文会绕过去重。修订要求:稳定的原因错误,动态详情放 logger ReqInfo,沿用现有每小时清理机制;不新增日志/限频框架。 - -S2:当前 silo-pkg 的 ActionSet.MarshalJSON 与 ResourceSet.MarshalJSON 直接枚举 map。实测同一个已解析 BucketPolicy 连续 json.Marshal 100 次,得到 4 种字节序列。源代码探针为 policy-encoding-probe.go.txt;使用项目 go.mod 选择的 pgsty/silo-pkg/v3 v3.13.4-0.20260910091716-2d8fd3cbbf07,命令为 GOWORK=off go run <探针临时路径>。 - -因此普通 json.Marshal 不能当作稳定排序键。v2 需要一个仅用于 Server 桶策略比较的确定性表示,对已通过现有解析器校验的策略排序集合成员;不能为了这一点修改依赖或持久化 wire 格式。 - -以上为作者在外部 v1 评审期间做的独立补查,不冒充 Claude 的评审发现,也不代表已经实现修复。 diff --git a/docs/investigations/issue-77/review/decisions-v2.md b/docs/investigations/issue-77/review/decisions-v2.md deleted file mode 100644 index 699419036..000000000 --- a/docs/investigations/issue-77/review/decisions-v2.md +++ /dev/null @@ -1,32 +0,0 @@ -# v1 对抗评审意见与 v2 处置 - -评审者:真实 Claude Code 2.1.258,审查消息模型为 `claude-opus-5`,CLI 明确使用 `--effort max`。首轮对 main `5c576581631561c446f30ae5b566f0aa793adc1c` 的冻结 v1 给出 **NO_GO**,耗时约 24 分钟。完整输出见 [首轮评审](opus5-max-v1.md),调用证据见 [元数据](opus5-max-v1.metadata.json)。 - -本文件记录作者核对源码后的处置,不能代替评审者的后续结论。v2 尚未实现。 - -| ID | 处置 | v2 中的具体变化与理由 | -| --- | --- | --- | -| R1 / P1 | 接受 | 选择其方案 b:保留合法零 quota 文档,取消普通 quota PUT 的出站 nil 改写。补充落盘/事件状态三元组一致性。Policy 保留既有 peer 空策略=删除语义,本地/导入/bulk 也归一成删除;说明空策略本地 GET 行为变化。导入空 Policy 用现有专用 nil 事件,避免 bulk omitempty 漏发。 | -| R2 / P1 | 接受,缩小实现方式 | 比较有效归一状态,复用已有 Object Lock→Versioning 规则;bulk 按最终接受的 Object Lock 处理 Versioning。saveMetadata 用指针让调用方保留 Save 后快照;公共 Update/Delete 签名保持不变,需要发送归一载荷的本地路径使用内部结果。写明同时间 live 键较大者胜,导入 hook 来自提交快照。 | -| R3 / P1 | 接受“不得缺省即删除”的要求;纠正部分依据 | RawMessage 的 nil/空切片确实被 omitempty 省略,但显式 JSON null 解码为非 nil 的字节 `null`;非空 JSON 的空策略也可明确表达。实际 madmin-go/Go 编解码探针已验证。因此不采纳“bulk Policy 不可能表达语义删除、只有 Tags/SSE 可以”的绝对结论。v2 列出真实 wire 判定:未提供保留、Policy 提供但语义空则删除、Quota 提供的零值/null 仍为 live,Tags/SSE 显式空字符串删除;T5 经真实 marshal/unmarshal 验证。 | -| R4 / P2 | 接受 | 选源和 apply 都使用 <= Created 的 baseline 判定;候选不晚于目标 Created 时逐目标跳过并限频诊断。独立创建世代冲突需运维,不纳入 T9 自动收敛。 | -| R5 / P2 | 接受但保留兼容例外 | 三种内部状态明确化,非零 baseline 事件不覆盖真实配置。零时间专用事件依 O1 保留兼容应用,它是明确例外;修复后的正常发送端不得产生该状态。 | -| R6 / P2 | 接受 | 比较视图统一把零字段时间回退到 Created,bulk 原始读取与 load/defaultTimestamps 不再得到不同裁决。 | -| R7 / P2 | 接受 | 六类 heal 的源/目标循环跳过 unknown/空 deployment ID;单个 peer 失败继续健康目标,覆盖可达站点不被占位项阻断。 | -| R8 / P2 | 接受 | 明确四类清空走现有 parse=false 新对象;bulk 原始读取后保存。不改 Quota getter 或无关解析分支;保留磁盘/缓存/重载回归。 | -| R9 / P2 | 接受 | 显式保留 Object Lock 载荷在 legacy Tags 字段时的回退与排序测试。 | -| R10 / P2 | 接受 | 升级文档点名旧 Tag heal 无 UpdatedAt,说明其到达时间/legacy-zero 污染不能自动修复。 | -| O1 / 缩减 | 接受 | 删除 on 模式拒绝零时间的新行为;两种模式统一兼容。开关改名 METADATA_TOMBSTONES,只控制新增墓碑导出/初次同步。 | -| O2 / 缩减 | 接受 | 沿用现有 env.Get 的 on/off 写法,不加配置框架。 | -| O3 / 缩减 | 接受 | 旧/新真实进程只作一次性升级冒烟;长期回归用真实 wire/SRInfo 模拟。保留修复版双站点实验以验证实际最终收敛。 | -| O4 / 缩减 | 接受 | 明确复用现有 lockBucketMetadataAcquireHook / RMW 屏障,不新增产品测试钩子。 | -| O5 / 缩减 | 接受 | 删除无行为意义的 deployment ID 日志裁决要求;它不参与持久化排序。 | -| O6 / 缩减 | 部分接受 | 删除不必要的 stale-conflict 警告,剩三类。保留 indeterminate 并明确真实触发条件:未知 Created、不可用来源、有实质差异却无合法候选;它不等于 before-created。 | - -## 作者补充核验 - -- **S1:日志去重。** `internal/logger/logonce.go:95-123` 同时依赖稳定 key 和错误正文,变化时间放正文会导致每轮记录。v2 使用固定原因错误,动态详情放 ReqInfo,不新增框架。 -- **S2:Policy JSON 编码。** 当前 silo-pkg 的 ActionSet/ResourceSet MarshalJSON 枚举 map。同一个已解析策略编码 100 次得到 4 种结果,因此 v1 的普通 JSON 比较键不稳定。v2 增加 Server 内的策略比较键,对已校验策略的集合数组/对象键排序,不改变 wire 或依赖。探针见 [policy-encoding-probe.go.txt](policy-encoding-probe.go.txt)。 -- **S3:R3 的实际 wire 边界。** 使用项目选定的 madmin-go v3.0.110 与 silo-pkg 执行真实编解码,确认显式 null 与 omitted 不同,带 Version 的空 Statement 策略通过现有解析且 IsEmpty=true。源码和输出见 [wire-state-probe.go.txt](wire-state-probe.go.txt)、[wire-state-probe.log](wire-state-probe.log)。`cmd/bucket-quota.go:99-110` 证实 Quota null 由现有解析器接受为零值对象;不把它改成删除。 - -以上均为方案核查和小型诊断。没有修改产品源文件,没有把首轮 NO_GO 改写为通过;后续复审针对冻结 v2 独立给出结论。 diff --git a/docs/investigations/issue-77/review/decisions-v3.md b/docs/investigations/issue-77/review/decisions-v3.md deleted file mode 100644 index b7fca1b9a..000000000 --- a/docs/investigations/issue-77/review/decisions-v3.md +++ /dev/null @@ -1,31 +0,0 @@ -# v2 对抗复审意见与 v3 处置 - -第二轮由相同 Claude Code / `claude-opus-5 --effort max` 执行,仅有 Read/Grep/Glob 工具,耗时约 11 分钟。结论 **NO_GO,1 个新增阻断**;明确确认首轮 R1/R2/R3 已全部关闭,并承认首轮 R3 关于 RawMessage 的部分依据错误。见 [第二轮原文](opus5-max-v2.md)、[调用证据](opus5-max-v2.metadata.json)。 - -## P1-1:历史桶字段时间等于 Created - -接受阻断。源码核对确认历史配置可合法带 `UpdatedAt == Created`,MakeBucketHook 还会把同一 Created 传给新站点。v2 把等号也拒绝,会阻断初次同步,并因 heal 过滤 baseline 而无法补救。 - -对评审中的小处事实作校正:当前 `applyLegacyConfigs` 末尾已经调用 `defaultTimestamps()`,因此“迁移后磁盘字段仍为零”不普遍成立;**字段等于 Created 的反例成立,不影响阻断判断**。 - -v3 的最小修订是保留 baseline 的弱初值语义,不引入新 wire 字段或重盖到达时间: - -| 输入/目标 | v3 行为 | -| --- | --- | -| 历史 live@Created → 空 baseline | 接受初始化,保存相同源时间;初次同步和 heal 一致 | -| baseline-live → baseline-live | 稳定比较键较大者胜;全 baseline-live 也能收敛 | -| baseline-live → 真实 live/tombstone | 不覆盖;真实状态优先级先于时间比较 | -| 空/nil@Created → 任意已有配置 | 不能作为删除,不清空 | -| 非零时间严格早于目标 Created | 原有创建保护,成功/no-op 与有界诊断 | -| 全为空 baseline | found=false,安静不写入 | - -T7/T8 新增上述用例,特别是历史桶从初次同步到完整一轮 heal 的验收;六类类型约束、删除 parse=false、旧事件不能复活 tombstone 均保留。 - -## 四条 P2 - -1. **Policy 稳定键:接受目标,不叠加第二种判等。** v3 明确对已解析策略完整 JSON 树做递归排序,覆盖 NotAction/NotResource 与 Condition,保留数字精度,并由两站点独立解析验证同键。未采纳 Equals 优先再用另一种键的建议:当前 `BucketPolicy.Equals` 对 Statement 次序敏感,而 `BPStatement.Equals` 忽略 Sid,两套判等混用可能使同时间冲突的比较不一致。公开统计现用 Equals 不变。当前 BPStatement 不支持 NotPrincipal,不为此新增语法或改 silo-pkg。 -2. **off 并非禁止删除:接受。** 直写 Policy 原有墓碑在 off 仍参与 heal;普通删除事件一直复制;只有新增 Tags/SSE/Quota 墓碑导出与初次发送受开关控制。 -3. **off 期间过时 RPC:接受。** 隐藏墓碑使部分旧 RPC 仍被尝试、在接收端被排序拒绝;这是升级阶段明确代价,不要求零 RPC,不加每轮日志。稳定无额外 RPC 的验收要求明确放在完整状态可见之后。 -4. **空策略 API 行为:接受。** T1 明确带合法 Version 的空策略 PUT 成功,随后 GET NotFound,落盘与专用删除事件三元组一致;不能误改为 4xx。 - -三个实现提交没有增加,也没有新增协议、框架、依赖或长期集成门槛。v3 只需对本次 baseline 规则与以上说明做最后针对性复审;已关闭的首轮问题不重开全仓库审查。当前仍是设计文件,没有实现或执行实现期测试。 diff --git a/docs/investigations/issue-77/review/decisions-v4.md b/docs/investigations/issue-77/review/decisions-v4.md deleted file mode 100644 index dee030c83..000000000 --- a/docs/investigations/issue-77/review/decisions-v4.md +++ /dev/null @@ -1,19 +0,0 @@ -# v3 通过后的作者实测与 v4 最小补充 - -Opus 第三轮已经给出 **GO_WITH_NONBLOCKING_NOTES,实施前阻断 0**,确认历史 baseline-live 初始化规则正确。见 [第三轮意见](opus5-max-v3.md)。其两条可执行非阻断说明(历史初值第二轮不再广播、deployment ID 不参与比较/持久化)已明确写入 v4。 - -## S4:接管改变 Created 会把默认时间变成假墓碑 - -这是作者补查发现,不冒称为 Opus 结论。 - -当前 `PeerBucketMakeWithVersioningHandler`(`cmd/site-replication.go:946-961`)先加载并补齐默认字段时间,再调用 SetCreatedAt 改写 Created。若桶被接管到较早的共同 Created,原本 `PolicyUpdatedAt == oldCreated` 且 policy=nil 的缺省状态,变成 `PolicyUpdatedAt > newCreated`,新排序/导出规则便会把它当成真实删除。 - -已在固定 main 上的独立 worktree 执行 `GOWORK=off go test ./cmd -run '^TestIssue77PlanAdoptionBaseline$' -count=1 -v`:ErasureSD 与 Erasure(默认多盘)都复现了该条件。Created 前移一小时,nil Policy 的字段时间留在原 Created。测试 PASS 的含义是**旧代码确实出现该现象**,不是修复通过;详见 [测试源码](adoption-baseline-probe.go.txt)、[实际输出](adoption-baseline-probe.log)、[元数据](adoption-baseline-probe.metadata.json)。测试只新增在临时 worktree,没有改产品代码。 - -## 最小修订 - -在现有接管锁内记住 oldCreated,执行现有 SetCreatedAt 得到 newCreated;仅当 Created 改变时,将本轮六类字段中原本为 0 或 oldCreated 的默认时间调整为 newCreated,再执行既有 bootstrap。所有真实字段时间(原本 > oldCreated)及载荷不动。绝不能仅以 nil 载荷识别默认状态,否则会丢失真正删除时间。 - -这不是另做 #78 的配置接管修复,而是让 #77 新比较器需要的 baseline 不变量跨越已有 SetCreatedAt 写入路径仍成立。只在提交 1 加小分支,T8 增加默认时间维持 baseline、真实 PUT/DELETE 时间保留的断言;没有增加实现提交、存储字段、协议、锁或依赖。 - -v4 其余行为与已通过的 v3 相同;第四轮只核查这个接管增量。 diff --git a/docs/investigations/issue-77/review/final-review.md b/docs/investigations/issue-77/review/final-review.md deleted file mode 100644 index 0de659b3b..000000000 --- a/docs/investigations/issue-77/review/final-review.md +++ /dev/null @@ -1,32 +0,0 @@ -# Issue #77 最终方案评审记录 - -2026-09-12,最终计划为 [issue-77-plan.md](../../issue-77-plan.md)。固定代码基准为 main `5c576581631561c446f30ae5b566f0aa793adc1c`;当日重新查询 #77 仍为 OPEN。 - -**结论:GO_WITH_NONBLOCKING_NOTES,实施前阻断 0。** 作者已把末轮非阻断说明落实到定稿,可进入实现;本轮没有修改产品代码、提交 PR、关闭 Issue 或发布制品。 - -## 实际执行的外部评审 - -使用本机 Claude Code 2.1.258,每轮均明确传入 `--model claude-opus-5 --effort max`,在固定 main 的隔离 worktree 中执行,仅提供 Read/Grep/Glob。所有记录到的评审 assistant 消息模型均为 `claude-opus-5`。CLI 的用量信息另含少量辅助模型调用,已如实保留在 metadata;这不是用其它模型代替 Opus 评审。 - -| 轮次 | 待审快照 | 结果 | 处置 | -| --- | --- | --- | --- | -| 1,全面方案审查 | [v1](plan-v1.md) | [NO_GO,3 个 P1](opus5-max-v1.md) | 修正零 quota 保存/发送不一致、Object Lock 归一后的提交快照、bulk wire 删除规则;缩减开关及验收范围 | -| 2,修订复审 | [v2](plan-v2.md) | [NO_GO,新增 1 个 P1](opus5-max-v2.md) | 首轮 3 项确认关闭;修正历史 live@Created 的初始化回归 | -| 3,历史初值增量 | [v3](plan-v3.md) | [GO_WITH_NONBLOCKING_NOTES,0 阻断](opus5-max-v3.md) | baseline-live 可初始化、不能覆盖真实状态;默认空值不能删除 | -| 4,接管增量 | [v4](plan-v4.md) | [GO_WITH_NONBLOCKING_NOTES,0 阻断](opus5-max-v4.md) | 作者实测假墓碑后新增最小时间归一分支,经 Opus 确认必要性与充分性 | - -逐项记录:[首轮处置](decisions-v2.md)、[第二轮处置](decisions-v3.md)、[接管增量](decisions-v4.md)。每轮相邻的 `opus5-max-vN.metadata.json` 保存命令、模型、工具、耗时、源 SHA、待审计划哈希及结果哈希。 - -## 末轮非阻断说明的落实 - -- T8 已明确 Created 前移、后移与不变三种情况;历史 baseline-live 第一轮补齐,第二轮无额外写入/广播。 -- 保持只调整 #77 六类字段,在实现注释中说明边界。不采纳扩大到其它配置时间的可选建议:那些时间不是新比较器的输入,没有必要同批改变其行为。 -- Policy 稳定键与公开计数的判等职责明确;Sid 差异可以触发一次状态同步,但不重做 #91 的既有统计语义。递归规范化和数字精度由 T2 验证。 - -定稿相对于第四轮冻结快照只补充上述验收/注释说明和评审状态,没有新增实现范围。 - -## 证据边界 - -作者实测使用了实际 Go 编解码、当前 silo-pkg 的 Policy 编码,以及 ErasureSD/默认多盘 ObjectLayer 的接管路径。接管诊断 PASS 表示在未修复 main 上成功观察到假墓碑条件。既有问题复现记录见 [当前核验](../../issue-77-current.md)。 - -外部 reviewer 做的是源代码与方案核查,没有运行新实现测试;现在不存在本轮产品实现。局部回归、race、完整 CI、修复版双站点收敛、一次性升级冒烟均是后续实现阶段的验收,不得把本次评审结论当成这些验证已经通过。 diff --git a/docs/investigations/issue-77/review/opus5-max-v1.md b/docs/investigations/issue-77/review/opus5-max-v1.md deleted file mode 100644 index aef81524a..000000000 --- a/docs/investigations/issue-77/review/opus5-max-v1.md +++ /dev/null @@ -1,124 +0,0 @@ -Write 工具在本会话被禁用,无法落盘评审文件,因此评审报告直接输出如下。 - -# SILO #77 修复计划 v1 对抗评审 - -基准:worktree = main `5c576581631561c446f30ae5b566f0aa793adc1c`。本次只做方案评审:未改产品代码、未运行测试、未写 GitHub、未调用其他代理。 - -**证据分级**:**[源码]** 本次在该 SHA 直接确认(给文件:行)/**[复现]** `issue-77/current-tests.log` 已记录/**[推论]** 由源码事实推导、未执行/**[待验]** 需实现期测试。 - ---- - -## 结论:NO_GO - -方向正确,但 §3 / §5 / §6 三处**契约文字**按字面实现会产生新的不收敛与潜在错删。计划约 85% 的内容经源码核对成立,不需要推翻结构——**修订 R1、R2、R3 后复审即可进入实现**。 - ---- - -## P1 阻断项 - -### R1 零配额在发送端被改写成删除,本地却保留 live 文档 → off(默认)永不收敛,on 主动删除本地合法文档 - -**源码位置** -- `cmd/admin-bucket-handlers.go:85` 本地写入用的是**原始请求体** `data`; -- `cmd/admin-bucket-handlers.go:97-99` `if quotaConfig.Size == 0 && quotaConfig.Quota == 0 { bucketMeta.Quota = nil }` —— 发出的事件被改写成**删除**; -- `cmd/site-replication.go:2088-2107` 对端 `quota == nil` 走 `Delete`; -- `madmin-go v3.0.110 quota-commands.go:54-60` `Quota == 0` 时 `IsValid()` 恒真(`{}` 是合法 live 文档),且该版本**只有 `SetBucketQuota`,没有删除端点** —— "清除配额"只能走这条零值 PUT; -- `cmd/site-replication.go:4090-4094` 导出端仅在 `len(QuotaConfigJSON) > 0` 时带出时间,删除时间被隐藏。**[源码]** - -**计划条目**:已确定契约"合法的空 quota 对象 `{}`…不能仅因配额为零就改写为 nil";§3"同时间真实删除大于 live";提交 3 tombstone 导出;完成条件"四类 DELETE"、T7。 - -**触发步骤** -1. A 站点执行等价于 `mc quota clear`(零值 SetBucketQuota)。 -2. A 落盘 live `{...}`@T;发出的事件是 `Quota=nil`@T。 -3. B 按计划落盘 tombstone@T。 -4. **off(默认)**:B 的 tombstone 时间不导出 → B 候选被当 baseline 过滤 → 选源永远是 A 的 live@T → 推给 B → B 按"同时间删除优先"拒绝 → **每 30 秒(`site-replication.go:4651`)一次 RPC + 一次拒绝日志,永不收敛**。 -5. **on**:B 导出 tombstone@T → 同时间删除胜 → heal 反过来**删除 A 上合法的 live 文档**。 - -**影响**:这是清除配额的常规路径。主干今天同样不收敛(到达时间更大导致拒绝),所以**不是回归,但计划声称会修而按 v1 修不了**,T1/T7 不可达成。**[源码]+[推论]** - -**同类第二处**:`cmd/bucket-policy-handlers.go:87-109` 本地存 live 的"语义为空"策略,`cmd/admin-handlers-site-replication.go:233-237` 在接收端判为删除。Policy 仍能收敛,只因 `site-replication.go:4069-4070` **无条件**导出 `PolicyUpdatedAt`。可见根因是"发送端语义 ≠ 本地落盘语义",不是导出策略。 - -**最小修正(二选一,必须成对,不能只改一侧)** -- (a) 解析后 quota 为零值结构时本地也走 `Delete`(parse=false),事件仍为 nil;或 -- (b) 取消 `Quota = nil` 改写,事件携带真实文档。 - -并把"同一次本地操作,落盘状态与发出事件状态必须是同一状态"写成契约不变量;T1 增加三元组 (kind, payload, time) 一致性断言。 - ---- - -### R2 `saveMetadata` 内部静默归一化:落盘载荷 ≠ 比较载荷 ≠ 事件载荷;§6"无状态变化不保存"按字面不可实现;§3 未定义字节序方向 - -**源码位置** -- `cmd/bucket-metadata.go:391-399` `parseAllConfigs` 在 `objectLockConfig != nil` 且 versioning 文档解析失败 / `!Enabled()` / `PrefixesExcluded()` 时改写 `VersioningConfigXML` 为常量,**不改 `VersioningConfigUpdatedAt`**; -- `cmd/bucket-metadata.go:581-603` `Save` 每次保存前都调 `parseAllConfigs` → 归一化发生在**每一次** `saveMetadata`; -- `cmd/bucket-metadata-sys.go:232-243` `saveMetadata(…, meta BucketMetadata)` **按值接收**,归一化只作用于被落盘/入缓存的副本,调用方手里的结构仍未归一化; -- `cmd/admin-bucket-handlers.go:1094-1116` 导入把未归一化的 `merged` 赋回 `*meta`,随后 `hook.Versioning = enc(meta.VersioningConfigXML)` —— **发出的载荷与落盘载荷不同,时间戳却相同**; -- `cmd/admin-bucket-handlers.go:823-835` 导入只拒绝 `Suspended`,**不拒绝 `PrefixesExcluded()`**(`internal/bucket/versioning/versioning.go:153-155`:`ExcludedPrefixes` 或 `ExcludeFolders` 均算); -- `cmd/bucket-versioning-handler.go:68-84` S3 PUT 只在**本地** LockEnabled 时拒绝 prefix-excluded; -- 现有回归 `cmd/site-replication-object-lock_test.go:149-167` 已把该机制固化为断言。**[源码][复现-通过]** - -**计划条目**:§3"live/live 按稳定载荷字节序比较""apply 和 heal 使用同一比较函数";§6"无状态变化时不保存、不通知";提交 1 完成条件"导入的持久化时间与实际发出的事件一致"。 - -**为什么阻断** -1. §6 只能在 `saveMetadata` 之前比较,而真正落盘的是归一化之后的结构——**规则按字面无法实现**,"无变化"的判定可能与磁盘不符。 -2. 计划把到达时间换成源时间后,**同一时间戳 + 不同载荷成为正常可达状态**(Object Lock 在站点间短暂不对称时必然出现);旧代码靠"到达时间总在前进"意外掩盖了它。 -3. §3 **没写明大者胜还是小者胜**。以 `…Enabled` 对 `…Enabled` 为例,分歧字节是 `E`(0x45) 与 `/`(0x2F):方向选错,heal 每轮把未归一化文档推给已归一化站点,对方再归一化 → 重复写 + 全节点 `LoadBucketMetadata` 广播;方向选对则一轮收敛。**让正确性取决于未写明的字节序方向不可接受。** -4. 导入路径违反提交 1 完成条件的实质:时间一致、状态不一致。 - -**诚实边界**:我**没有**证明这会无限发散。Object Lock 不可删除,`healOLockConfigMetadata`(`5379-5447`)通常 1 轮内补齐,之后两侧归一化结果相同并收敛。实际损害是"窗口期重复写/广播 + 实现定义的裁决"。**[源码]+[推论],永久性未证实。** - -**最小修正**:比较器对 Versioning 先做与 `parseAllConfigs` 相同的归一化再比较;`saveMetadata` 回写/返回已持久化结构,导入与所有 hook 用**落盘后的字节**;§6 改为"以归一化后的落盘表示判定状态变化";§3 写明字节序方向并注明它只用于真并发冲突,不得用来消化序列化差异。 - ---- - -### R3 §5 的"bulk 中显式提供且为空 = 删除"对 Policy/Quota 在线路上不可表达;按字面实现存在"缺省即删除"的错删路径 - -**源码位置** -- `madmin-go v3.0.110 cluster-commands.go:501-530`:`Policy`/`Quota` 是 `json.RawMessage` + `omitempty` —— **nil 与空切片都会被省略**,解码端永远拿到 nil;而 `Tags/SSEConfig/Versioning/ObjectLockConfig` 是 `*string`,`ptr("")` 可表达"提供且为空"; -- `cmd/site-replication.go:1653-1697` bulk 一律按 `!= nil` 判"是否提供"; -- `cmd/admin-bucket-handlers.go:1103-1131` 导入发出的正是 `Type` 为空的 bulk 事件,且只填本次导入涉及的字段。**[源码]** - -**风险**:对 Policy/Quota,这条规则要么是走不到的死代码,要么诱导实现者写 `len(item.Policy) == 0 ⇒ 删除` —— 那样**每个只导入 tags 的 bulk 事件都会删掉对端 bucket policy**,正是 #77 要消灭的错删类。这是规范缺陷,不是当前代码缺陷。**[源码]+[推论]** - -**最小修正**:§5 改为——bulk 只有 Tags/SSE 能表达删除(显式空 base64 串);Policy/Quota 删除只走专用事件;bulk 缺省字段任何情况下不改变该字段。T5 补两条断言。 - ---- - -## P2(建议同批修订) - -- **R4 before-created 扩大到六类,但选源端不做同一判定。** `site-replication.go:1646-1651`(bulk 已有)、`2035-2040`(CORS 先例)、`bucket-metadata.go:190-197`(`SetCreatedAt` 无条件覆盖)、`site-replication.go:5486-5512`(`healBucket` 不修复两站点 Created 分歧)。§提交 2 只过滤 `==0` 与 `==CreatedAt`,**不过滤"早于自身 Created"**:分区期间两站点各自建过同名桶后,较老站点的真实配置被永久拒绝,heal 每轮仍选中它并挡住其它候选(今天会收敛)。**最小修正**:选源与 apply 用同一可用性判定(`time <= 自身 CreatedAt` 视为 baseline);对 `目标 CreatedAt > 候选时间` 按目标跳过并记一次 `before-created`;计划中明确这是**终态不收敛、需运维介入**,不计入 T9。 -- **R5 比较器缺第三种 kind(baseline)。** `site-replication.go:1851-1898` 的 `corsReplicationState` 已有三值模型。§3 只定义"删除 vs live",把"创建默认/未知"放在选源侧 → 任何异常发送端发出的 `nil + 创建默认时间` 在 apply 侧会被当 tombstone,同时间即可删掉对端 live。**最小修正**:六类复用 CORS 三值模型(baseline = 时间为零或不晚于 Created),apply 对 baseline 一律 no-op;顺带获得 `latestCORSConfig`(`5315-5329`)的稳定选择语义。 -- **R6 两个入口读到的"当前字段时间"来源不同。** `site-replication.go:1641` 用 `readBucketMetadata`(**不调** `defaultTimestamps`),`bucket-metadata.go:230-238` 才补默认值 → 同一记录在两条路径上分别是 `0` 与 `Created`,事件时间恰等于 `Created` 时裁决不同。**最小修正**:比较器统一 `if fieldTime.IsZero() { fieldTime = meta.Created }`。 -- **R7 heal 中不可达站点占位项会中途终止本轮修复。** `3136-3142` 把不可达站点填 `SRInfo{}`,`3219` 以空 DeploymentID 进入 `BucketStats`;`5006-5009`、`5070-5073`、`5145-5148` 等处 `getAdminClient("")` 失败即 `return`,**放弃该类型剩余站点**,且是否提前退出取决于 map 顺序。与 T9 承诺冲突。**最小修正**:两处循环都跳过 `info.Sites` 中不存在的 dID 并计入 `indeterminate`。 -- **R8 parse=true 置空字段留下已解析缓存。** `bucket-metadata.go:401-413`(versioning、quota)与 `334-338`(notification)在载荷为空时**无 else 分支**(即 [复现] `QUOTA_CACHE`)。§6 的"删除走 parse=false"规避有效且最小,但应写成显式不变量并在 T7 加断言。**不建议**本轮补 else —— 会改变 `GetQuotaConfig` 返回语义(`bucket-metadata-sys.go:572-581` 不做 nil 判断),超出范围。 -- **R9 Object Lock 的 legacy `Tags` 载荷回退必须保留。** `site-replication.go:1787-1796` + 回归 `site-replication-object-lock_test.go:132-138`。提交 1 的重构容易丢掉;T3 应显式覆盖"Type=ObjectLockConfig、载荷在 Tags 字段"的排序行为。 -- **R10 off 模式下 legacy-zero 事件盖上本地新时间后永久胜出。** `site-replication.go:5011-5015` 至今不带 `UpdatedAt`([复现] `TAG_WIRE_TIME`)。计划已有"升级后由操作者重新提交"的兜底,建议在文档条目里点名这个具体来源。 - ---- - -## 过度设计 / 可缩减 - -- **O1 开关承担三件事,只有一件有证据。** 证据(quota 缓存复现)只支持"tombstone 导出需等全站点升级"。"on 模式拒绝零源时间事件"没有对应缺陷,且计划已修复树内唯一的零时间发送端;保留它只会在误开时让 heal 每 30 秒报错。**建议把开关收缩为单一含义**:是否导出/发送真实删除状态(含初次同步);零时间事件两种模式统一按 legacy-zero 兼容应用 + 限频记录。 -- **O2 开关实现不需要新框架。** `cmd/common-main.go:902` 已有 `env.Get(name, config.EnableOff) == config.EnableOn` 的两行先例,照抄即可。 -- **O3 T8 的"固定旧版与修复版两个 SILO 进程"不必作为提交门槛。** 旧版对线路的可观测差异只有两点(heal 事件不带 `UpdatedAt`;导出不含 nil 载荷时间),进程内构造 `SRInfo`/`SRBucketMeta` 即可完整覆盖;降级为一次性人工冒烟。 -- **O4 T4 不需要新建"确定性锁屏障"。** 已有 `lockBucketMetadataAcquireHook`(`bucket-metadata-sys.go:249-255`)与 `bucket-metadata-lock_test.go` 的 `runBucketMetadataRMWConflict` 模式可直接复用。 -- **O5 §3 的 deployment ID 规则可删。** "仅在状态完全相同时用于日志来源"无可观测行为;复用 `latestCORSConfig`(`5322` 的 `cmp == 0 && dID > latestID`)即自动获得稳定性。 -- **O6 `indeterminate` 缺少明确产生条件。** 若采纳 R4,`before-created` 与 `indeterminate` 会合并,应减为三类,不要为对称保留空类别。 - ---- - -## 经核对成立、不应削减 - -锁外 getter→Update 竞态([复现] `TestIssue77CurrentPeerCheckBeforeLock` 失败);bulk 绕过逐字段排序(`1653-1697` 无字段时间比较,[复现] 失败);选源前过滤创建默认值([复现] 5/32、2/32 错删 Policy;六处重复写法 `4966-4982`、`5038-5054`、`5104-5120`、`5178-5194`、`5253-5269`、`5393-5409` 抽函数是净减法);Tag 远端 heal 缺 `UpdatedAt`(`5011-5015`);同内容不同时间不同步([复现] `BARRIER_NOT_HEALED`);复用 `metadata.lock` 与 `saveMetadata` 的物理桶检查(`232-243`)、放弃 `peer-require-existing`;导入 `updatedAt := UTCNow()`(`723`)在 ZIP 开始时取一次且可能早于随后创建的桶 `Created`,per-bucket `commitAt` 修正必要。另:`lockBucketMetadataWithTimeout` 不可重入,重入会挂到 `globalOperationTimeout`(非永久死锁但同样不可接受),"不循环调用公开 handler"正确;实现内部 apply 时须沿用 `updateAndParse` 传递 lock-ctx,否则 `loadBucketMetadataParse` 的迁移分支(`bucket-metadata.go:252`、`282`)会再次取锁。 - ---- - -## 实施前必须修订的条目 - -1. **R1** 零值配额的发送端/本地写一致性 + "落盘状态 == 发出状态"不变量。 -2. **R2** §6 比较基准改为归一化后的落盘表示;`saveMetadata` 回写已持久化结构;导入 hook 用落盘字节;§3 写明同时间载荷序方向及适用边界。 -3. **R3** §5 重写 bulk 删除语义(仅 Tags/SSE 可表达;缺省永不改变字段)+ 对应断言。 - -建议一并吸收 R4、R5 措辞修正与 O1、O3、O4 缩减,再做一次短复审即可进入实现。 - -**实现阶段需要运行(本次未运行)**:把两份审计用例改为确定性回归并先记录未修复失败;R1 零配额两侧三元组一致 + off/on 各一轮 heal 无重复 RPC;R2 Object Lock 桶 + prefix-excluded/ExcludeFolders versioning 走导入与 peer 两条路径,断言"发出载荷 == 落盘载荷"且第二轮 heal 无写入;R3 bulk 缺省/空串两类断言;既有回归 `TestPeerBucketObjectLockMetadata*`、`TestPeerBucketAdoption*`、`TestQueuedMetadataUpdateAfterDelete`、`TestSiteReplicationStatusAccountsPerSiteAndSurvivesMalformedConfig`、CORS 站点复制用例,加目标 race 与完整 Go CI。 diff --git a/docs/investigations/issue-77/review/opus5-max-v1.metadata.json b/docs/investigations/issue-77/review/opus5-max-v1.metadata.json deleted file mode 100644 index 2ee21216e..000000000 --- a/docs/investigations/issue-77/review/opus5-max-v1.metadata.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "reviewer": "Claude Code", - "cli_version": "2.1.258", - "requested_model": "claude-opus-5", - "requested_effort": "max", - "source_commit": "5c576581631561c446f30ae5b566f0aa793adc1c", - "permission_mode": "plan", - "tools": [ - "Read", - "Grep", - "Glob" - ], - "round": 1, - "plan_snapshot": "plan-v1.md", - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --output-format stream-json --verbose", - "status": "completed", - "model_configuration_reference": "https://code.claude.com/docs/en/model-config", - "observed_model": "claude-opus-5", - "session_id": "9fb76061-6bcc-490c-a96b-9a1d4f9daa84", - "observed_tools": [ - "Glob", - "Grep", - "Read" - ], - "verdict": "NO_GO", - "subtype": "success", - "is_error": false, - "duration_ms": 1437147, - "num_turns": 57, - "assistant_models": [ - "claude-opus-5" - ], - "plan_sha256": "0218a2ca59750acd6a24dc5ece1d55a19762a863f1a2de8edc6ffc9b4ab072e0", - "auxiliary_model_note": "CLI may report small auxiliary model calls in model_usage; assistant_models lists the actual recorded review message models. max was explicitly supplied in the CLI command.", - "collected_at_utc": "2026-09-12T01:57:46.010985+00:00", - "stream_sha256": "a145cd75ebf2ab3b722c7e1764036fcac8e3245ed2de3c2390dc0b5826e63d16", - "review_sha256": "8a3b750a261ef520acbe658395d3303cea0835b07184b96022781a2a5d0d4dcf", - "used_tools": [ - "Glob", - "Grep", - "Read", - "Write" - ], - "temporary_review_worktree_removed": true, - "archive_note": "Workstation paths normalized; billing data and raw model traces omitted. Original hashes are retained in archive-manifest.json." -} diff --git a/docs/investigations/issue-77/review/opus5-max-v2.md b/docs/investigations/issue-77/review/opus5-max-v2.md deleted file mode 100644 index d846b06de..000000000 --- a/docs/investigations/issue-77/review/opus5-max-v2.md +++ /dev/null @@ -1,44 +0,0 @@ -# SILO #77 修复计划 v2 对抗复审(Claude Code Opus 5,只读) - -基准 worktree = main `5c576581631561c446f30ae5b566f0aa793adc1c`。本轮只针对 v1→v2 的修订差异做源码核验:未改产品代码、未运行测试、未调用其他代理。 - -## 首轮三个阻断的关闭判断 - -**R1(零配额发送端改写 / 落盘≠出站):已关闭。** v2 取消 `admin-bucket-handlers.go:97-99` 的 `Quota=nil` 改写后,`mc quota clear` 两侧都落 live 零值文档:`bucket-quota.go:99-110` 对 `{}`/`null` 均解析为零值且 `IsValid()` 为真,`isBktQuotaCfgReplicated`(`site-replication.go:3850-3888`)此时 `numquotaCfgs==total`、Type 相同 → 判为已复制,`quotaCfgSet`(`3635`)仍为 false,#91 计数不受影响。接收端 `PeerBucketQuotaConfigHandler:2089` 会重新 `json.Marshal` 导致字节不同,但 v2 §2 规定 Quota 比较键取"解析结果的 JSON 编码",`madmin.BucketQuota` 是定长结构体、编码确定,不会因此空转。Policy 侧改成本地/导入/bulk 统一按 `IsEmpty()` 删除,与既有 `admin-handlers-site-replication.go:233-237` 一致,三元组不变量成立。 - -**R2(saveMetadata 静默归一 / §6 不可实现 / 字节序方向未定):已关闭。** `saveMetadata`(`bucket-metadata-sys.go:232`)全部 6 个调用点(`site-replication.go:961/1708/2049`、`bucket-metadata-sys.go:203/222/354`、`admin-bucket-handlers.go:1095`)传入的都是调用方自有值,改指针回写无别名风险;导入 hook 改用提交快照后,`1110-1127` 与落盘文档不再分叉;比较改用 `parseAllConfigs:391-399` 同一条 Object Lock→Enabled 归一规则,方向也已写明"键较大者胜"且禁止用字节序补偿隐式改写。 - -**R3(bulk 删除语义):已关闭,且首轮依据确属错误。** 复核确认:`json.RawMessage` 显式 `null` 解码为非 nil 的 `[]byte("null")`(探针 log 第 3-4 行),`policy/bucket-policy.go:78` 允许 `Version==""`,故 `null` 与 `{"Version":...,"Statement":[]}` 都能通过 `ParseBucketPolicyConfig` 且 `IsEmpty()` 为真;`*string` 的 `null` 才退化成 nil。v2 的逐字段规则与 `PeerBucketMetadataUpdateHandler:1653-1697` 的 `!=nil` 约定一致,也堵住了"`len==0 ⇒ 删除"的错删诱导。唯一 bulk 生产者(导入 `1103-1131`)在 `omitempty` 下漏发空 Policy 的问题,v2 用专用 nil 事件补齐,处置正确。 - -## P1(新增阻断,1 项) - -**P1-1:`fieldTime <= Created` 一律判 baseline,会让历史桶的配置在初次同步中被静默丢弃且 heal 永不修复。** - -反例(全部为已修复版本、同一桶世代、合法带时间事件,属计划承诺范围): - -1. A 站点有 2020 年前迁移来的桶。`applyLegacyConfigs`(`bucket-metadata.go:482-513`)不写任何 `*UpdatedAt`,`convertLegacyConfigs` 保存后磁盘上这些字段仍是零值;每次 `loadBucketMetadataParse:236-238` → `defaultTimestamps:534-561` 把 `PolicyConfigUpdatedAt` 补成 **正好等于 Created**。 -2. 运维执行 `AddPeerClusters`。`MakeBucketHook:821-823` 把 `createdAt` 传给对端,`PeerBucketMakeWithVersioningHandler:950` 的 `SetCreatedAt` 使 B 的 `Created` 与 A **完全相等**。 -3. `syncToAllPeers:2217-2228` 发出 Policy@Created(非零,因此不走 legacy-zero 例外)。 -4. 按 v2 §3"非零事件**不晚于**目标桶 Created 时成功/no-op,记录 before-created" → B 直接 no-op,policy 落不下去。 -5. 按 v2 §2/提交 2.1,A 的候选是 baseline → 过滤 → "无候选,显式 found=false" → 永远不 heal,只产出 indeterminate。 - -主干今天这条路径是成功的(`PeerBucketPolicyHandler:1718-1746` 无 before-created 检查,直接应用),bulk 侧 `1646` 也只拒绝 **严格早于** Created。因此这是回归,不是遗留缺陷;Tags/SSE/Quota 同理,Object Lock 因 `955-959` 自举侥幸掩盖。T1–T9 没有"字段时间等于 Created 的历史桶初次同步"用例,实现期不会被发现。 - -最小修正(两句契约 + 一行验收):①把 apply 侧边界改成**严格早于** Created 才 before-created;`fieldTime == Created` 保留 baseline 的"弱"语义。②baseline 只是排序上低于任何非 baseline 候选,不是"不可用":目标该字段自身也是 baseline/缺失时,baseline-live 仍可确立初值,仅在存在非 baseline 候选时被压制;全 baseline 且内容不同时按既定键排序取胜者,而不是报 indeterminate。③T7/T8 增加一行"全部字段时间等于 Created 的历史桶,初次同步与一轮 heal 后各站点一致"。 - -## P2(仍需改动) - -- **Policy 比较键的构造方式。** 递归排序自造规范 JSON 需要覆盖 `Condition` 的 `map[string]map[string]ValueSet`、`NotAction/NotResource/NotPrincipal`,一旦两站点对同一策略算出不同键,同时间平局会各自选出不同胜者 → 每轮互推、不收敛。建议判等直接复用现成的 `BucketPolicy.Equals`(`site-replication.go:3912` 已在用),规范串只用于同时间平局排序,并复用 `ActionSet.String()`(silo-pkg `policy/actionset.go:151-158`,已排序)。同时把"键必须是**已解析策略**的纯函数、跨站点必须一致"写成不变量,T2 补一条"两站点各自字节 → 相同键"断言。 -- **开关语义与 Policy 的实际不对称需写进文档。** `site-replication.go:4069-4070` 无条件导出 `PolicyUpdatedAt`,因此提交 1/2 落地后,**开关 off 时 Policy 删除照样会经 heal 传播**,只有 Tags/SSE/Quota 被门控。v2 表格里"初次同步的真实删除:off 保留已有行为"容易被读成"off 完全不传播删除",运维会据此做错误的升级判断。 -- **off 模式下的稳定态 RPC 要写进验收预期。** 持有 Tags/SSE/Quota 墓碑的站点在 off 下导出不带时间 → 每轮仍被选为待修复目标 → 每 30s(`4651`)一次必然被拒的 RPC。提交 2 的"对已归一且相同的目标不写入、不发 RPC"不覆盖该情形。请明确这是 off 期间的预期代价,并禁止为它加每轮日志(会与 §3 的三类有界日志冲突)。 -- **空策略 PUT 的对外行为补一条断言。** v2 让本地 `PutBucketPolicy` 对 `{"Version":"2012-10-17","Statement":[]}`(`bucket-policy-handlers.go:98-101` 只拒空 Version,该文档合法)改走删除。T1 请断言"PUT 返回成功、GET 返回 NotFound、出站专用事件与落盘一致",避免实现时误改成 4xx。 - -## 作者补查核验 - -**S1 成立。** `internal/logger/logonce.go:109` 仅在 `prev.Err.Error() == nerr.Error()` 时抑制;正文含变化时间会每轮重新打印(且分支不更新 map,等于完全失效)。固定正文 + ReqInfo 的做法正确。 - -**S2 成立。** `policy/actionset.go:144-168` 的 `MarshalJSON → ToSlice` 直接遍历 map 不排序,`json.Marshal(BucketPolicy)` 确实不稳定,v1 的字节比较键不可用;v2 另建比较键的结论正确(构造方式见上 P2)。 - ---- - -**结论:NO_GO。实施前阻断数:1(P1-1)。** 首轮 R1/R2/R3 三项均已实质关闭,R4–R10、O1–O6 的处置未引入新的明确阻断;余下 4 条 P2 建议与 P1-1 一并修订即可进入实现,不需要再做全量复审。 diff --git a/docs/investigations/issue-77/review/opus5-max-v2.metadata.json b/docs/investigations/issue-77/review/opus5-max-v2.metadata.json deleted file mode 100644 index a090e6068..000000000 --- a/docs/investigations/issue-77/review/opus5-max-v2.metadata.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "reviewer": "Claude Code", - "cli_version": "2.1.258", - "requested_model": "claude-opus-5", - "requested_effort": "max", - "source_commit": "5c576581631561c446f30ae5b566f0aa793adc1c", - "permission_mode": "plan", - "tools": [ - "Read", - "Grep", - "Glob" - ], - "round": 2, - "plan_snapshot": "plan-v2.md", - "plan_sha256": "2b2c712b17f757ca1a8501714c6c2472bd442a6be50b29d92f2d87e23c412ba8", - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --output-format stream-json --verbose", - "status": "completed", - "observed_model": "claude-opus-5", - "session_id": "e40ce534-ece8-430f-841d-99add4fab13d", - "observed_tools": [ - "Glob", - "Grep", - "Read" - ], - "assistant_models": [ - "claude-opus-5" - ], - "subtype": "success", - "is_error": false, - "duration_ms": 666254, - "num_turns": 41, - "collected_at_utc": "2026-09-12T01:40:02.942542+00:00", - "stream_sha256": "7b3aa8de142e77b4ea1bca468685a04203261021c7978c3a97a27d1febacacab", - "review_sha256": "848a476f18ce88208b72d8bf6cb49a2316b2b14dcba64ad66e636fe0a3d70595", - "used_tools": [ - "Glob", - "Grep", - "Read" - ], - "auxiliary_model_note": "CLI may report small auxiliary model calls in model_usage; assistant_models lists the actual recorded review message models. max was explicitly supplied in the CLI command.", - "verdict": "NO_GO", - "blocking_findings": 1, - "closed_previous_blockers": [ - "R1", - "R2", - "R3" - ], - "temporary_review_worktree_removed": true, - "archive_note": "Workstation paths normalized; billing data and raw model traces omitted. Original hashes are retained in archive-manifest.json." -} diff --git a/docs/investigations/issue-77/review/opus5-max-v3.md b/docs/investigations/issue-77/review/opus5-max-v3.md deleted file mode 100644 index 65fcd31d2..000000000 --- a/docs/investigations/issue-77/review/opus5-max-v3.md +++ /dev/null @@ -1,22 +0,0 @@ -## P1-1:已关闭 - -v2 的阻断链条有三个环节,v3 逐个断开: - -1. **apply 侧边界**:计划 §2 第 39 行改为「非零事件**严格早于**目标桶 Created 才成功/no-op 并记录 before-created」,并明写「等于目标 Created 的 live 事件可更新仍是 baseline 的目标字段,时间仍保存为源 Created」。v2 反例第 4 步(`==` 被判 before-created)不再成立。 -2. **heal 候选**:第 37 行只把「空 baseline」排除出源,baseline-live 是初始化候选;提交 2.1(第 72 行)过滤条件同样是「严格早于**自身** Created」,等于 Created 不被滤掉。v2 反例第 5 步(found=false 永不修复)不再成立。 -3. **验收**:T8 第 111 行有「历史字段时间全部等于 Created 的桶经初次同步和一轮 heal 后一致」,T7 第 110 行有 baseline-live 初始化与同级收敛。 - -## 四条反例核验 - -1. **历史 live@Created → 空字段**:初次同步走 apply 分支(baseline-live 胜空 baseline,时间存原值 Created);漏发时 heal 也能选到该候选。写入后两侧同为 baseline-live@Created、键相等,按第 40 行「键、状态级别和源时间相同为 no-op」+ 提交 2.4 稳定。✅ -2. **较晚空默认值**:第 40 行排序先比状态级别,「任何真实 live/tombstone 都胜 baseline」,且空 baseline 既不是源也不是删除(37/39 行、decisions 表第 18 行)。时间更晚的空默认值无法压过较早真实修改。✅ -3. **不复活真实删除 / 同级收敛**:baseline-live「永远不能覆盖真实 live/tombstone」在 heal 选源与 apply 两侧同一套规则(提交 2 标题即「选源与应用同规则」),因此 off 模式下墓碑隐藏时,被选中的 baseline-live 推到持有墓碑的站点仍被接收端拒绝——是过时 RPC,不是复活。全 baseline-live 不同载荷按同一稳定键较大者胜,两侧独立计算同键(第 41 行纯函数不变量),单轮收敛且不回摆。✅ -4. **四条 P2**:均落到契约或验收行(41 行取消双判等、92 行 off≠禁止删除且不加每轮日志、T1 空策略 PUT 成功/GET NotFound)。剩余风险是 Policy 键的实现细节(递归排序与大整数精度),已转为 T2 断言,属实现期验证,非方案阻断。✅ - -## 非阻断注记 - -- 比较键含 Sid,而公开统计仍用忽略 Sid 的 `Equals`:仅 Sid 差异时 heal 会写一次、状态接口报已复制;收敛后二者一致,不会持续分歧。 -- 建议把 T8 历史桶那行的「一轮 heal」补一句第二轮无写入/广播(与第 78 行完成条件同口径),可选。 -- 第 40 行末「同状态来源不参与持久化冲突裁决」措辞含糊,实现前口头澄清即可。 - -**结论:GO_WITH_NONBLOCKING_NOTES。实施前阻断数:0。** 未发现新反例;已关闭范围(R1/R2/R3、六类 handler、导入、计数、调用图)未重审。 diff --git a/docs/investigations/issue-77/review/opus5-max-v3.metadata.json b/docs/investigations/issue-77/review/opus5-max-v3.metadata.json deleted file mode 100644 index f38919354..000000000 --- a/docs/investigations/issue-77/review/opus5-max-v3.metadata.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "reviewer": "Claude Code", - "cli_version": "2.1.258", - "requested_model": "claude-opus-5", - "requested_effort": "max", - "source_commit": "5c576581631561c446f30ae5b566f0aa793adc1c", - "permission_mode": "plan", - "tools": [ - "Read", - "Grep", - "Glob" - ], - "round": 3, - "plan_snapshot": "plan-v3.md", - "plan_sha256": "8c4ab51a397246a07bb1cc967df3377d26ac084b7928191eb8fecacf6e7c3aed", - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --output-format stream-json --verbose", - "status": "completed", - "observed_model": "claude-opus-5", - "session_id": "3ae0c14b-58cf-415d-8d2d-3a388d322534", - "observed_tools": [ - "Glob", - "Grep", - "Read" - ], - "subtype": "success", - "is_error": false, - "duration_ms": 181195, - "num_turns": 4, - "collected_at_utc": "2026-09-12T01:50:13.403953+00:00", - "stream_sha256": "728542385eb75e02050c17a389b49d2b4dfacdf1c54801fc6792694f91f08cff", - "review_sha256": "557ef70288a9a3939842a2f76d63694e2c103a8bfcaa99b7c161ee89e1f58061", - "assistant_models": [ - "claude-opus-5" - ], - "used_tools": [ - "Read" - ], - "auxiliary_model_note": "CLI may report small auxiliary model calls in model_usage; assistant_models lists the actual recorded review message models. max was explicitly supplied in the CLI command.", - "verdict": "GO_WITH_NONBLOCKING_NOTES", - "blocking_findings": 0, - "temporary_review_worktree_removed": true, - "archive_note": "Workstation paths normalized; billing data and raw model traces omitted. Original hashes are retained in archive-manifest.json." -} diff --git a/docs/investigations/issue-77/review/opus5-max-v4.md b/docs/investigations/issue-77/review/opus5-max-v4.md deleted file mode 100644 index 2bfaa44c9..000000000 --- a/docs/investigations/issue-77/review/opus5-max-v4.md +++ /dev/null @@ -1,16 +0,0 @@ -## 复核结论(第四轮,仅针对接管增量) - -**必要性成立,证据链闭合。** `loadBucketMetadataParse` 内 `defaultTimestamps`(`cmd/bucket-metadata.go:534`)先把字段时间补成 oldCreated,随后 `SetCreatedAt`(`:190-197`)无条件改写 `Created`,两者之间无补偿;probe 日志两个 ObjectLayer 均得 `nil with field>Created=true`,与 v4 新比较器「时间 > Created 即真实状态、nil 载荷即 tombstone」直接相撞,会伪造删除并向外广播。补丁点选在 `cmd/site-replication.go:946-950` 现有 `lockBucketMetadata` 内,位置正确。 - -**充分性成立。** 用「为 0 或 == oldCreated」判定默认值,与计划 §39「等于 Created 仍属 baseline」同一口径,重映射不改变任何状态级别;真实(> oldCreated)时间与全部载荷不动,故不覆盖真修改/真删除。不触 payload、不新增写入或广播路径,#78 配置保护不受影响;baseline-live 之间按稳定键而非时间决胜,平移时间不改变胜负。 - -另补一条支持性事实:`enablePeerBucketVersioning:901` 与 handler `:957` 的 `IsZero` 守卫在此路径上已被 `defaultTimestamps` 提前失效,bootstrap 分支本身无法把时间修正到 newCreated——归一化是唯一补救,必要性比 decisions-v4 描述的更强。 - -**两点非阻断修正:** - -1. `SetCreatedAt` 对方向无约束,newCreated 也可能晚于 oldCreated;此时旧默认时间变成「严格早于自身 Created」,按 §37 被当作无效候选过滤,历史 baseline-live 静默丧失初始化能力。计划正文写的是「如果 Created 改变」(双向,正确),但 decisions-v4 §S4 与 T8 只写「前移」。T8 应补后移方向断言。 -2. `defaultTimestamps` 覆盖 11 个字段,补丁只归一六类,其余五类接管后遗留 != Created 的伪真实时间。#77 比较器不读它们,不阻断;实现时请在代码注释写明限定理由,或直接对该函数的全集做同样归一(同等成本,不扩大范围)。 - -未发现范围外扩、未证实假设或与已关闭项的冲突;独立桶世代合并仍在既定边界外,正确。 - -**GO_WITH_NONBLOCKING_NOTES;实施前阻断数 0。** diff --git a/docs/investigations/issue-77/review/opus5-max-v4.metadata.json b/docs/investigations/issue-77/review/opus5-max-v4.metadata.json deleted file mode 100644 index 0eff0ec13..000000000 --- a/docs/investigations/issue-77/review/opus5-max-v4.metadata.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "reviewer": "Claude Code", - "cli_version": "2.1.258", - "requested_model": "claude-opus-5", - "requested_effort": "max", - "source_commit": "5c576581631561c446f30ae5b566f0aa793adc1c", - "permission_mode": "plan", - "tools": [ - "Read", - "Grep", - "Glob" - ], - "round": 4, - "plan_snapshot": "plan-v4.md", - "plan_sha256": "fbfd37498ab32a589d06254f32182869d5d49fc97722416c46c10b41e5f77816", - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --output-format stream-json --verbose", - "status": "completed", - "observed_model": "claude-opus-5", - "session_id": "72c12e7b-d830-4f3a-9066-5fa829de7a4f", - "observed_tools": [ - "Glob", - "Grep", - "Read" - ], - "subtype": "success", - "is_error": false, - "duration_ms": 112227, - "num_turns": 10, - "collected_at_utc": "2026-09-12T01:55:02.590226+00:00", - "stream_sha256": "d8f3509c695749cb5fae9e2f7142b887e48a950a5d96818f73a05529feab2bea", - "review_sha256": "cf10b9d7963ff2b7d4c0fbb1fe744a0e853549eaf7ce675a94cc000f74fe6682", - "assistant_models": [ - "claude-opus-5" - ], - "used_tools": [ - "Grep", - "Read" - ], - "auxiliary_model_note": "CLI may report small auxiliary model calls in model_usage; assistant_models lists the actual recorded review message models. max was explicitly supplied in the CLI command.", - "verdict": "GO_WITH_NONBLOCKING_NOTES", - "blocking_findings": 0, - "nonblocking_notes_disposition": "Final plan adds Created earlier/later/unchanged acceptance and a comment limiting timestamp normalization to the six fields. No implementation scope change.", - "final_plan_sha256": "2ee214694f16e5949ed58290364770149aee3748faee3614122fee189ef83add", - "temporary_review_worktree_removed": true, - "archive_note": "Workstation paths normalized; billing data and raw model traces omitted. Original hashes are retained in archive-manifest.json." -} diff --git a/docs/investigations/issue-77/review/plan-v1.md b/docs/investigations/issue-77/review/plan-v1.md deleted file mode 100644 index f951e966f..000000000 --- a/docs/investigations/issue-77/review/plan-v1.md +++ /dev/null @@ -1,103 +0,0 @@ -# Issue #77 最小充分修复计划 - -版本:v1,2026-09-12。状态:作者自审完成,待 Claude Code Opus 5 Max 对抗评审。 - -基准:`pgsty/silo` main `5c576581631561c446f30ae5b566f0aa793adc1c`。本计划只授权设计与评审,不表示已实现、合并或发布。复现材料见 [当前核验](../../issue-77-current.md)。 - -## 目标与范围 - -让修复版本间的 Policy、Tags、SSE、Quota、Versioning、Object Lock 桶配置,在合法带源时间的 peer 事件被延迟、重复、乱序或漏发后,能按同一规则应用并由 heal 最终收敛;删除状态必须落盘、能传播,不能被较旧配置复活。桶元数据批量导入的复制入口属于同一范围。 - -本轮不重做 #91 的计数、#76 的 Object Lock wire 字段、#78 的桶接管、#103/#156 的整记录锁和删除保护。CORS、Lifecycle/expiry、notification、对象数据复制、MRF、resync、IAM 均不改变其语义。支持与验收对象是协调的 PGSTY 栈;不引入为了上游 MinIO 兼容而修改依赖的工作。 - -## 已确定的行为契约 - -| 类型 | 专用事件 nil 或解码后空内容 | bulk 字段未提供/null | 真实删除状态导出 | -| --- | --- | --- | --- | -| Policy / Tags / SSE / Quota | 删除;保留源时间,删除走现有 parse=false 路径 | 不修改该字段 | Policy 保留现状;另三类受下述单一开关控制 | -| Versioning / Object Lock | no-op,不能通过 heal 删除配置 | 不修改该字段 | 不新增删除语义 | - -合法的空 quota 对象 `{}` 仍按现有 quota 解析语义处理,不能仅因配额为零就改写为 nil。Policy 空策略继续使用现有专用 admin handler 的判空行为。所有既有字段编码、公开 getter 的 NotFound 行为、RPC 返回结构及权限要求保持不变。 - -1. **读取原始字段时间。** 排序使用 `BucketMetadata` 的原始 `*UpdatedAt` 字段,不能用会隐藏删除时间的公开 getter,也不能用整记录 `lastUpdate()` 代替字段时间。 -2. **创建边界与未知值。** `UpdatedAt == 0` 或 `UpdatedAt == CreatedAt` 的记录不能作为 heal 的权威候选。后者沿用既有创建默认值约定,包括无法恢复真实修改时间的旧记录;全部为此类状态时不写入并给出有界诊断。peer 事件早于当前桶 CreatedAt 时不应用。新产生的实际配置写入时间必须严格大于 CreatedAt。 -3. **带时间事件的确定性次序。** 先比较源时间;相同时间下,真实删除大于 live,live/live 按稳定载荷字节序比较;内容和时间均相同则 no-op。Policy/Quota 使用既有解析后 JSON 编码作为比较表示;XML 使用解码后的实际文档字节。apply 和 heal 使用同一个比较函数。deployment ID 只在完全相同状态下用于选取日志中的来源,不能作为未持久化的冲突元数据。 -4. **本地新操作不倒退。** 对上述六类的实际本地写入,锁内分配 `max(UTCNow(), CreatedAt+1ns, 当前字段时间+1ns)`。其他配置类型保持原有 UTCNow 行为。保留现有 Object Lock 强制 Enabled versioning 的规则,不改变 suspend、prefix exclusion、retention 的合法性。 -5. **nil 与删除区分。** 只有可删除类型的专用事件,或 bulk 中明确提供且按既有规则解析为空的可删除字段,表示删除。bulk 的 nil 必须继续表示未提供。Versioning/Object Lock 的 nil/空输入在任何路径均不能清空配置。 -6. **写入原子性。** 原始读取、比较、修改及保存全部在现有桶 `metadata.lock` 内。更新用现有 parse=true 路径,删除用 parse=false 路径;bulk 保留当前原始读取、最后统一解析保存的方式,避免 Quota 已解析缓存残留。任何字段校验失败不得保存部分 bulk。无状态变化时不保存、不通知;有变化时成功保存一次,解锁后再通知。 - -## 实施顺序 - -### 提交 1:原子排序与本地事件时间 - -改动主要在 `cmd/bucket-metadata-sys.go`、`cmd/site-replication.go` 和 `cmd/admin-bucket-handlers.go`,只增加六类字段的内部访问/比较/更新时间能力,不引入注册表、接口插件或复制框架。 - -- 复用现有 `updateAndParse` 的加载、类型设置、保存和通知边界,增加内部 peer 源时间输入及是否应用的结果。公开 `Update/Delete` 的签名保持不变;专用 peer handler 的公开 error-only 签名也不变。 -- 六个 peer handler 删除锁外 getter 判旧,使用内部源时间更新/删除路径。对非零源时间执行上述比较,持久化原始源时间而非到达时间;重复和被拒绝的旧事件不落盘。 -- 批量 `PeerBucketMetadataUpdateHandler` 对六类已提供字段在其已有整记录锁内执行同一比较。不能循环调用会再次取得桶锁的公开 handler。保留 bulk 中未提供字段、既有 CORS 分支以及一次保存语义。 -- 普通本地六类写入用字段内单调时间。`enablePeerBucketVersioning` 在实际修改现有配置时也用该时间函数;仅缺失字段的创建 bootstrap 保留 CreatedAt 默认值,不给默认配置伪造一次新修改。 -- **导入生成端一并修复。** 在每桶最终提交锁内,对本次导入涉及的六类字段分配一个共同 `commitAt`:严格大于 CreatedAt 和这些字段现存时间,并且不早于锁内当前时刻。覆盖导入暂存对象中这些字段的时间后,再调用现有 `applyImportedBucketMetadata`。发送 bulk hook 的 UpdatedAt 必须是同一个 commitAt,不能再用整个 ZIP 请求开始时的 updatedAt。未导入字段不改;CORS 继续自己的时间函数和独立事件,Lifecycle/notification 不参与这个时间上界。 -- 原样保留 `saveMetadata` 的物理桶存在检查、桶删除锁序、后台通知上下文和加载/迁移路径。不得另加一套 peer-require-existing 或新锁。 -- 对真实历史桶 `Created == 0`,仅在该少见路径取得现有物理桶 Created 并补齐,再按现有默认时间规则加载;物理桶不存在就返回现有错误。若物理创建时间也未知,不能用本次到达时间伪造桶世代:不应用并报告 indeterminate。 - -提交 1 的完成条件:六类 peer PUT 落盘保留源时间;四类 DELETE 后旧 PUT 不复活;相同内容重复事件不保存;getter→Update 竞态和 bulk 旧事件覆盖被挡住;本地后续写入可以胜过已有未来源时间;导入的持久化时间与实际发出的事件一致。 - -### 提交 2:heal 统一选源与状态同步 - -改动主要在 `cmd/site-replication.go`。 - -- 先过滤未知/创建默认状态和 update-only 的空候选,再从剩余候选中按相同比较器选最新;必须显式返回 found=false。取消“先用 map 首项初始化,再判断是否默认”的写法。 -- 为 Policy、Tags、SSE、Quota、Versioning、Object Lock 六类 heal 替换对应选源循环。不触碰 CORS、Lifecycle 或对象数据修复路径。 -- 本地 heal 通过提交 1 的源时间更新/删除路径;远端 heal 保留对应事件类型并携带选中状态的源时间,补齐 Tag 的 UpdatedAt。 -- heal 是否写入由完整状态比较决定,不再由公开 mismatch 计数或仅 payload 相等决定。即使内容相同,较旧时间也必须同步;完全相同的状态不保存、不发送。 -- 保留 #91 的 payload/数量统计语义和字段,不为实现时间戳同步更改 madmin API。持续 indeterminate 或因桶创建边界拒绝而留下的状态差异通过提交 3 的日志解释。 - -提交 2 的完成条件:map 顺序不影响选源;默认状态不能删除真配置;全默认/未知状态不修改磁盘;nil 不清空 Versioning/Object Lock;Quota 删除后磁盘和缓存一致;远端 Tag 时间完整;同内容不同时间、同时间不同内容都能确定性收敛。 - -### 提交 3:删除传播与有界诊断 - -使用一个启动配置开关,暂定名称 `MINIO_SITE_REPLICATION_METADATA_CONVERGENCE`,`off` 为默认,`on` 为显式启用。不加入 madmin/silo-pkg 字段、不新增 RPC、不构建通用能力协商。 - -| 行为 | off:兼容升级阶段 | on:全站点修复后 | -| --- | --- | --- | -| 有源时间的 peer apply、锁内排序、heal | 使用提交 1/2 的修复 | 同左 | -| 专用事件零源时间 | 保留旧版兼容:锁内按本地新操作时间应用,限频记录兼容降级;此类事件不在排序收敛保证内 | 拒绝为无效事件,不写入 | -| bulk 零源时间 | 维持当前拒绝行为 | 同左 | -| Tags/SSE/Quota nil payload 时间导出 | 维持旧版 payload 条件导出 | 导出真实删除时间;零/创建默认仍不是删除事件 | -| Policy 时间导出 | 保持已有行为 | 保持已有行为 | -| 初次同步的删除状态 | 保持已有行为 | Policy/Tags/SSE/Quota 的真实删除也发送专用 nil + source time 事件 | - -这里的开关只隔离新增的删除传播和不再接受无时间事件的行为。它不证明远端能力;启用的明确条件是所有参与站点、每站点全部节点都已部署包含提交 1/2/3 的修复版本,旧请求已排空。配置同一站点内必须一致。任何旧节点仍在线时保持 off。 - -- exporter 只把可区分的真实删除状态作为新增导出对象,不把新桶默认配置改成删除。Policy 已有导出保留,选择端过滤默认值。 -- `syncToAllPeers` 也使用同一状态判断发送真实删除;不能只修定期元数据查询而遗漏初次同步。保持现有逐类型 RPC 和编码。 -- 复用已有日志去重/限频设施,固定为有限的原因集合:legacy-zero、stale-conflict、before-created、indeterminate。精确重复属于正常 no-op,不记警告;普通顺序已知的旧事件不制造每轮噪音;仅实际错误/持续无法收敛的原因记录桶、配置类型、当前/源时间,不输出完整策略或配置。 -- stale 与 before-created 保持 RPC 成功/no-op;缺桶、非法 payload 和 on 模式下的零时间仍返回相应错误。不开新重试循环。 -- 更新 Server 的 site-replication 文档,写清 off/on、全节点升级条件、零时间兼容降级和回滚限制。回滚/降级前先在所有修复节点关闭该开关,再滚动降级;旧软件原有缺陷会恢复,不宣称回滚后仍具备收敛保证。 -- 旧版本已经记录成到达时间的历史值不能推导回真实源时间。升级完成后,操作者在看到各站点状态后选择权威配置,重新提交需要纠正的配置/删除;不得自动把时间归零或选任意站点强制覆盖。 - -## 最小验收矩阵 - -以下均为缺陷或上述修复引入的行为边界;不把发布制品、外部上游服务或不相关产品作为本计划硬门槛。 - -| 组 | 必须验证 | 验证层 | -| --- | --- | --- | -| T1 | 六类 PUT 原样源时间;四类 DELETE;较旧 PUT/DELETE 不回退;精确重复无写入 | 真实 admin 路由 + 两种 ObjectLayer + 磁盘重载 | -| T2 | 同时间不同 live 状态两种到达顺序结果相同;可删除类型 PUT/DELETE 同时间删除获胜 | 比较器确定性测试 + 代表性真实 handler | -| T3 | Versioning/Object Lock nil/空不删除;保留 lock→Enabled versioning 约束;空 quota 对象语义 | 类型回归 + 现有 #76/#78 测试 | -| T4 | 旧 peer 在锁前通过检查,较新写入先提交后旧 peer 再落锁,不能覆盖;并发不同字段均保留 | 确定性锁屏障 + 两种 ObjectLayer,针对性 race | -| T5 | bulk 有旧字段/新字段/未提供字段混合,分别忽略/应用/保留;非法字段不部分保存;不能借 bulk 复活墓碑 | admin bulk 路由 + 磁盘/缓存 | -| T6 | 本地操作的时间大于收到的未来时间;两个相邻本地提交不倒序;ZIP 导入期间插入写入,最终提交字段与发出的 bulk 时间完全一致 | 本地 API、import 路由、捕获 RPC | -| T7 | 多站点排序排列、默认/零时间、全部无候选、空 update-only;同 payload 新时间、同时间冲突;Tag 出站源时间;Quota nil 后缓存清空 | heal + 本地/远端路径 | -| T8 | 保存/缓存失效/进程重启后墓碑仍有效;缺桶/排队写入/真实零 Created 历史桶;on/off 与新旧发送端混合;初次同步携带真实删除 | ObjectLayer、现有删除测试、固定旧版与修复版两个 SILO 进程 | -| T9 | 短暂断线、事件漏发/重复/乱序后重新连通,六类允许状态收敛;开关 on 后旧零时间事件失败且无修改;日志有界 | 隔离的双站点/多节点实验 | - -实现期间先把已有审计用例改成确定性正式回归:不能将反复遍历 map 的概率复现直接提交为生产测试。先记录未修复失败,再验证修复后通过。完成局部修复后运行相关现有回归、目标 race、仓库必需的格式/静态检查与完整 Go CI;同一基准通过后不因形式要求反复全量测试。 - -## 作者自审结论 - -- 必要:六类共享的是字段排序与锁内写入;删除、编码、bulk 未提供字段和 Object Lock 约束仍按类型处理。 -- 充分:普通本地写、专用 peer、bulk/import、local/remote heal、导出、初次同步、重载均在清单内。没有继续保留绕过排序的实际写入入口。 -- 最小:复用一把已有锁、现有持久化和通知、一套小的字段比较逻辑;只加一个针对实际混合版本风险的开关。没有存储 schema、SDK、Console、客户端或新协议依赖。 -- 明确限制:off 模式、无时间旧事件、任意未升级节点和已污染的历史时间不承诺自动收敛;on 模式的验收仅针对修复 PGSTY 栈的合法状态与已知桶世代。 -- 当前只批准进入外部方案评审。任何对抗评审发现的具体遗漏或多余工作,应先修订本文件并复审,再决定进入实现。 diff --git a/docs/investigations/issue-77/review/plan-v2.md b/docs/investigations/issue-77/review/plan-v2.md deleted file mode 100644 index 7915aa772..000000000 --- a/docs/investigations/issue-77/review/plan-v2.md +++ /dev/null @@ -1,118 +0,0 @@ -# Issue #77 最小充分修复计划 - -版本:v2,2026-09-12。状态:已处理首轮对抗评审并完成作者复核,待 Claude Code Opus 5 Max 复审。 - -基准:`pgsty/silo` main `5c576581631561c446f30ae5b566f0aa793adc1c`。本轮交付是方案与评审,不表示已经实现、合并或发布。问题证据见 [当前核验](../../issue-77-current.md),首轮意见及处置见 [评审处置](decisions-v2.md)。 - -## 目标与边界 - -修复 Policy、Tags、SSE、Quota、Versioning、Object Lock 六类桶配置的源时间丢失、锁外判旧、heal 错选源和删除传播不完整。覆盖普通本地写入、专用 peer 事件、bulk/import、local/remote heal、元数据导出与初次同步。 - -在修复 PGSTY 栈、同一已知桶世代、合法带源时间事件的范围内,使重复、乱序和漏发后的状态能够确定性收敛。开关关闭期间新增删除信息不可见,不承诺完整删除收敛;无时间旧事件、旧版污染时间和桶创建世代冲突需要单独解释,不能自动推断历史真相。 - -不重做 #91 的计数、#76 的 Object Lock wire 修复、#78 的桶接管、#103/#156 的锁与删除保护。不改变 CORS、Lifecycle/expiry、notification、对象复制、MRF、resync、IAM 的语义;不改存储 schema、SDK、Console、mcli 或 silo-pkg,不新建能力协商、复制框架、锁或重试系统。 - -## 行为契约 - -### 1. 明确事件、缺省值与删除 - -| 类型 | 专用事件 | bulk 字段未提供 | bulk 字段明确提供 | -| --- | --- | --- | --- | -| Policy | nil 为删除;沿用现有解析器 `IsEmpty()` 为删除 | 保留 | 非空 RawMessage 按现有解析器处理;语义空策略归一为删除 | -| Quota | nil 为删除;非 nil 按现有 quota 解析器处理 | 保留 | 非空 RawMessage 按现有解析器处理,零 quota 仍是 live 文档 | -| Tags / SSE | nil 或 base64 解码后空内容为删除 | 保留 | 空字符串为删除,其他内容按原规则解码、校验 | -| Versioning / Object Lock | nil/空内容为 no-op | 保留 | nil/空内容仍为 no-op,不能清空配置 | - -“未提供”必须依据真实 wire 类型判定:Policy/Quota 的 `json.RawMessage` 为 nil 或空切片时经 `omitempty` 省略;**显式 JSON `null` 解码后是非 nil 的 `[]byte("null")`**,不能与缺省混淆。按现有解析器,Policy `null` 是语义空策略,Quota `null` 是零值 quota 文档。`*string` 类型的 JSON `null` 则解码为 nil。不得使用统一的 `len(payload)==0 => 删除` 来处理 bulk。 - -合法 `{}`/零值 quota 保持 live;取消普通 quota PUT 出站时“零配额改写为 nil”的逻辑,保存与发送同一个语义状态。Policy 保留现有专用 peer 的“空策略=删除”解释,本地 PUT、导入和 bulk 统一归一为同样的删除状态;这是需要写入兼容说明的小范围变化:空策略本地立即按删除处理,GET 返回既有 NotFound 行为,不再先保留空文档、等复制后才被清除。 - -**同一次操作的落盘状态与出站事件,经同一归一规则后,必须具有相同的 `(kind, payload key, source time)`。** JSON 的无意义编码次序不要求字节相同;Object Lock 改写后的有效 Versioning 文档必须来自提交结果。 - -### 2. 统一状态与排序 - -内部仅需一个小状态表示:baseline / live / tombstone,以及比较键和字段源时间。复用 CORS 已有设计思路,不改 CORS 本身或扩展为通用框架。 - -- 使用 `BucketMetadata` 原始字段时间;字段时间为零时在比较视图中补为 Created,与 `defaultTimestamps()` 一致,不借用会隐藏墓碑时间的 getter 或整记录 `lastUpdate()`。 -- 在已知 Created 下,零时间或 `fieldTime <= Created` 是 baseline;它不能成为 heal 权威源,也不能作为带时间 peer 更新覆盖实际状态。Versioning/Object Lock 的空候选无论时间如何都不参与选源。 -- 专用 peer 的零时间保留兼容例外:锁内按本地新操作分配时间并限频记录 legacy-zero;不受删除传播开关影响,不在源时间排序保证之内。bulk 零时间仍按现状拒绝。 -- 非零事件不晚于目标桶 Created 时成功/no-op,记录 before-created。heal 同时过滤源端 baseline;对候选时间不晚于目标 Created 的目标跳过,不能每轮继续发送必然拒绝的 RPC。不同桶世代不能自动合并,需要运维处理,不纳入收敛承诺。 -- 有效状态先按源时间排序;同时间 tombstone 胜 live;同时间 live/live 的**稳定比较键字节序较大者胜**。键与时间相同为 no-op。同状态来源按现有稳定遍历/选源方式取即可,deployment ID 不参与持久化冲突裁决。 -- Quota 使用现有解析结果的 JSON 编码作为比较键。Policy 在 Server 内生成确定性比较表示:既有解析器校验/去重后,对 JSON 对象键和集合数组递归排序,包括 Statement、Action、Resource、Principal、Condition 值。普通 `json.Marshal(BucketPolicy)` 不稳定,不能直接作键;不为此修改 silo-pkg 或 wire/schema。 -- XML 使用有效文档字节,保留大小写和实际内容;Versioning 先应用下述现有 Object Lock 约束。比较器不能依靠字节序方向来补偿保存阶段的隐式改写。 - -### 3. 先得到有效状态,再比较与提交 - -原始读取、类型处理、比较、修改及保存均在现有 `metadata.lock` 内。内部入口必须传递现有 lock context,避免 legacy migration 再次取锁。 - -- 提取并复用 `parseAllConfigs` 已有的 Object Lock→Enabled Versioning 归一规则,使比较视图和真正保存一致;不得扩大 suspend、prefix exclusion、retention 的限制。bulk 先确定实际接受的 Object Lock,再比较该约束下的 Versioning,并在最终提交前应用同一规则。 -- 以归一化后的有效 `(kind, key, time)` 判定变化;更新了时间也算变化。完全重复不保存、不通知;一次 bulk 校验失败不保存部分结果;成功至多保存一次,解锁后通知。 -- 可删除字段清空必须基于现有 parse=false 的新加载对象,不能在已经解析且仍持有旧 quota 的对象上执行 Update(nil)。bulk 保留原始读取再解析保存的方式。不要顺手修改 Quota getter 或所有 `parseAllConfigs` 空分支。 -- 保存函数必须让需要发送 hook 的调用方拿到**本次提交的最终快照**,不能先解锁再读取“最新”状态拼接旧时间。最小做法是让内部 `saveMetadata` 接收元数据指针并回写 Save 的归一化结果,机械更新现有少量调用;对外 `Update/Delete` 签名不变,新增内部提交结果仅供需要该快照的本地 handler/import 使用。不得原地修改已发布到缓存的引用字段。 -- 保留现有物理桶存在检查、删除锁序、迁移、后台通知上下文。真实历史桶 Created 为零时仅走少见的物理桶 Created 补齐路径;物理桶缺失返回现有错误;创建时间仍未知则不伪造到达时间,报告 indeterminate。 - -## 三个实现提交 - -### 提交 1:原子 apply、发送一致性与本地时间 - -主要文件:`cmd/bucket-metadata-sys.go`、`cmd/bucket-metadata.go`、`cmd/site-replication.go`、`cmd/admin-bucket-handlers.go`,以及实际需要提交快照的本地配置 handler。 - -1. 在现有 update/delete 内部路径增加源时间、状态比较和提交结果;公开签名不变。六个 peer handler 移除锁外 getter 判旧,锁内持久化原始源时间。保留 Object Lock 的 legacy Tags 字段载荷回退。 -2. bulk 对明确提供的六类字段在已有锁内逐字段比较,再原子保存;未提供字段不动,不能循环调用会重入锁的公开 handler。保留 CORS 独立分支与既有行为。 -3. 六类本地实际写入在锁内分配 `max(UTCNow(), Created+1ns, 当前字段时间+1ns)`。其他类型不变。`enablePeerBucketVersioning` 的实际变更也使用它,只有缺失配置的创建 bootstrap 继续 Created 默认值。 -4. quota 本地 PUT 保留零值文档并原样表示该语义;Policy 空策略本地与 peer 一致走删除。需要归一化的本地 handler 从本次提交快照生成 hook;其他内容不发生归一变化的路径可保留既有编码,但必须满足三元组一致性。 -5. 导入在每桶最终提交锁内,为本次涉及的六类字段生成共同 commitAt,严格大于 Created 和这些字段当前时间且不早于锁内现在。该时间同时用于落盘和 bulk hook,不能沿用 ZIP 开始时间。bulk hook 从最终提交快照构建;若导入的空 Policy 已归一成删除,另外使用现有专用 Policy nil 事件表达它,不能因 `omitempty` 漏发。未导入字段不改,Object Lock 的既有派生 Versioning 修正保留原时间语义;CORS 继续独立时间/事件,其他字段不参与该上界。 - -完成条件:六类源时间落盘;旧事件不能越过锁覆盖新状态;四类删除不会被旧 PUT 复活;真实 wire、落盘和出站状态一致;重复无写入;bulk 与 import 没有绕过排序或静默遗漏删除。 - -### 提交 2:heal 选源与应用同规则 - -主要文件:`cmd/site-replication.go`。 - -1. 先过滤 baseline、无效来源和 update-only 空配置,再选最大状态;无候选必须显式返回 found=false。消除六处“先 seed map 首项,再过滤默认值”的写法。 -2. 选源和目标遍历都跳过 `info.Sites` 中不存在的 deployment ID,包括不可达站点的空 ID 占位项;单一 peer 失败记录后继续其它目标,不因 map 顺序放弃健康站点。不改变状态计数或新建重试机制。 -3. 本地 heal 使用提交 1 的源时间 update/delete;远端仍用原有逐类型 RPC,全部携带源时间,补齐 Tag 的 UpdatedAt。 -4. 比较完整有效状态,去掉公开 mismatch/payload-only 对写入的门控;同内容较旧时间也同步。对已归一且相同的目标不写入、不发 RPC。Versioning 比较使用与该站点 Object Lock 一致的有效文档;全站点 Lock 状态补齐后不再因旧原始文档产生空转。 -5. 保留 #91 的计数与公开字段;创建世代冲突、无可用来源通过有限诊断解释。已知 baseline 且各站点无实质差异时安静 no-op。 - -完成条件:map 顺序不影响结果;默认值不再删真配置;同内容不同时间、同时间冲突最终一致;Tag 时间完整;Quota 删除后磁盘、缓存、重载一致;不可达占位项不阻断健康目标;第二轮稳定 heal 无写入/广播。 - -### 提交 3:新增删除传播与有界诊断 - -只增加一个启动开关,暂定 `MINIO_SITE_REPLICATION_METADATA_TOMBSTONES=off/on`,默认 off;实现沿用现有 env 开关写法,不做能力协商。 - -| 行为 | off:升级阶段 | on:所有参与节点修复后 | -| --- | --- | --- | -| 带时间 peer apply、锁内排序与 heal | 使用提交 1/2 | 同左 | -| 专用事件零时间 | 兼容应用并记录 legacy-zero | 同左,不新增协议拒绝 | -| Tags/SSE/Quota nil payload 时间导出 | 保留旧版条件导出 | 导出 `time > Created` 的真实删除时间 | -| Policy 时间导出 | 保留已有行为 | 保留已有行为 | -| 初次同步的真实删除 | 保留已有行为 | Policy/Tags/SSE/Quota 都发送专用 nil + source time 事件 | - -开关只控制**新增**删除信息的导出/初次发送,普通本地删除事件照常复制。它不检测或证明远端能力。启用条件是所有参与站点的全部节点已经修复,同一站点配置一致,旧请求排空;旧节点仍在线时保持 off。此隔离有实证依据:旧版接到新增 Quota heal 墓碑会留下已解析缓存残留。 - -日志仅保留三个实际原因:legacy-zero、before-created、indeterminate(未知创建时间、缺失/不可达来源或有实际差异却无可用候选)。精确重复、正常旧事件和成功裁决的同时间冲突不记警告。复用 `LogOnceIf`,以稳定的桶/字段/原因作为 key,**错误正文也必须稳定**;变化的时间与 peer 详情放入日志 ReqInfo,沿用现有每小时清理,不新增限流框架、不输出完整策略。 - -Server 文档解释启用顺序和回滚:降级前所有修复节点先关开关,然后滚动降级;旧软件缺陷会恢复。点名旧版 Tag heal 无 UpdatedAt 的来源。旧版到达时间污染、legacy-zero 产生的新本地时间以及创建世代分歧无法自动还原;操作者查看状态后在权威站点重新提交需要纠正的配置/删除。历史世代冲突先处理桶身份,不能靠任意站点强刷绕过创建保护。 - -## 最小验收矩阵 - -| 组 | 必须覆盖 | 证据方式 | -| --- | --- | --- | -| T1 | 六类 PUT 源时间;四类 DELETE;旧事件不回退;重复无写入;零 quota、空 Policy 的落盘/出站三元组一致 | 真实 admin/S3 路由、ErasureSD/Erasure16、磁盘重载、RPC 捕获 | -| T2 | 同时间两种到达顺序结果相同、删除优先;Policy 多 Action/Resource/Principal/Condition 集合反复编码与排列后键稳定 | 确定性比较器测试与代表性真实 handler | -| T3 | Versioning/Object Lock nil/空 no-op;legacy Tags 载荷回退;Object Lock + prefix exclusion/ExcludeFolders 在普通写、peer、bulk/import 后有效状态一致,第二轮 heal 无额外写入 | 原有 #76/#78 回归加针对性用例 | -| T4 | 锁前旧事件排队、较新写先提交后旧事件不得覆盖;不同字段并发均保留 | 复用已有 `lockBucketMetadataAcquireHook` / RMW 屏障、两种 ObjectLayer、目标 race | -| T5 | bulk 新/旧/缺省字段混合;真实 JSON 编解码的 nil、空 RawMessage、显式 null、空字符串、空策略、零 quota;非法字段不部分保存;只导入 tags 不修改 Policy/Quota | 真实 bulk 路由、缓存与磁盘 | -| T6 | 本地时间胜过已有未来时间;相邻提交不倒序;ZIP 导入期间插入写入,最终落盘和发出事件的状态与时间一致,空 Policy 删除不漏发 | 本地 API、import 路由与 RPC 捕获 | -| T7 | 多站点排序排列、baseline/全无候选、空 update-only;同内容新时间;Tag 源时间;Quota 删除清缓存;空 deployment ID 不阻断其它目标;世代冲突跳过且不反复发 RPC | 确定性 heal 本地/远端用例 | -| T8 | 墓碑经保存/缓存失效/重启仍有效;缺桶、排队写入、零 Created 历史桶;开关 on/off、legacy-zero 与初次同步删除 | ObjectLayer 与进程内旧版 wire/SRInfo 模拟;复用既有删除/迁移回归 | -| T9 | 修复版双站点短暂断线、漏发/重复/乱序后六类合法配置收敛;删除传播启用后第二轮稳定无写入;日志确实有界 | 隔离双站点实验,不把未知桶世代或零时间事件算成通过 | - -固定旧版与修复版混合进程仅作一次性升级冒烟,不新增为长期提交门槛。将已有审计用例改成正式确定性回归,不能把遍历 map 的概率复现直接提交。先记录未修复失败,再验证通过;复用原有 Object Lock、adoption、metadata-lock、计数、CORS 回归,并在实现后运行目标 race、仓库必需检查和完整 Go CI。方案审查、局部测试、双站点结果、合并与发布是不同证据。 - -## 作者复核结论 - -三个提交分别处理写入正确性、heal 收敛和新增删除信息的升级边界;每项对应已证实缺陷或本次修复直接触及的实际路径。缩减了拒绝零时间的新协议行为、混合版本长期测试门槛和新锁屏障;保留已有持久化、通知与缓存语义。 - -首轮三个 P1 意见均已有明确处置:R1 修发送/保存一致性;R2 使用有效状态和提交快照;R3 给出经过实际编解码验证的逐字段规则,而非照搬其不准确的 RawMessage 推断。作者另补充了 Policy 非确定性编码与日志去重问题。外部复审未返回前不标记为可进入实现;后续仍须用实现期测试证明,而非把本计划当作修复完成。 diff --git a/docs/investigations/issue-77/review/plan-v3.md b/docs/investigations/issue-77/review/plan-v3.md deleted file mode 100644 index a0d685777..000000000 --- a/docs/investigations/issue-77/review/plan-v3.md +++ /dev/null @@ -1,120 +0,0 @@ -# Issue #77 最小充分修复计划 - -版本:v3,2026-09-12。状态:首轮三个阻断已被 Opus 复审确认关闭;已修订第二轮发现的历史桶初始化边界,待最后的针对性复审。 - -基准:`pgsty/silo` main `5c576581631561c446f30ae5b566f0aa793adc1c`。本轮交付是方案与评审,不表示已经实现、合并或发布。问题证据见 [当前核验](../../issue-77-current.md),意见处置见 [首轮处置](decisions-v2.md)、[第二轮处置](decisions-v3.md)。 - -## 目标与边界 - -修复 Policy、Tags、SSE、Quota、Versioning、Object Lock 六类桶配置的源时间丢失、锁外判旧、heal 错选源和删除传播不完整。覆盖普通本地写入、专用 peer 事件、bulk/import、local/remote heal、元数据导出与初次同步。 - -在修复 PGSTY 栈、同一已知桶世代、合法带源时间事件的范围内,使重复、乱序和漏发后的状态能够确定性收敛。开关关闭期间新增删除信息不可见,不承诺完整删除收敛;无时间旧事件、旧版污染时间和桶创建世代冲突需要单独解释,不能自动推断历史真相。 - -不重做 #91 的计数、#76 的 Object Lock wire 修复、#78 的桶接管、#103/#156 的锁与删除保护。不改变 CORS、Lifecycle/expiry、notification、对象复制、MRF、resync、IAM 的语义;不改存储 schema、SDK、Console、mcli 或 silo-pkg,不新建能力协商、复制框架、锁或重试系统。 - -## 行为契约 - -### 1. 明确事件、缺省值与删除 - -| 类型 | 专用事件 | bulk 字段未提供 | bulk 字段明确提供 | -| --- | --- | --- | --- | -| Policy | nil 为删除;沿用现有解析器 `IsEmpty()` 为删除 | 保留 | 非空 RawMessage 按现有解析器处理;语义空策略归一为删除 | -| Quota | nil 为删除;非 nil 按现有 quota 解析器处理 | 保留 | 非空 RawMessage 按现有解析器处理,零 quota 仍是 live 文档 | -| Tags / SSE | nil 或 base64 解码后空内容为删除 | 保留 | 空字符串为删除,其他内容按原规则解码、校验 | -| Versioning / Object Lock | nil/空内容为 no-op | 保留 | nil/空内容仍为 no-op,不能清空配置 | - -“未提供”必须依据真实 wire 类型判定:Policy/Quota 的 `json.RawMessage` 为 nil 或空切片时经 `omitempty` 省略;**显式 JSON `null` 解码后是非 nil 的 `[]byte("null")`**,不能与缺省混淆。按现有解析器,Policy `null` 是语义空策略,Quota `null` 是零值 quota 文档。`*string` 类型的 JSON `null` 则解码为 nil。不得使用统一的 `len(payload)==0 => 删除` 来处理 bulk。 - -合法 `{}`/零值 quota 保持 live;取消普通 quota PUT 出站时“零配额改写为 nil”的逻辑,保存与发送同一个语义状态。Policy 保留现有专用 peer 的“空策略=删除”解释,本地 PUT、导入和 bulk 统一归一为同样的删除状态;这是需要写入兼容说明的小范围变化:空策略本地立即按删除处理,GET 返回既有 NotFound 行为,不再先保留空文档、等复制后才被清除。 - -**同一次操作的落盘状态与出站事件,经同一归一规则后,必须具有相同的 `(kind, payload key, source time)`。** JSON 的无意义编码次序不要求字节相同;Object Lock 改写后的有效 Versioning 文档必须来自提交结果。 - -### 2. 统一状态与排序 - -内部仅需一个小状态表示:baseline / live / tombstone,以及比较键和字段源时间。baseline 可携带历史有效配置,但没有真实修改时间;它绝不能被当成删除。复用 CORS 已有设计思路,不改 CORS 本身或扩展为通用框架。 - -- 使用 `BucketMetadata` 原始字段时间;字段时间为零时在比较视图中补为 Created,与 `defaultTimestamps()` 一致,不借用会隐藏墓碑时间的 getter 或整记录 `lastUpdate()`。 -- 在已知 Created 下,零字段时间回退后等于 Created 的状态是 baseline:有有效非空配置的 baseline-live 可作为初始化候选;空 baseline 只是缺省值,不能作为删除或 heal 来源。真正严格早于自身 Created 的字段不是候选;Versioning/Object Lock 的空候选无论时间如何都不参与选源。真实 live/tombstone 的时间必须大于 Created。 -- 专用 peer 的零时间保留兼容例外:锁内按本地新操作分配时间并限频记录 legacy-zero;不受删除传播开关影响,不在源时间排序保证之内。bulk 零时间仍按现状拒绝。 -- 非零事件**严格早于**目标桶 Created 才成功/no-op 并记录 before-created,heal 对这种目标跳过。等于目标 Created 的 live 事件可更新仍是 baseline 的目标字段,时间仍保存为源 Created,不盖上到达时间;同时间的空/nil 只算空 baseline,不能删除配置。不同桶世代不能自动合并,需要运维处理,不纳入收敛承诺。 -- 排序先比较是否为真实状态:任何真实 live/tombstone 都胜 baseline,不能让较晚创建的默认值压过较早的真实修改。真实状态之间再比较源时间,同时间 tombstone 胜 live、live/live 的**稳定比较键字节序较大者胜**。baseline-live 只胜空 baseline,或在 baseline-live 之间按同一稳定键较大者胜;永远不能覆盖真实 live/tombstone。所有候选都是 baseline-live 时仍可确定性初始化并收敛;全部为空 baseline 才安静 found=false。键、状态级别和源时间相同为 no-op。同状态来源不参与持久化冲突裁决。 -- Quota 使用现有解析结果的 JSON 编码作为比较键。Policy 在 Server 内生成确定性比较表示:既有解析器校验/去重后,对其**完整 JSON 树**的对象键和集合数组递归排序,统一覆盖 Statement、Action/NotAction、Resource/NotResource、Principal、Condition;保留数字类型和精度。比较键必须是已解析策略的纯函数,同一配置从两个站点分别解析也必须得到相同键。普通 `json.Marshal(BucketPolicy)` 不稳定,不能直接作键;不增加依赖不支持的 NotPrincipal 语法,不改 wire/schema。新状态比较仅使用这一套键:不把忽略 Sid 且对 Statement 顺序敏感的 `BucketPolicy.Equals` 再叠加为另一套判等规则,既有公开统计对 Equals 的使用保持不变。 -- XML 使用有效文档字节,保留大小写和实际内容;Versioning 先应用下述现有 Object Lock 约束。比较器不能依靠字节序方向来补偿保存阶段的隐式改写。 - -### 3. 先得到有效状态,再比较与提交 - -原始读取、类型处理、比较、修改及保存均在现有 `metadata.lock` 内。内部入口必须传递现有 lock context,避免 legacy migration 再次取锁。 - -- 提取并复用 `parseAllConfigs` 已有的 Object Lock→Enabled Versioning 归一规则,使比较视图和真正保存一致;不得扩大 suspend、prefix exclusion、retention 的限制。bulk 先确定实际接受的 Object Lock,再比较该约束下的 Versioning,并在最终提交前应用同一规则。 -- 以归一化后的有效 `(kind, key, time)` 判定变化;更新了时间也算变化。完全重复不保存、不通知;一次 bulk 校验失败不保存部分结果;成功至多保存一次,解锁后通知。 -- 可删除字段清空必须基于现有 parse=false 的新加载对象,不能在已经解析且仍持有旧 quota 的对象上执行 Update(nil)。bulk 保留原始读取再解析保存的方式。不要顺手修改 Quota getter 或所有 `parseAllConfigs` 空分支。 -- 保存函数必须让需要发送 hook 的调用方拿到**本次提交的最终快照**,不能先解锁再读取“最新”状态拼接旧时间。最小做法是让内部 `saveMetadata` 接收元数据指针并回写 Save 的归一化结果,机械更新现有少量调用;对外 `Update/Delete` 签名不变,新增内部提交结果仅供需要该快照的本地 handler/import 使用。不得原地修改已发布到缓存的引用字段。 -- 保留现有物理桶存在检查、删除锁序、迁移、后台通知上下文。真实历史桶 Created 为零时仅走少见的物理桶 Created 补齐路径;物理桶缺失返回现有错误;创建时间仍未知则不伪造到达时间,报告 indeterminate。 - -## 三个实现提交 - -### 提交 1:原子 apply、发送一致性与本地时间 - -主要文件:`cmd/bucket-metadata-sys.go`、`cmd/bucket-metadata.go`、`cmd/site-replication.go`、`cmd/admin-bucket-handlers.go`,以及实际需要提交快照的本地配置 handler。 - -1. 在现有 update/delete 内部路径增加源时间、状态比较和提交结果;公开签名不变。六个 peer handler 移除锁外 getter 判旧,锁内持久化原始源时间。保留 Object Lock 的 legacy Tags 字段载荷回退。 -2. bulk 对明确提供的六类字段在已有锁内逐字段比较,再原子保存;未提供字段不动,不能循环调用会重入锁的公开 handler。保留 CORS 独立分支与既有行为。 -3. 六类本地实际写入在锁内分配 `max(UTCNow(), Created+1ns, 当前字段时间+1ns)`。其他类型不变。`enablePeerBucketVersioning` 的实际变更也使用它,只有缺失配置的创建 bootstrap 继续 Created 默认值。 -4. quota 本地 PUT 保留零值文档并原样表示该语义;Policy 空策略本地与 peer 一致走删除。需要归一化的本地 handler 从本次提交快照生成 hook;其他内容不发生归一变化的路径可保留既有编码,但必须满足三元组一致性。 -5. 导入在每桶最终提交锁内,为本次涉及的六类字段生成共同 commitAt,严格大于 Created 和这些字段当前时间且不早于锁内现在。该时间同时用于落盘和 bulk hook,不能沿用 ZIP 开始时间。bulk hook 从最终提交快照构建;若导入的空 Policy 已归一成删除,另外使用现有专用 Policy nil 事件表达它,不能因 `omitempty` 漏发。未导入字段不改,Object Lock 的既有派生 Versioning 修正保留原时间语义;CORS 继续独立时间/事件,其他字段不参与该上界。 - -完成条件:六类源时间落盘;旧事件不能越过锁覆盖新状态;四类删除不会被旧 PUT 复活;真实 wire、落盘和出站状态一致;重复无写入;bulk 与 import 没有绕过排序或静默遗漏删除。 - -### 提交 2:heal 选源与应用同规则 - -主要文件:`cmd/site-replication.go`。 - -1. 先过滤空 baseline、无效来源、严格早于自身 Created 的字段和 update-only 空配置,再按上述强弱排序选最大状态;无候选必须显式返回 found=false。保留历史 baseline-live 的初次同步和 heal 能力,消除六处“先 seed map 首项,再过滤默认值”的写法。 -2. 选源和目标遍历都跳过 `info.Sites` 中不存在的 deployment ID,包括不可达站点的空 ID 占位项;单一 peer 失败记录后继续其它目标,不因 map 顺序放弃健康站点。不改变状态计数或新建重试机制。 -3. 本地 heal 使用提交 1 的源时间 update/delete;远端仍用原有逐类型 RPC,全部携带源时间,补齐 Tag 的 UpdatedAt。 -4. 比较完整有效状态,去掉公开 mismatch/payload-only 对写入的门控;同内容较旧时间也同步。对已归一且相同的目标不写入、不发 RPC。Versioning 比较使用与该站点 Object Lock 一致的有效文档;全站点 Lock 状态补齐后不再因旧原始文档产生空转。 -5. 保留 #91 的计数与公开字段;创建世代冲突、无可用来源通过有限诊断解释。已知 baseline 且各站点无实质差异时安静 no-op。 - -完成条件:map 顺序不影响结果;默认空值不再删真配置;历史 baseline-live 可以初始化新站点、不能覆盖真实状态;同内容不同时间、同时间冲突最终一致;Tag 时间完整;Quota 删除后磁盘、缓存、重载一致;不可达占位项不阻断健康目标;完整状态已可见且稳定时,第二轮 heal 无写入/广播。 - -### 提交 3:新增删除传播与有界诊断 - -只增加一个启动开关,暂定 `MINIO_SITE_REPLICATION_METADATA_TOMBSTONES=off/on`,默认 off;实现沿用现有 env 开关写法,不做能力协商。 - -| 行为 | off:升级阶段 | on:所有参与节点修复后 | -| --- | --- | --- | -| 带时间 peer apply、锁内排序与 heal | 使用提交 1/2 | 同左 | -| 专用事件零时间 | 兼容应用并记录 legacy-zero | 同左,不新增协议拒绝 | -| Tags/SSE/Quota nil payload 时间导出 | 保留旧版条件导出 | 导出 `time > Created` 的真实删除时间 | -| Policy 时间导出 | 保留已有行为 | 保留已有行为 | -| 初次同步的真实删除 | 保留已有行为 | Policy/Tags/SSE/Quota 都发送专用 nil + source time 事件 | - -开关只控制**新增**删除信息的导出/初次发送,普通本地删除事件照常复制。**off 不等于禁止删除传播:Policy 墓碑原本已导出,修复后在 off 下也照常参与 heal;Tags/SSE/Quota 的新增墓碑信息才被门控。** off 期间这些字段的隐藏墓碑会使 heal 继续尝试过时 RPC,由接收端排序拒绝;这是状态不可见时的已知代价,不承诺第二轮零 RPC,也不为这种正常拒绝增加每轮日志。 - -开关不检测或证明远端能力。启用条件是所有参与站点的全部节点已经修复,同一站点配置一致,旧请求排空;旧节点仍在线时保持 off。此隔离有实证依据:旧版接到新增 Quota heal 墓碑会留下已解析缓存残留。 - -日志仅保留三个实际原因:legacy-zero、before-created、indeterminate(未知创建时间、缺失/不可达来源或有实际差异却无可用候选)。精确重复、正常旧事件和成功裁决的同时间冲突不记警告。复用 `LogOnceIf`,以稳定的桶/字段/原因作为 key,**错误正文也必须稳定**;变化的时间与 peer 详情放入日志 ReqInfo,沿用现有每小时清理,不新增限流框架、不输出完整策略。 - -Server 文档解释启用顺序和回滚:降级前所有修复节点先关开关,然后滚动降级;旧软件缺陷会恢复。点名旧版 Tag heal 无 UpdatedAt 的来源。旧版到达时间污染、legacy-zero 产生的新本地时间以及创建世代分歧无法自动还原;操作者查看状态后在权威站点重新提交需要纠正的配置/删除。历史世代冲突先处理桶身份,不能靠任意站点强刷绕过创建保护。 - -## 最小验收矩阵 - -| 组 | 必须覆盖 | 证据方式 | -| --- | --- | --- | -| T1 | 六类 PUT 源时间;四类 DELETE;旧事件不回退;重复无写入;零 quota 三元组一致;带合法 Version 的空 Policy PUT 成功、GET NotFound、专用删除事件与落盘一致 | 真实 admin/S3 路由、ErasureSD/Erasure16、磁盘重载、RPC 捕获 | -| T2 | 同时间两种到达顺序结果相同、删除优先;Policy 多集合及 NotAction/NotResource/Condition 反复编码与排列后,两站点独立解析得到相同键;Sid 差异及大整数不被错误合并 | 确定性比较器测试与代表性真实 handler | -| T3 | Versioning/Object Lock nil/空 no-op;legacy Tags 载荷回退;Object Lock + prefix exclusion/ExcludeFolders 在普通写、peer、bulk/import 后有效状态一致,第二轮 heal 无额外写入 | 原有 #76/#78 回归加针对性用例 | -| T4 | 锁前旧事件排队、较新写先提交后旧事件不得覆盖;不同字段并发均保留 | 复用已有 `lockBucketMetadataAcquireHook` / RMW 屏障、两种 ObjectLayer、目标 race | -| T5 | bulk 新/旧/缺省字段混合;真实 JSON 编解码的 nil、空 RawMessage、显式 null、空字符串、空策略、零 quota;非法字段不部分保存;只导入 tags 不修改 Policy/Quota | 真实 bulk 路由、缓存与磁盘 | -| T6 | 本地时间胜过已有未来时间;相邻提交不倒序;ZIP 导入期间插入写入,最终落盘和发出事件的状态与时间一致,空 Policy 删除不漏发 | 本地 API、import 路由与 RPC 捕获 | -| T7 | map 排列、空 baseline/全无候选;baseline-live 初始化与同级冲突收敛,但不能覆盖真实 live/tombstone;nil@Created 不删除配置;空 update-only;Tag 时间、Quota 清缓存、空 ID、世代冲突 | 确定性 heal 本地/远端用例 | -| T8 | 墓碑经保存/缓存失效/重启仍有效;缺桶、排队写入、零 Created;历史字段时间全部等于 Created 的桶经初次同步和一轮 heal 后一致;on/off 与 legacy-zero,off 下 Policy 仍 heal、其它隐藏墓碑允许被拒 RPC 但无每轮警告 | ObjectLayer 与进程内旧版 wire/SRInfo 模拟;复用既有删除/迁移回归 | -| T9 | 修复版双站点短暂断线、漏发/重复/乱序后六类合法配置收敛;删除传播启用后第二轮稳定无写入;日志确实有界 | 隔离双站点实验,不把未知桶世代或零时间事件算成通过 | - -固定旧版与修复版混合进程仅作一次性升级冒烟,不新增为长期提交门槛。将已有审计用例改成正式确定性回归,不能把遍历 map 的概率复现直接提交。先记录未修复失败,再验证通过;复用原有 Object Lock、adoption、metadata-lock、计数、CORS 回归,并在实现后运行目标 race、仓库必需检查和完整 Go CI。方案审查、局部测试、双站点结果、合并与发布是不同证据。 - -## 作者复核结论 - -三个提交分别处理写入正确性、heal 收敛和新增删除信息的升级边界;每项对应已证实缺陷或本次修复直接触及的实际路径。缩减了拒绝零时间的新协议行为、混合版本长期测试门槛和新锁屏障;保留已有持久化、通知与缓存语义。 - -Opus 第二轮已确认首轮 R1/R2/R3 实质关闭,并确认作者的 Policy 编码和日志去重补查。v3 修正第二轮新增的历史桶 baseline-live 初始化回归,补齐相关验收与 off 模式说明,保留三个实现提交。最后针对性复审返回前不标记为可进入实现;后续仍须用实现期测试证明,而非把本计划当作修复完成。 diff --git a/docs/investigations/issue-77/review/plan-v4.md b/docs/investigations/issue-77/review/plan-v4.md deleted file mode 100644 index af3af5e80..000000000 --- a/docs/investigations/issue-77/review/plan-v4.md +++ /dev/null @@ -1,121 +0,0 @@ -# Issue #77 最小充分修复计划 - -版本:v4,2026-09-12。状态:Opus 已对 v3 给出 GO_WITH_NONBLOCKING_NOTES、0 阻断;作者追加实测发现接管时默认时间需随 Created 保持 baseline,补入提交 1 后待该增量复核。 - -基准:`pgsty/silo` main `5c576581631561c446f30ae5b566f0aa793adc1c`。本轮交付是方案与评审,不表示已经实现、合并或发布。问题证据见 [当前核验](../../issue-77-current.md),意见处置见 [首轮处置](decisions-v2.md)、[第二轮处置](decisions-v3.md)、[接管边界补查](decisions-v4.md)。 - -## 目标与边界 - -修复 Policy、Tags、SSE、Quota、Versioning、Object Lock 六类桶配置的源时间丢失、锁外判旧、heal 错选源和删除传播不完整。覆盖普通本地写入、专用 peer 事件、bulk/import、local/remote heal、元数据导出与初次同步。 - -在修复 PGSTY 栈、同一已知桶世代、合法带源时间事件的范围内,使重复、乱序和漏发后的状态能够确定性收敛。开关关闭期间新增删除信息不可见,不承诺完整删除收敛;无时间旧事件、旧版污染时间和桶创建世代冲突需要单独解释,不能自动推断历史真相。 - -不重做 #91 的计数、#76 的 Object Lock wire 修复、#78 的桶接管、#103/#156 的锁与删除保护。不改变 CORS、Lifecycle/expiry、notification、对象复制、MRF、resync、IAM 的语义;不改存储 schema、SDK、Console、mcli 或 silo-pkg,不新建能力协商、复制框架、锁或重试系统。 - -## 行为契约 - -### 1. 明确事件、缺省值与删除 - -| 类型 | 专用事件 | bulk 字段未提供 | bulk 字段明确提供 | -| --- | --- | --- | --- | -| Policy | nil 为删除;沿用现有解析器 `IsEmpty()` 为删除 | 保留 | 非空 RawMessage 按现有解析器处理;语义空策略归一为删除 | -| Quota | nil 为删除;非 nil 按现有 quota 解析器处理 | 保留 | 非空 RawMessage 按现有解析器处理,零 quota 仍是 live 文档 | -| Tags / SSE | nil 或 base64 解码后空内容为删除 | 保留 | 空字符串为删除,其他内容按原规则解码、校验 | -| Versioning / Object Lock | nil/空内容为 no-op | 保留 | nil/空内容仍为 no-op,不能清空配置 | - -“未提供”必须依据真实 wire 类型判定:Policy/Quota 的 `json.RawMessage` 为 nil 或空切片时经 `omitempty` 省略;**显式 JSON `null` 解码后是非 nil 的 `[]byte("null")`**,不能与缺省混淆。按现有解析器,Policy `null` 是语义空策略,Quota `null` 是零值 quota 文档。`*string` 类型的 JSON `null` 则解码为 nil。不得使用统一的 `len(payload)==0 => 删除` 来处理 bulk。 - -合法 `{}`/零值 quota 保持 live;取消普通 quota PUT 出站时“零配额改写为 nil”的逻辑,保存与发送同一个语义状态。Policy 保留现有专用 peer 的“空策略=删除”解释,本地 PUT、导入和 bulk 统一归一为同样的删除状态;这是需要写入兼容说明的小范围变化:空策略本地立即按删除处理,GET 返回既有 NotFound 行为,不再先保留空文档、等复制后才被清除。 - -**同一次操作的落盘状态与出站事件,经同一归一规则后,必须具有相同的 `(kind, payload key, source time)`。** JSON 的无意义编码次序不要求字节相同;Object Lock 改写后的有效 Versioning 文档必须来自提交结果。 - -### 2. 统一状态与排序 - -内部仅需一个小状态表示:baseline / live / tombstone,以及比较键和字段源时间。baseline 可携带历史有效配置,但没有真实修改时间;它绝不能被当成删除。复用 CORS 已有设计思路,不改 CORS 本身或扩展为通用框架。 - -- 使用 `BucketMetadata` 原始字段时间;字段时间为零时在比较视图中补为 Created,与 `defaultTimestamps()` 一致,不借用会隐藏墓碑时间的 getter 或整记录 `lastUpdate()`。 -- 在已知 Created 下,零字段时间回退后等于 Created 的状态是 baseline:有有效非空配置的 baseline-live 可作为初始化候选;空 baseline 只是缺省值,不能作为删除或 heal 来源。真正严格早于自身 Created 的字段不是候选;Versioning/Object Lock 的空候选无论时间如何都不参与选源。真实 live/tombstone 的时间必须大于 Created。 -- 专用 peer 的零时间保留兼容例外:锁内按本地新操作分配时间并限频记录 legacy-zero;不受删除传播开关影响,不在源时间排序保证之内。bulk 零时间仍按现状拒绝。 -- 非零事件**严格早于**目标桶 Created 才成功/no-op 并记录 before-created,heal 对这种目标跳过。等于目标 Created 的 live 事件可更新仍是 baseline 的目标字段,时间仍保存为源 Created,不盖上到达时间;同时间的空/nil 只算空 baseline,不能删除配置。不同桶世代不能自动合并,需要运维处理,不纳入收敛承诺。 -- 排序先比较是否为真实状态:任何真实 live/tombstone 都胜 baseline,不能让较晚创建的默认值压过较早的真实修改。真实状态之间再比较源时间,同时间 tombstone 胜 live、live/live 的**稳定比较键字节序较大者胜**。baseline-live 只胜空 baseline,或在 baseline-live 之间按同一稳定键较大者胜;永远不能覆盖真实 live/tombstone。所有候选都是 baseline-live 时仍可确定性初始化并收敛;全部为空 baseline 才安静 found=false。键、状态级别和源时间相同为 no-op。deployment ID 不参与上述比较,也不作为新字段保存。 -- Quota 使用现有解析结果的 JSON 编码作为比较键。Policy 在 Server 内生成确定性比较表示:既有解析器校验/去重后,对其**完整 JSON 树**的对象键和集合数组递归排序,统一覆盖 Statement、Action/NotAction、Resource/NotResource、Principal、Condition;保留数字类型和精度。比较键必须是已解析策略的纯函数,同一配置从两个站点分别解析也必须得到相同键。普通 `json.Marshal(BucketPolicy)` 不稳定,不能直接作键;不增加依赖不支持的 NotPrincipal 语法,不改 wire/schema。新状态比较仅使用这一套键:不把忽略 Sid 且对 Statement 顺序敏感的 `BucketPolicy.Equals` 再叠加为另一套判等规则,既有公开统计对 Equals 的使用保持不变。 -- XML 使用有效文档字节,保留大小写和实际内容;Versioning 先应用下述现有 Object Lock 约束。比较器不能依靠字节序方向来补偿保存阶段的隐式改写。 - -### 3. 先得到有效状态,再比较与提交 - -原始读取、类型处理、比较、修改及保存均在现有 `metadata.lock` 内。内部入口必须传递现有 lock context,避免 legacy migration 再次取锁。 - -- 提取并复用 `parseAllConfigs` 已有的 Object Lock→Enabled Versioning 归一规则,使比较视图和真正保存一致;不得扩大 suspend、prefix exclusion、retention 的限制。bulk 先确定实际接受的 Object Lock,再比较该约束下的 Versioning,并在最终提交前应用同一规则。 -- 以归一化后的有效 `(kind, key, time)` 判定变化;更新了时间也算变化。完全重复不保存、不通知;一次 bulk 校验失败不保存部分结果;成功至多保存一次,解锁后通知。 -- 可删除字段清空必须基于现有 parse=false 的新加载对象,不能在已经解析且仍持有旧 quota 的对象上执行 Update(nil)。bulk 保留原始读取再解析保存的方式。不要顺手修改 Quota getter 或所有 `parseAllConfigs` 空分支。 -- 保存函数必须让需要发送 hook 的调用方拿到**本次提交的最终快照**,不能先解锁再读取“最新”状态拼接旧时间。最小做法是让内部 `saveMetadata` 接收元数据指针并回写 Save 的归一化结果,机械更新现有少量调用;对外 `Update/Delete` 签名不变,新增内部提交结果仅供需要该快照的本地 handler/import 使用。不得原地修改已发布到缓存的引用字段。 -- 保留现有物理桶存在检查、删除锁序、迁移、后台通知上下文。真实历史桶 Created 为零时仅走少见的物理桶 Created 补齐路径;物理桶缺失返回现有错误;创建时间仍未知则不伪造到达时间,报告 indeterminate。 - -## 三个实现提交 - -### 提交 1:原子 apply、发送一致性与本地时间 - -主要文件:`cmd/bucket-metadata-sys.go`、`cmd/bucket-metadata.go`、`cmd/site-replication.go`、`cmd/admin-bucket-handlers.go`,以及实际需要提交快照的本地配置 handler。 - -1. 在现有 update/delete 内部路径增加源时间、状态比较和提交结果;公开签名不变。六个 peer handler 移除锁外 getter 判旧,锁内持久化原始源时间。保留 Object Lock 的 legacy Tags 字段载荷回退。 -2. bulk 对明确提供的六类字段在已有锁内逐字段比较,再原子保存;未提供字段不动,不能循环调用会重入锁的公开 handler。保留 CORS 独立分支与既有行为。 -3. 六类本地实际写入在锁内分配 `max(UTCNow(), Created+1ns, 当前字段时间+1ns)`。其他类型不变。`enablePeerBucketVersioning` 的实际变更也使用它,只有缺失配置的创建 bootstrap 继续 Created 默认值。 - 接管已有桶时保留原 Created,再执行现有 `SetCreatedAt`;如果 Created 改变,仅将这六类中原本为零或等于原 Created 的默认字段时间调整到新 Created,随后再做既有 versioning/lock bootstrap。原本晚于旧 Created 的真实修改/删除时间及其配置保持不变。不能仅凭 payload 为 nil 判断默认值。该小分支用于防止原默认时间因 Created 前移被误识别为墓碑,复用已有接管锁,不重做 #78 的配置保护。 -4. quota 本地 PUT 保留零值文档并原样表示该语义;Policy 空策略本地与 peer 一致走删除。需要归一化的本地 handler 从本次提交快照生成 hook;其他内容不发生归一变化的路径可保留既有编码,但必须满足三元组一致性。 -5. 导入在每桶最终提交锁内,为本次涉及的六类字段生成共同 commitAt,严格大于 Created 和这些字段当前时间且不早于锁内现在。该时间同时用于落盘和 bulk hook,不能沿用 ZIP 开始时间。bulk hook 从最终提交快照构建;若导入的空 Policy 已归一成删除,另外使用现有专用 Policy nil 事件表达它,不能因 `omitempty` 漏发。未导入字段不改,Object Lock 的既有派生 Versioning 修正保留原时间语义;CORS 继续独立时间/事件,其他字段不参与该上界。 - -完成条件:六类源时间落盘;旧事件不能越过锁覆盖新状态;四类删除不会被旧 PUT 复活;真实 wire、落盘和出站状态一致;重复无写入;bulk 与 import 没有绕过排序或静默遗漏删除。 - -### 提交 2:heal 选源与应用同规则 - -主要文件:`cmd/site-replication.go`。 - -1. 先过滤空 baseline、无效来源、严格早于自身 Created 的字段和 update-only 空配置,再按上述强弱排序选最大状态;无候选必须显式返回 found=false。保留历史 baseline-live 的初次同步和 heal 能力,消除六处“先 seed map 首项,再过滤默认值”的写法。 -2. 选源和目标遍历都跳过 `info.Sites` 中不存在的 deployment ID,包括不可达站点的空 ID 占位项;单一 peer 失败记录后继续其它目标,不因 map 顺序放弃健康站点。不改变状态计数或新建重试机制。 -3. 本地 heal 使用提交 1 的源时间 update/delete;远端仍用原有逐类型 RPC,全部携带源时间,补齐 Tag 的 UpdatedAt。 -4. 比较完整有效状态,去掉公开 mismatch/payload-only 对写入的门控;同内容较旧时间也同步。对已归一且相同的目标不写入、不发 RPC。Versioning 比较使用与该站点 Object Lock 一致的有效文档;全站点 Lock 状态补齐后不再因旧原始文档产生空转。 -5. 保留 #91 的计数与公开字段;创建世代冲突、无可用来源通过有限诊断解释。已知 baseline 且各站点无实质差异时安静 no-op。 - -完成条件:map 顺序不影响结果;默认空值不再删真配置;历史 baseline-live 可以初始化新站点、不能覆盖真实状态;同内容不同时间、同时间冲突最终一致;Tag 时间完整;Quota 删除后磁盘、缓存、重载一致;不可达占位项不阻断健康目标;完整状态已可见且稳定时,第二轮 heal 无写入/广播。 - -### 提交 3:新增删除传播与有界诊断 - -只增加一个启动开关,暂定 `MINIO_SITE_REPLICATION_METADATA_TOMBSTONES=off/on`,默认 off;实现沿用现有 env 开关写法,不做能力协商。 - -| 行为 | off:升级阶段 | on:所有参与节点修复后 | -| --- | --- | --- | -| 带时间 peer apply、锁内排序与 heal | 使用提交 1/2 | 同左 | -| 专用事件零时间 | 兼容应用并记录 legacy-zero | 同左,不新增协议拒绝 | -| Tags/SSE/Quota nil payload 时间导出 | 保留旧版条件导出 | 导出 `time > Created` 的真实删除时间 | -| Policy 时间导出 | 保留已有行为 | 保留已有行为 | -| 初次同步的真实删除 | 保留已有行为 | Policy/Tags/SSE/Quota 都发送专用 nil + source time 事件 | - -开关只控制**新增**删除信息的导出/初次发送,普通本地删除事件照常复制。**off 不等于禁止删除传播:Policy 墓碑原本已导出,修复后在 off 下也照常参与 heal;Tags/SSE/Quota 的新增墓碑信息才被门控。** off 期间这些字段的隐藏墓碑会使 heal 继续尝试过时 RPC,由接收端排序拒绝;这是状态不可见时的已知代价,不承诺第二轮零 RPC,也不为这种正常拒绝增加每轮日志。 - -开关不检测或证明远端能力。启用条件是所有参与站点的全部节点已经修复,同一站点配置一致,旧请求排空;旧节点仍在线时保持 off。此隔离有实证依据:旧版接到新增 Quota heal 墓碑会留下已解析缓存残留。 - -日志仅保留三个实际原因:legacy-zero、before-created、indeterminate(未知创建时间、缺失/不可达来源或有实际差异却无可用候选)。精确重复、正常旧事件和成功裁决的同时间冲突不记警告。复用 `LogOnceIf`,以稳定的桶/字段/原因作为 key,**错误正文也必须稳定**;变化的时间与 peer 详情放入日志 ReqInfo,沿用现有每小时清理,不新增限流框架、不输出完整策略。 - -Server 文档解释启用顺序和回滚:降级前所有修复节点先关开关,然后滚动降级;旧软件缺陷会恢复。点名旧版 Tag heal 无 UpdatedAt 的来源。旧版到达时间污染、legacy-zero 产生的新本地时间以及创建世代分歧无法自动还原;操作者查看状态后在权威站点重新提交需要纠正的配置/删除。历史世代冲突先处理桶身份,不能靠任意站点强刷绕过创建保护。 - -## 最小验收矩阵 - -| 组 | 必须覆盖 | 证据方式 | -| --- | --- | --- | -| T1 | 六类 PUT 源时间;四类 DELETE;旧事件不回退;重复无写入;零 quota 三元组一致;带合法 Version 的空 Policy PUT 成功、GET NotFound、专用删除事件与落盘一致 | 真实 admin/S3 路由、ErasureSD/Erasure16、磁盘重载、RPC 捕获 | -| T2 | 同时间两种到达顺序结果相同、删除优先;Policy 多集合及 NotAction/NotResource/Condition 反复编码与排列后,两站点独立解析得到相同键;Sid 差异及大整数不被错误合并 | 确定性比较器测试与代表性真实 handler | -| T3 | Versioning/Object Lock nil/空 no-op;legacy Tags 载荷回退;Object Lock + prefix exclusion/ExcludeFolders 在普通写、peer、bulk/import 后有效状态一致,第二轮 heal 无额外写入 | 原有 #76/#78 回归加针对性用例 | -| T4 | 锁前旧事件排队、较新写先提交后旧事件不得覆盖;不同字段并发均保留 | 复用已有 `lockBucketMetadataAcquireHook` / RMW 屏障、两种 ObjectLayer、目标 race | -| T5 | bulk 新/旧/缺省字段混合;真实 JSON 编解码的 nil、空 RawMessage、显式 null、空字符串、空策略、零 quota;非法字段不部分保存;只导入 tags 不修改 Policy/Quota | 真实 bulk 路由、缓存与磁盘 | -| T6 | 本地时间胜过已有未来时间;相邻提交不倒序;ZIP 导入期间插入写入,最终落盘和发出事件的状态与时间一致,空 Policy 删除不漏发 | 本地 API、import 路由与 RPC 捕获 | -| T7 | map 排列、空 baseline/全无候选;baseline-live 初始化与同级冲突收敛,但不能覆盖真实 live/tombstone;nil@Created 不删除配置;空 update-only;Tag 时间、Quota 清缓存、空 ID、世代冲突 | 确定性 heal 本地/远端用例 | -| T8 | 墓碑经保存/缓存失效/重启仍有效;缺桶、排队写入、零 Created;历史字段时间等于 Created 的桶经初次同步、一轮 heal 后一致,第二轮无写入/广播;接管前移 Created 后默认时间仍是 baseline,真实 PUT/DELETE 时间不变;on/off 与 legacy-zero,off 下 Policy 仍 heal、其它隐藏墓碑允许被拒 RPC 但无每轮警告 | ObjectLayer 与进程内旧版 wire/SRInfo 模拟;复用既有删除/迁移回归 | -| T9 | 修复版双站点短暂断线、漏发/重复/乱序后六类合法配置收敛;删除传播启用后第二轮稳定无写入;日志确实有界 | 隔离双站点实验,不把未知桶世代或零时间事件算成通过 | - -固定旧版与修复版混合进程仅作一次性升级冒烟,不新增为长期提交门槛。将已有审计用例改成正式确定性回归,不能把遍历 map 的概率复现直接提交。先记录未修复失败,再验证通过;复用原有 Object Lock、adoption、metadata-lock、计数、CORS 回归,并在实现后运行目标 race、仓库必需检查和完整 Go CI。方案审查、局部测试、双站点结果、合并与发布是不同证据。 - -## 作者复核结论 - -三个提交分别处理写入正确性、heal 收敛和新增删除信息的升级边界;每项对应已证实缺陷或本次修复直接触及的实际路径。缩减了拒绝零时间的新协议行为、混合版本长期测试门槛和新锁屏障;保留已有持久化、通知与缓存语义。 - -Opus 第二轮已确认首轮 R1/R2/R3 实质关闭,第三轮确认 v3 的历史桶初始化边界修正,给出 0 阻断。作者随后在两个真实 ObjectLayer 上补查到接管改变 Created 会把旧默认时间变成假墓碑,v4 仅补充默认时间随接管保持 baseline 的分支与回归。所有变更仍在三个实现提交内;该增量复审返回前不标记最终方案通过,后续仍须用实现期测试证明,而非把本计划当作修复完成。 diff --git a/docs/investigations/issue-77/review/policy-encoding-probe.go.txt b/docs/investigations/issue-77/review/policy-encoding-probe.go.txt deleted file mode 100644 index b9f43a6f0..000000000 --- a/docs/investigations/issue-77/review/policy-encoding-probe.go.txt +++ /dev/null @@ -1,14 +0,0 @@ -package main -import ( - "encoding/json" - "fmt" - "strings" - "github.com/pgsty/silo-pkg/v3/policy" -) -func main() { - p,err:=policy.ParseBucketPolicyConfig(strings.NewReader(`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":"*","Action":["s3:GetObject","s3:PutObject"],"Resource":["arn:aws:s3:::issue77-test/a/*","arn:aws:s3:::issue77-test/b/*"]}]}`),"issue77-test") - if err!=nil {panic(err)} - variants:=map[string]int{} - for i:=0;i<100;i++ {b,err:=json.Marshal(p);if err!=nil{panic(err)};variants[string(b)]++} - fmt.Printf("100 encodings of the same parsed BucketPolicy produced %d distinct byte sequences\n",len(variants)) -} diff --git a/docs/investigations/issue-77/review/wire-state-probe.go.txt b/docs/investigations/issue-77/review/wire-state-probe.go.txt deleted file mode 100644 index eb77b6b53..000000000 --- a/docs/investigations/issue-77/review/wire-state-probe.go.txt +++ /dev/null @@ -1,24 +0,0 @@ -package main -import ( - "bytes" - "encoding/json" - "fmt" - "github.com/minio/madmin-go/v3" - "github.com/pgsty/silo-pkg/v3/policy" -) -func main() { - for _, raw := range []string{`{"bucket":"b"}`, `{"bucket":"b","policy":null,"quota":null}`, `{"bucket":"b","policy":{"Version":"2012-10-17","Statement":[]},"quota":{}}`} { - var x madmin.SRBucketMeta - if err:=json.Unmarshal([]byte(raw),&x);err!=nil{panic(err)} - fmt.Printf("wire=%s\nPolicy nil=%t bytes=%q; Quota nil=%t bytes=%q\n",raw,x.Policy==nil,[]byte(x.Policy),x.Quota==nil,[]byte(x.Quota)) - if len(x.Policy)>0 && !bytes.Equal(x.Policy,[]byte("null")) { - p,err:=policy.ParseBucketPolicyConfig(bytes.NewReader(x.Policy),"b") - if err!=nil{panic(err)} - fmt.Printf("Policy parses successfully, IsEmpty=%t\n",p.IsEmpty()) - } - } - for _, v := range []json.RawMessage{nil,{},[]byte("null")} { - b,err:=json.Marshal(madmin.SRBucketMeta{Bucket:"b",Policy:v});if err!=nil{panic(err)} - fmt.Printf("encoded raw=%q -> %s\n",[]byte(v),b) - } -} diff --git a/docs/investigations/issue-77/review/wire-state-probe.log b/docs/investigations/issue-77/review/wire-state-probe.log deleted file mode 100644 index 592373551..000000000 --- a/docs/investigations/issue-77/review/wire-state-probe.log +++ /dev/null @@ -1,10 +0,0 @@ -wire={"bucket":"b"} -Policy nil=true bytes=""; Quota nil=true bytes="" -wire={"bucket":"b","policy":null,"quota":null} -Policy nil=false bytes="null"; Quota nil=false bytes="null" -wire={"bucket":"b","policy":{"Version":"2012-10-17","Statement":[]},"quota":{}} -Policy nil=false bytes="{\"Version\":\"2012-10-17\",\"Statement\":[]}"; Quota nil=false bytes="{}" -Policy parses successfully, IsEmpty=true -encoded raw="" -> {"type":"","bucket":"b","updatedAt":"0001-01-01T00:00:00Z","expiryUpdatedAt":"0001-01-01T00:00:00Z"} -encoded raw="" -> {"type":"","bucket":"b","updatedAt":"0001-01-01T00:00:00Z","expiryUpdatedAt":"0001-01-01T00:00:00Z"} -encoded raw="null" -> {"type":"","bucket":"b","policy":null,"updatedAt":"0001-01-01T00:00:00Z","expiryUpdatedAt":"0001-01-01T00:00:00Z"} diff --git a/docs/investigations/issue-77/runtime/converged.json b/docs/investigations/issue-77/runtime/converged.json deleted file mode 100644 index e7e392f5d..000000000 --- a/docs/investigations/issue-77/runtime/converged.json +++ /dev/null @@ -1 +0,0 @@ -[null,"2026-09-12T09:14:44.759058Z",null,"2026-09-12T09:14:44.759058Z",null,"2026-09-12T09:14:44.759058Z",null,"2026-09-12T09:14:44.759058Z","PFZlcnNpb25pbmdDb25maWd1cmF0aW9uIHhtbG5zPSJodHRwOi8vczMuYW1hem9uYXdzLmNvbS9kb2MvMjAwNi0wMy0wMS8iPjxTdGF0dXM+RW5hYmxlZDwvU3RhdHVzPjwvVmVyc2lvbmluZ0NvbmZpZ3VyYXRpb24+","2026-09-12T09:13:44.759058Z","PE9iamVjdExvY2tDb25maWd1cmF0aW9uPjxPYmplY3RMb2NrRW5hYmxlZD5FbmFibGVkPC9PYmplY3RMb2NrRW5hYmxlZD48UnVsZT48RGVmYXVsdFJldGVudGlvbj48TW9kZT5HT1ZFUk5BTkNFPC9Nb2RlPjxEYXlzPjMwPC9EYXlzPjwvRGVmYXVsdFJldGVudGlvbj48L1J1bGU+PC9PYmplY3RMb2NrQ29uZmlndXJhdGlvbj4=","2026-09-12T09:13:44.759058Z"] \ No newline at end of file diff --git a/docs/investigations/issue-77/runtime/main.go b/docs/investigations/issue-77/runtime/main.go deleted file mode 100644 index e0282400d..000000000 --- a/docs/investigations/issue-77/runtime/main.go +++ /dev/null @@ -1,333 +0,0 @@ -//go:build ignore - -// Standalone disposable loopback-lab driver; see ../../issue-77.md. -package main - -import ( - "bytes" - "context" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "net" - "net/http" - "net/http/httptest" - "net/http/httputil" - "net/url" - "os" - "os/exec" - "path/filepath" - "strings" - "sync/atomic" - "time" - - "github.com/minio/madmin-go/v3" - minio "github.com/minio/minio-go/v7" - "github.com/minio/minio-go/v7/pkg/credentials" - "github.com/minio/minio-go/v7/pkg/tags" -) - -const bucket = "issue77-runtime" -const user = "issue77-lab-admin" -const password = "issue77-disposable-lab-secret" - -var ctx = context.Background() -var root string - -type site struct { - name, address, binary, gate string - cmd *exec.Cmd - log *os.File - proxy *httptest.Server - block atomic.Bool - calls atomic.Int64 - dropped atomic.Int64 - admin *madmin.AdminClient - s3 *minio.Client -} - -func check(err error) { - if err != nil { - panic(err) - } -} -func stamp(s string) { fmt.Println(time.Now().UTC().Format(time.RFC3339), s) } -func port() string { - l, e := net.Listen("tcp", "127.0.0.1:0") - check(e) - s := l.Addr().String() - check(l.Close()) - return s -} -func newSite(name, binary string) *site { - s := &site{name: name, address: port(), binary: binary, gate: "on"} - target, e := url.Parse("http://" + s.address) - check(e) - reverse := httputil.NewSingleHostReverseProxy(target) - reverse.ErrorHandler = func(w http.ResponseWriter, r *http.Request, e error) { w.WriteHeader(503) } - s.proxy = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasSuffix(r.URL.Path, "/site-replication/peer/bucket-meta") { - data, e := io.ReadAll(r.Body) - check(e) - r.Body = io.NopCloser(bytes.NewReader(data)) - var event madmin.SRBucketMeta - check(json.Unmarshal(data, &event)) - if event.Bucket == bucket { - s.calls.Add(1) - if s.block.Load() { - s.dropped.Add(1) - w.WriteHeader(503) - return - } - } - } - reverse.ServeHTTP(w, r) - })) - s.admin, e = madmin.New(s.address, user, password, false) - check(e) - s.s3, e = minio.New(s.address, &minio.Options{Creds: credentials.NewStaticV4(user, password, ""), Secure: false}) - check(e) - return s -} -func (s *site) start() { - var e error - s.log, e = os.OpenFile(filepath.Join(root, s.name+".jsonl"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) - check(e) - args := []string{"--json", "server", "--quiet", "--address", s.address, "--console-address", port()} - for i := 0; i < 4; i++ { - dir := filepath.Join(root, s.name, fmt.Sprintf("disk%d", i)) - check(os.MkdirAll(dir, 0700)) - args = append(args, dir) - } - s.cmd = exec.Command(s.binary, args...) - s.cmd.Env = append(os.Environ(), "MINIO_ROOT_USER="+user, "MINIO_ROOT_PASSWORD="+password, "MINIO_BROWSER=off", "MINIO_CI_CD=1", "MINIO_SITE_REPLICATION_METADATA_TOMBSTONES="+s.gate) - s.cmd.Stdout = s.log - s.cmd.Stderr = s.log - check(s.cmd.Start()) - client := http.Client{Timeout: time.Second} - deadline := time.Now().Add(50 * time.Second) - for time.Now().Before(deadline) { - r, e := client.Get("http://" + s.address + "/minio/health/ready") - if e == nil { - r.Body.Close() - if r.StatusCode == 200 { - stamp(s.name + " ready gate=" + s.gate) - return - } - } - time.Sleep(time.Second) - } - panic(s.name + " startup timed out; inspect " + s.log.Name()) -} -func (s *site) stop() { - if s.cmd == nil { - return - } - _ = s.cmd.Process.Signal(os.Interrupt) - done := make(chan error, 1) - go func() { done <- s.cmd.Wait() }() - select { - case <-done: - case <-time.After(10 * time.Second): - _ = s.cmd.Process.Kill() - <-done - } - s.log.Close() - s.cmd = nil -} -func (s *site) info() madmin.SRBucketInfo { - i, e := s.admin.SRMetaInfo(ctx, madmin.SRStatusOptions{Buckets: true}) - check(e) - return i.Buckets[bucket] -} -func (s *site) apply(e madmin.SRBucketMeta) { - e.Bucket = bucket - check(s.admin.SRPeerReplicateBucketMeta(ctx, e)) -} -func enc(s string) *string { r := base64.StdEncoding.EncodeToString([]byte(s)); return &r } -func events(at time.Time, newer bool) []madmin.SRBucketMeta { - value, days := "old", 10 - if newer { - value, days = "new", 30 - } - return []madmin.SRBucketMeta{ - {Type: madmin.SRBucketMetaTypePolicy, UpdatedAt: at, Policy: []byte(fmt.Sprintf(`{"Version":"2012-10-17","Statement":[{"Sid":%q,"Effect":"Allow","Principal":"*","Action":"s3:GetObject","Resource":"arn:aws:s3:::%s/*"}]}`, value, bucket))}, - {Type: madmin.SRBucketMetaTypeTags, UpdatedAt: at, Tags: enc(`key` + value + ``)}, - {Type: madmin.SRBucketMetaTypeSSEConfig, UpdatedAt: at, SSEConfig: enc(`AES256`)}, - {Type: madmin.SRBucketMetaTypeQuotaConfig, UpdatedAt: at, Quota: []byte(`{"quota":1024,"quotatype":"hard"}`)}, - {Type: madmin.SRBucketMetaTypeVersionConfig, UpdatedAt: at, Versioning: enc(`Enabled`)}, - {Type: madmin.SRBucketMetaTypeObjectLockConfig, UpdatedAt: at, ObjectLockConfig: enc(fmt.Sprintf(`EnabledGOVERNANCE%d`, days))}, - } -} -func states(m madmin.SRBucketInfo) []byte { - b, e := json.Marshal([]any{m.Policy, m.PolicyUpdatedAt, m.Tags, m.TagConfigUpdatedAt, m.SSEConfig, m.SSEConfigUpdatedAt, m.QuotaConfig, m.QuotaConfigUpdatedAt, m.Versioning, m.VersioningConfigUpdatedAt, m.ObjectLockConfig, m.ObjectLockConfigUpdatedAt}) - check(e) - return b -} -func times(m madmin.SRBucketInfo) []time.Time { - return []time.Time{m.PolicyUpdatedAt, m.TagConfigUpdatedAt, m.SSEConfigUpdatedAt, m.QuotaConfigUpdatedAt, m.VersioningConfigUpdatedAt, m.ObjectLockConfigUpdatedAt} -} -func converge(a, b *site, label string) { - stamp(label + ": waiting for ordinary 30-second heal") - deadline := time.Now().Add(130 * time.Second) - for time.Now().Before(deadline) { - if bytes.Equal(states(a.info()), states(b.info())) { - stamp(label + ": six field states equal") - return - } - time.Sleep(2 * time.Second) - } - check(os.WriteFile(filepath.Join(root, label+"-a.json"), states(a.info()), 0600)) - check(os.WriteFile(filepath.Join(root, label+"-b.json"), states(b.info()), 0600)) - panic(label + " convergence timed out") -} -func quiet(a, b *site, label string) { - beforeA, beforeB := a.calls.Load(), b.calls.Load() - stamp(label + ": observing two heal intervals") - time.Sleep(65 * time.Second) - if a.calls.Load() != beforeA || b.calls.Load() != beforeB { - panic(fmt.Sprintf("%s not quiet: a %d -> %d, b %d -> %d", label, beforeA, a.calls.Load(), beforeB, b.calls.Load())) - } - stamp(label + ": zero metadata RPCs across 65 seconds") -} -func main() { - root = os.Args[1] - binary, e := filepath.Abs(os.Args[2]) - check(e) - check(os.MkdirAll(root, 0700)) - a, b := newSite("site-a", binary), newSite("site-b", binary) - defer a.proxy.Close() - defer b.proxy.Close() - defer a.stop() - defer b.stop() - a.start() - b.start() - created := time.Now().UTC().Add(-time.Hour) - check(a.admin.SRPeerBucketOps(ctx, bucket, madmin.MakeWithVersioningBktOp, map[string]string{"createdAt": created.Format(time.RFC3339Nano), "lockEnabled": "true", "versioningEnabled": "true"})) - for _, event := range events(created, false) { - a.apply(event) - } - stamp("historical bucket prepared: six live fields at Created") - status, e := a.admin.SiteReplicationAdd(ctx, []madmin.PeerSite{{Name: a.name, Endpoint: a.proxy.URL, AccessKey: user, SecretKey: password}, {Name: b.name, Endpoint: b.proxy.URL, AccessKey: user, SecretKey: password}}, madmin.SRAddOptions{}) - check(e) - if !status.Success || status.InitialSyncErrorMessage != "" { - panic(fmt.Sprintf("add sites: %+v", status)) - } - converge(a, b, "baseline-initial-sync") - for _, tm := range times(a.info()) { - if !tm.Equal(created) { - panic("initial sync reclocked historical baseline: " + tm.String()) - } - } - stamp("initial sync preserved six historical Created timestamps") - a.block.Store(true) - b.block.Store(true) - faultTags, e := tags.NewTags(map[string]string{"fault": "outgoing-message-dropped"}, true) - check(e) - check(a.s3.SetBucketTagging(ctx, bucket, faultTags)) - if b.dropped.Load() == 0 { - panic("outbound fault injection did not drop a real metadata RPC") - } - stamp("real local PUT outgoing replication RPC dropped by partition") - at := time.Now().UTC().Add(time.Minute) - newer, older := events(at, true), events(at.Add(-time.Second), false) - for i := range newer { - a.apply(newer[i]) - a.apply(newer[i]) - b.apply(older[i]) - a.apply(older[i]) - } - for _, tm := range times(a.info()) { - if !tm.Equal(at) { - panic("duplicate/out-of-order source time changed: " + tm.String()) - } - } - if bytes.Equal(states(a.info()), states(b.info())) { - panic("fault setup did not create divergence") - } - a.block.Store(false) - b.block.Store(false) - converge(a, b, "live-reconnect") - quiet(a, b, "live-steady") - for _, event := range older { - b.apply(event) - } - if !bytes.Equal(states(a.info()), states(b.info())) { - panic("stale event after convergence rolled back state") - } - stamp("six duplicate/out-of-order events rejected without clock changes") - a.block.Store(true) - b.block.Store(true) - dropped := b.dropped.Load() - check(a.s3.RemoveBucketTagging(ctx, bucket)) - if b.dropped.Load() <= dropped { - panic("ordinary delete RPC was not dropped") - } - stamp("real local DELETE outgoing replication RPC dropped by partition") - deleteAt := at.Add(time.Minute) - for _, event := range newer[:4] { - a.apply(madmin.SRBucketMeta{Type: event.Type, UpdatedAt: deleteAt}) - } - beforeRestart := states(a.info()) - a.stop() - a.start() - if !bytes.Equal(beforeRestart, states(a.info())) { - panic("restart lost deletion/source state") - } - stamp("four tombstones survived process restart") - a.block.Store(false) - b.block.Store(false) - converge(a, b, "delete-reconnect") - quiet(a, b, "delete-steady") - final := a.info() - if len(final.Policy) != 0 || final.Tags != nil || final.SSEConfig != nil || final.QuotaConfig != nil { - panic("deletion did not converge") - } - check(os.WriteFile(filepath.Join(root, "converged.json"), states(final), 0600)) - // Deliberate legacy and generation exceptions are outside convergence proof. - for i := 0; i < 20; i++ { - event := newer[1] - event.UpdatedAt = time.Time{} - a.apply(event) - event.UpdatedAt = final.CreatedAt.Add(-time.Duration(i+1) * time.Second) - a.apply(event) - } - data, e := os.ReadFile(filepath.Join(root, "site-a.jsonl")) - check(e) - for _, reason := range []string{"legacy-zero", "before-created"} { - n := strings.Count(string(data), "bucket metadata replication: "+reason) - if n != 1 { - panic(fmt.Sprintf("%s diagnostics count %d, want 1", reason, n)) - } - } - stamp("40 exceptional events produced exactly one log per reason") - if len(os.Args) > 3 { - stamp("starting fixed/previous-server rolling-upgrade smoke with gate off") - a.stop() - b.stop() - a.gate = "off" - b.gate = "off" - b.binary, e = filepath.Abs(os.Args[3]) - check(e) - a.start() - b.start() - tag, e := tags.NewTags(map[string]string{"upgrade": "works"}, true) - check(e) - check(a.s3.SetBucketTagging(ctx, bucket, tag)) - remote, e := b.s3.GetBucketTagging(ctx, bucket) - check(e) - if remote.ToMap()["upgrade"] != "works" { - panic("mixed-version live event lost") - } - check(a.s3.RemoveBucketTagging(ctx, bucket)) - if a.info().Tags != nil || b.info().Tags != nil { - panic("mixed-version ordinary tag delete lost") - } - if !a.info().TagConfigUpdatedAt.IsZero() { - panic("off exporter exposed new tombstone") - } - stamp("mixed-version ordinary PUT/DELETE and off tombstone visibility passed") - } - stamp("PASS: isolated two-site implementation acceptance") -} diff --git a/docs/investigations/multipart-conditional-completion.md b/docs/investigations/multipart-conditional-completion.md deleted file mode 100644 index 3a2148ce3..000000000 --- a/docs/investigations/multipart-conditional-completion.md +++ /dev/null @@ -1,57 +0,0 @@ -# Conditional multipart completion across pools - -## Defect and scope - -An unfinished multipart upload can remain in one pool while another pool holds -the logical current object. Evaluating `If-Match` against the upload pool's -local copy can accept a stale ETag or reject the current ETag. The pools object -lock serializes writes, but a local read still does not identify the logical -current object. - -This layout does not require rebalance. Commit -`83b2ad418b15ff0fa78175e2014d78d02046edd8` (upstream #21115) made `getPoolIdx` -choose an available pool even when `pinfo.Err == nil`: normal overwrites and -upload initiation can select different pools. This change predates the SILO -multi-pool consistency work. The deterministic regression fixtures place copies -and uploads directly in real erasure pools; they do not claim to run rebalance. - -## Minimal correction - -For multi-pool conditional completion, retain the existing object lock and use -`objectPoolInfos` to read the logical current object before completing the -upload. An unreadable pool is an error, not proof of absence. The first sorted -copy supplies the ETag and encryption metadata used by the existing callback. -A current delete marker is treated as an absent key. `If-Match` then fails for -an absent object; `If-None-Match: *` may proceed. - -Use explicit read options with an empty `VersionID` and `NoAuditLog: true`. -The precondition concerns the logical current object, independently of an -internal completion's destination version. After a successful check, clear -the callback before entering the set layer, so it is evaluated only once. -No new lock, storage format, replica cleanup algorithm or distributed protocol -is introduced. Single-pool and unconditional completion retain their existing -paths. - -## Availability and validation - -If any pool cannot supply the required metadata, conditional completion fails, -even when GET/HEAD can still read a copy from another pool. The unreadable pool -might hold a newer object, a delete marker, or no copy at all; none of these -possibilities can be assumed. Retry after recovery. This behavior is recorded -in the unreleased changelog. - -The regression suite covers both upload-pool directions, stale/current ETags, -`If-None-Match: *`, absent objects and delete markers, read-quorum errors, -explicit destination versions, tied modification times, callback counts, -upload preservation, signed HTTP error bodies and concurrent completions. -The ordinary HTTP routing control accepts every valid placement; deterministic -fixtures provide the cross-pool regression gate. - -## Separate follow-up scope - -The pool-placement change and conditional checks in `PutObject` and -`NewMultipartUpload` require separate assessment. This completion fix does not -repair those paths. In particular, PUT has live destination-version and -preserved-ETag semantics, so its repair must not copy this completion-specific -empty-VersionID rule without examining that contract. Parallelizing the shared -pool metadata reader is also outside this correctness fix. diff --git a/docs/investigations/r4-r8-integration/README.md b/docs/investigations/r4-r8-integration/README.md deleted file mode 100644 index 978de884a..000000000 --- a/docs/investigations/r4-r8-integration/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# R4–R8 集成核验 - -## 结论与范围 - -2026-09-16,R5、R6、R8 的修复在已包含 R4、R7 的 main 基线上完成集成。 -本地完整 `cmd`、`internal` 测试、相关 race 检查、仓库 verifiers、构建和 -HTTP 超时进程探针均通过。环境中的真实 `claude-opus-5`(effort `max`)独立 -阅读合并差异与调用链,结论为 **GO_WITH_NONBLOCKING_NOTES,零阻断项**。 - -本记录对应 main 合并前的代码核验。最终 PR 的 Linux CI、DCO 和合并结果以该 -PR 的实际提交及检查为准;这里的本地结果不代表发布、部署或多站点生产验收。 - -## 提交对应关系 - -基线:`9f3037e941a49ab4cd8a0eed7c0f01083fbe4bbe`。 - -| 问题 | 原修复提交 | 集成提交 | 行为 | -| --- | --- | --- | --- | -| R4 | PR [#193](https://github.com/pgsty/silo/pull/193),已在基线 | `af2b1794d38d9e70e1d2c3ee692426e4b6cab4bd`(merge) | SSE-KMS 复制保留标签修订时间 | -| R7 | PR [#194](https://github.com/pgsty/silo/pull/194),已在基线 | `9f3037e941a49ab4cd8a0eed7c0f01083fbe4bbe`(merge) | 复制元数据恢复不再重新写入传输用 aws-chunked | -| R5 | `115fe8b12329d147adbaf817faa1737392ecbf9b` | `680eac66e40b0980bc20e70d7ad34185096e63f5` | 删除标签推进修订,接收端抵御乱序事件,重试及 ACK 保留新状态 | -| R6 | `cf381a7151ef25fc95ace5fedcd767fa19410de2` | `0c61128d23f05ce6b37e7ace713c3ffbfb68f4cb` | 旧形态 marker purge 正确分类,MRF 恢复 marker 并保留重试次数 | -| R6 核验记录 | `d38edb2c46182d3a8fa96e040604493d20a4b478` | `aea3882c95d16ec5598a07b40d593e04054137a9` | 保存 v3 共识及验证边界 | -| R8 | `0d48d32d7e038ae1ea5966f3d7e0cb86780a6311` | `055030ea53ca92ee22ce1e601ef4757c247edde8` | 配置绑定到读头绝对超时,正文继续采用滚动空闲超时 | - -各原任务先取得 Opus 方案共识,再实施修复。原始方案、实现复核及验证记录保留在 -[R5](../r5/verification.md)、[R6](../r6/README.md)、[R8](../r8/README.md)。 -R5、R6、R8 原任务又分别只读核验了集成后的交叉影响,未发现新增生产阻断项。 - -集成使用 `git cherry-pick -x -s`,保留原作者、来源及 DCO。后续 -`80684fed59f556d579e268c5a855d936c1347b68` 仅处理两类贡献规范问题: - -- 六个新建测试文件统一使用实际贡献者姓名及 AGPL-3.0-or-later 头部;从 - `package` 开始的内容逐字节不变,Linux build tag 保留。 -- 按 CONTRIBUTING 的规则更新兼容标识清单。唯一新增条目是 R6 测试拼接既有 - replication ARN 所用的 `arn:minio:replication::`,没有新增协议名称或生产行为。 - -22 个源码/测试文件的最终哈希见 [manifest.json](manifest.json)。共享文件中的 -R5、R6 补丁与原修复具有相同稳定 patch ID;其余源文件直接比较,六个测试仅允许 -上述头部差异。[等价检查](evidence/integration-equivalence.json)全部通过。 - -## Opus 集成复核与处置 - -实际 CLI 为 2.1.270,显式指定 `claude-opus-5 --effort max`;只允许 Read、Grep、 -Glob,未执行测试或修改代码。实际返回模型为 `claude-opus-5`,进程和结果均成功。 -复核基于 `055030ea53ca92ee22ce1e601ef4757c247edde8` 的 22 个文件及完整差异; -此后的代码变化仅为上文已证明等价的头部与兼容清单调整。 - -原文、调用元数据及提示词分别见 [复核结果](evidence/opus-review.md)、 -[metadata](evidence/opus-integration.metadata.json)、[prompt](evidence/opus-integration.prompt.md)。 -保留原文中的判断,再用直接证据逐项处置,避免把模型意见当作测试结果: - -| 非阻断意见 | 核验与决定 | -| --- | --- | -| 非法或空的历史标签时间戳可能使复制失败并重试 | 保留 R5 共识中的失败关闭行为;历史异常数据修复另行处理 | -| 带标签修订的版本在 resync 时可能多一次 metadata COPY | R5 已接受的可靠性成本;正常 COMPLETED 路径保持原有门控 | -| purge 审计状态由 COMPLETE 规范为 COMPLETED,统计开始记录实际目标结果 | R6 的预期行为;后续发布说明应告知审计/指标使用者 | -| 配置的较短 ReadHeaderTimeout 同时缩短 TLS 握手窗口 | Go net/http 的预期语义,已在 R8 共识中说明 | -| 新增多池标签测试单独运行可能缺少全局初始化 | **未成立**:精确单独运行通过;`consistencyPools` 经 `prepareErasurePoolsWithContext` → `initObjectLayer` → `newTestObjectLayer` 调用 `initAllSubsystems`。保留测试原样 | -| 审计 fixture 重复取消可能输出栈信息 | 本地完整及 race 测试通过;不扩大本次生产修复范围 | -| 新测试文件头部应按实际贡献者整理 | 已在 `80684fed` 修正,测试代码及 build tag 不变 | - -## 本地直接验证 - -下表全部针对 `80684fed59f556d579e268c5a855d936c1347b68`,未使用额外的源码或 -容量 overlay;测试代码自身的容量 fixture 保留。限制并行度仅为 -`GOMAXPROCS=4`、`GOFLAGS=-p=2`,并使用 -仓库 CI 的 `MINIO_API_REQUESTS_MAX=10000`。详细命令、时间和日志哈希在 -[validation-results.json](evidence/validation-results.json)。 - -| 检查 | 结果 | -| --- | --- | -| `make verifiers`:lint、生成文件、rebrand guard | 通过,76.9 秒;可选 typos 工具按现有 Makefile 规则跳过 | -| `make build`、`./silo --version` | 通过,产物为 silo | -| `CGO_ENABLED=0 go test -p 2 ./cmd ./internal/... -count=1 -timeout=30m` | 全部通过,340.2 秒,50 个有测试的包 | -| `CGO_ENABLED=1 go test -race`,cmd/deadlineconn/http 中变更测试的函数集合 | 通过,46.3 秒;Linux build tag 用例由最终 Linux CI 覆盖 | -| `TestAPIPoolsTaggingReplicaDeletion` 精确单独执行 | 通过,无需其他测试预先运行 | -| 实际 silo 进程的 CLI/环境变量读头超时探针 | 两种配置均在 100ms 读头限制下拒绝 400ms 才完成的请求头;空闲超时为 2s,随后健康请求成功 | - -进程探针使用二进制 SHA-256 -`1cc536f1a3c8d8372ff2d5b140b1fd2bc98a299324fea0f73f67288d48104ce4`。 -原始输出见 [runtime-probe.json](evidence/runtime-probe.json)。 - -各子任务较早遇到的磁盘容量不足或筛选测试初始化问题,不作为这次通过的证据。 -本地完整测试已重新执行并成功;原失败记录仍保留在各自调查档案。 - -## 验收边界 - -- 最终 PR 必须通过实际提交的全部仓库检查,尤其 Linux internal 测试、完整 cmd - 测试、构建/vet、lint/生成文件、交叉编译、S3 Select race、DCO 和漏洞检查。 -- 既有无时间戳对象、异常时间戳、标签筛选的目标选择、任意站点时钟偏差等不由本次 - 修复追溯重建。共享状态解析器的历史限制按 R6 v3 共识在写入点规避。 -- R8 原先未完成的 S3 长传输脚本不计为通过;本次进程探针验证配置生效,不替代 - S3 长传输、独立多进程、多节点或跨区域生产验收。 -- 本次不引入依赖变更或上游 MinIO 兼容硬门槛;R9 不属于这五项修复。 diff --git a/docs/investigations/r4-r8-integration/evidence/full-tests.log b/docs/investigations/r4-r8-integration/evidence/full-tests.log deleted file mode 100644 index 03138b03a..000000000 --- a/docs/investigations/r4-r8-integration/evidence/full-tests.log +++ /dev/null @@ -1,79 +0,0 @@ -ok github.com/minio/minio/cmd 320.458s -ok github.com/minio/minio/internal/amztime 1.605s -ok github.com/minio/minio/internal/arn 0.392s -ok github.com/minio/minio/internal/auth 0.440s -ok github.com/minio/minio/internal/bpool 0.422s -ok github.com/minio/minio/internal/bucket/bandwidth 0.450s -ok github.com/minio/minio/internal/bucket/cors 0.484s -ok github.com/minio/minio/internal/bucket/encryption 0.644s -ok github.com/minio/minio/internal/bucket/lifecycle 0.647s -ok github.com/minio/minio/internal/bucket/object/lock 0.653s -ok github.com/minio/minio/internal/bucket/replication 0.496s -ok github.com/minio/minio/internal/bucket/versioning 0.449s -ok github.com/minio/minio/internal/cachevalue 5.457s -? github.com/minio/minio/internal/color [no test files] -ok github.com/minio/minio/internal/config 0.656s -? github.com/minio/minio/internal/config/api [no test files] -? github.com/minio/minio/internal/config/batch [no test files] -? github.com/minio/minio/internal/config/browser [no test files] -? github.com/minio/minio/internal/config/callhome [no test files] -ok github.com/minio/minio/internal/config/compress 0.624s -ok github.com/minio/minio/internal/config/dns 0.731s -? github.com/minio/minio/internal/config/drive [no test files] -ok github.com/minio/minio/internal/config/etcd 1.015s -? github.com/minio/minio/internal/config/heal [no test files] -ok github.com/minio/minio/internal/config/identity/ldap 0.593s -ok github.com/minio/minio/internal/config/identity/openid 0.675s -? github.com/minio/minio/internal/config/identity/openid/provider [no test files] -? github.com/minio/minio/internal/config/identity/plugin [no test files] -? github.com/minio/minio/internal/config/identity/tls [no test files] -ok github.com/minio/minio/internal/config/ilm 1.057s -? github.com/minio/minio/internal/config/lambda [no test files] -ok github.com/minio/minio/internal/config/lambda/event 0.485s -? github.com/minio/minio/internal/config/lambda/target [no test files] -ok github.com/minio/minio/internal/config/notify 0.752s -? github.com/minio/minio/internal/config/policy/opa [no test files] -? github.com/minio/minio/internal/config/policy/plugin [no test files] -? github.com/minio/minio/internal/config/scanner [no test files] -ok github.com/minio/minio/internal/config/storageclass 0.585s -ok github.com/minio/minio/internal/config/subnet 0.582s -ok github.com/minio/minio/internal/crypto 0.843s -ok github.com/minio/minio/internal/deadlineconn 4.934s -ok github.com/minio/minio/internal/disk 0.418s -ok github.com/minio/minio/internal/dsync 131.144s -ok github.com/minio/minio/internal/etag 0.504s -ok github.com/minio/minio/internal/event 0.595s -ok github.com/minio/minio/internal/event/target 0.957s -ok github.com/minio/minio/internal/grid 7.616s -ok github.com/minio/minio/internal/handlers 0.717s -ok github.com/minio/minio/internal/hash 0.700s -? github.com/minio/minio/internal/hash/sha256 [no test files] -ok github.com/minio/minio/internal/http 13.643s -? github.com/minio/minio/internal/init [no test files] -ok github.com/minio/minio/internal/ioutil 1.921s -ok github.com/minio/minio/internal/jwt 0.423s -ok github.com/minio/minio/internal/kms 0.538s -ok github.com/minio/minio/internal/lock 1.077s -ok github.com/minio/minio/internal/logger 0.546s -? github.com/minio/minio/internal/logger/message/audit [no test files] -? github.com/minio/minio/internal/logger/target/console [no test files] -? github.com/minio/minio/internal/logger/target/http [no test files] -? github.com/minio/minio/internal/logger/target/kafka [no test files] -? github.com/minio/minio/internal/logger/target/loggertypes [no test files] -? github.com/minio/minio/internal/logger/target/testlogger [no test files] -ok github.com/minio/minio/internal/lsync 10.501s -? github.com/minio/minio/internal/mcontext [no test files] -? github.com/minio/minio/internal/mountinfo [no test files] -? github.com/minio/minio/internal/net [no test files] -? github.com/minio/minio/internal/once [no test files] -ok github.com/minio/minio/internal/pubsub 0.511s -ok github.com/minio/minio/internal/rest 0.525s -ok github.com/minio/minio/internal/ringbuffer 1.316s -ok github.com/minio/minio/internal/s3select 0.603s -ok github.com/minio/minio/internal/s3select/csv 0.446s -ok github.com/minio/minio/internal/s3select/json 0.439s -ok github.com/minio/minio/internal/s3select/jstream 0.417s -? github.com/minio/minio/internal/s3select/parquet [no test files] -? github.com/minio/minio/internal/s3select/simdj [no test files] -ok github.com/minio/minio/internal/s3select/sql 0.425s -ok github.com/minio/minio/internal/store 1.451s diff --git a/docs/investigations/r4-r8-integration/evidence/header-equivalence.json b/docs/investigations/r4-r8-integration/evidence/header-equivalence.json deleted file mode 100644 index ca464a36f..000000000 --- a/docs/investigations/r4-r8-integration/evidence/header-equivalence.json +++ /dev/null @@ -1,50 +0,0 @@ -[ - { - "file": "cmd/replication-delete-mrf_test.go", - "before_sha256": "5e160f7e19cbbb8cd5fa4e7ffd9cff9e09361b3fc4c5ee5ae61458f99c777781", - "after_sha256": "6fcfaf505d28f98e42dff9c0d965895be2c2d112e9e996220d424aea1f76d691", - "body_sha256": "5b3cecec74273540f4a5f82ca55e8a28545d0252822af3568f7e36b19011bda8", - "body_identical": true, - "build_prefix_preserved": true - }, - { - "file": "cmd/replication-delete-operation_test.go", - "before_sha256": "2888a04a543324776041316de2821f388d28c3c1a6f5d0031e5ac9d34f58d504", - "after_sha256": "2e674cab5ca4dbc38cb2c1ddcca117269276e6419e1869c154c3bd6a05676715", - "body_sha256": "7921af42a9f123450e3e567d0bf65cd030678404709c77fde4a9cbb366230c35", - "body_identical": true, - "build_prefix_preserved": true - }, - { - "file": "cmd/server_deadline_config_test.go", - "before_sha256": "1013157f83baa5f7882ec2d41c7b1fccb9e05fb418d0fa61263953037c9698c4", - "after_sha256": "a8259d273922a8973b44d9a468791761d373fe265fff8b2b53871e4626b11405", - "body_sha256": "5714a9275aee7230d54ba8ed03e3705a3cf6e150bda122929a9131b4b2b8dd5c", - "body_identical": true, - "build_prefix_preserved": true - }, - { - "file": "internal/deadlineconn/deadlineconn_strict_test.go", - "before_sha256": "f405690c9ff044595f48323d68f4a9b33ce695b3ad6820f54f17151067bfae5e", - "after_sha256": "b4aec28c5daddb36e6ebb98dd8af2a44b7a2c48f0660068534f69669609f67a7", - "body_sha256": "ab2b54e29ed34de6e59ba2fa4984d61ed5f93d74ae4f6cd22dd44ba77a37e569", - "body_identical": true, - "build_prefix_preserved": true - }, - { - "file": "internal/http/dial_deadline_linux_test.go", - "before_sha256": "0939d05b72a09760d53fcdf249775989e3f89bca824b9961d0b2a657ebfdf41e", - "after_sha256": "c4c83bda92ba9bac53166453920456134b3d8265cd59101b4203067c67eca59e", - "body_sha256": "fac091ef08f29fe32c2668eccd8cd505106c9fe3c4715e4c3ed9063d4b71c86e", - "body_identical": true, - "build_prefix_preserved": true - }, - { - "file": "internal/http/server_deadline_test.go", - "before_sha256": "a6e687b3904a876fa92a4c5b86453159f3e5a38a4b9412dc213c7772f47fbf0b", - "after_sha256": "87303cc389bf1cf759898489f06b001073de2dd9c4b3687c4eca14aa186b62ab", - "body_sha256": "20b238f34090c356b2254388e863d77d0a316f33b849291d15d94fad260f23dd", - "body_identical": true, - "build_prefix_preserved": true - } -] diff --git a/docs/investigations/r4-r8-integration/evidence/integration-equivalence.json b/docs/investigations/r4-r8-integration/evidence/integration-equivalence.json deleted file mode 100644 index 4c5037152..000000000 --- a/docs/investigations/r4-r8-integration/evidence/integration-equivalence.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "head": "80684fed59f556d579e268c5a855d936c1347b68", - "checks": { - "r6_shared_file_patch": true, - "r5_shared_file_patch": true, - "cmd/erasure-object.go": true, - "cmd/erasure-server-pool-consistency.go": true, - "cmd/erasure-server-pool.go": true, - "cmd/object-handlers-common.go": true, - "cmd/object-handlers.go": true, - "cmd/object-multipart-handlers.go": true, - "cmd/replication-tagging-order_test.go": true, - "cmd/replication-tagging-sender_test.go": true, - "cmd/bucket-replication-utils.go": true, - "cmd/replication-delete-marker_test.go": true, - "cmd/replication-delete-operation_test.go": true, - "cmd/replication-delete-mrf_test.go": true, - "cmd/common-main.go": true, - "cmd/server-main.go": true, - "internal/deadlineconn/deadlineconn.go": true, - "internal/http/listener.go": true, - "internal/http/server.go": true, - "cmd/server_deadline_config_test.go": true, - "internal/deadlineconn/deadlineconn_strict_test.go": true, - "internal/http/dial_deadline_linux_test.go": true, - "internal/http/server_deadline_test.go": true, - "only_reviewed_hygiene_changes": true, - "dco:80684fed59f556d579e268c5a855d936c1347b68": true, - "dco:055030ea53ca92ee22ce1e601ef4757c247edde8": true, - "dco:aea3882c95d16ec5598a07b40d593e04054137a9": true, - "dco:0c61128d23f05ce6b37e7ace713c3ffbfb68f4cb": true, - "dco:680eac66e40b0980bc20e70d7ad34185096e63f5": true - }, - "all_pass": true -} diff --git a/docs/investigations/r4-r8-integration/evidence/isolated-pools-before.log b/docs/investigations/r4-r8-integration/evidence/isolated-pools-before.log deleted file mode 100644 index 2d59ab2de..000000000 --- a/docs/investigations/r4-r8-integration/evidence/isolated-pools-before.log +++ /dev/null @@ -1 +0,0 @@ -ok github.com/minio/minio/cmd 1.583s diff --git a/docs/investigations/r4-r8-integration/evidence/isolated-pools-before.result.json b/docs/investigations/r4-r8-integration/evidence/isolated-pools-before.result.json deleted file mode 100644 index 7bf02c351..000000000 --- a/docs/investigations/r4-r8-integration/evidence/isolated-pools-before.result.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "command": [ - "go", - "test", - "-p", - "2", - "./cmd", - "-run", - "^TestAPIPoolsTaggingReplicaDeletion$", - "-count=1", - "-timeout=2m" - ], - "exit_code": 0, - "seconds": 5.096, - "log": "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/isolated-pools-before.log", - "log_sha256": "f014a1a72dda7b973cd1e0b7e77c70b470eeebca0d04805d98f07c2622c87b1e" -} diff --git a/docs/investigations/r4-r8-integration/evidence/make-build.log b/docs/investigations/r4-r8-integration/evidence/make-build.log deleted file mode 100644 index 1c3f2c153..000000000 --- a/docs/investigations/r4-r8-integration/evidence/make-build.log +++ /dev/null @@ -1,2 +0,0 @@ -Checking dependencies -Building Silo binary to './silo' diff --git a/docs/investigations/r4-r8-integration/evidence/make-verifiers-final.log b/docs/investigations/r4-r8-integration/evidence/make-verifiers-final.log deleted file mode 100644 index 759785f46..000000000 --- a/docs/investigations/r4-r8-integration/evidence/make-verifiers-final.log +++ /dev/null @@ -1,7 +0,0 @@ -Running lint check -0 issues. -typos binary is not found.. skipping.. -compatibility manifest: imports=119 env=428 metrics=19 headers=87 routes=224 roots=1 grid=3 storage=16 policy=59 brand=181 sha256=ad05829578cf879b462a12fa65f3c10c8c7c6aa8d6c329e04779459eec2645be -Silo rebrand compatibility baseline is unchanged -Silo delivery and runtime rebrand checks passed -docker entrypoint argv compatibility tests passed diff --git a/docs/investigations/r4-r8-integration/evidence/opus-integration.metadata.json b/docs/investigations/r4-r8-integration/evidence/opus-integration.metadata.json deleted file mode 100644 index ca3ef9796..000000000 --- a/docs/investigations/r4-r8-integration/evidence/opus-integration.metadata.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "candidate": "055030ea53ca92ee22ce1e601ef4757c247edde8", - "base": "9f3037e941a49ab4cd8a0eed7c0f01083fbe4bbe", - "requested_model": "claude-opus-5", - "requested_effort": "max", - "cli_version": "2.1.270", - "command": [ - "/opt/homebrew/bin/claude", - "--print", - "--model", - "claude-opus-5", - "--effort", - "max", - "--safe-mode", - "--permission-mode", - "plan", - "--tools", - "Read,Grep,Glob", - "--strict-mcp-config", - "--no-session-persistence", - "--add-dir", - "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab", - "--add-dir", - "/Users/vonng/pgsty/silo", - "--output-format", - "stream-json", - "--verbose" - ], - "source_sha256": { - "cmd/bucket-replication-utils.go": "365641c760901641e8320cc5123697ab92a46f1add612048b991ed2d1cfad43b", - "cmd/bucket-replication.go": "2e766c5946dcabaea79455b50a8e426f404e55c92dcbccbe28d55906e2e43843", - "cmd/common-main.go": "f8777fe8a07d175aceee07b4dd13792b2384449c404c004c38a06893be997843", - "cmd/erasure-object.go": "4bc848685ea714d88cabbd5d1b8585fbcc06f7b19c775e1a811030e783d0e1a4", - "cmd/erasure-server-pool-consistency.go": "d2736ef6bffbb5c5758eba8df38f8d4ecb888a838ab0de8ad3cf015c051f8ad7", - "cmd/erasure-server-pool.go": "87ad0b25dfa3081d0e63d0073b788614a9c88e2498a2ce0956b93f8a0a03ef53", - "cmd/object-handlers-common.go": "101bd7d7447072d13fed50983b69b562e4725632645e623d7fdd490f388ecdec", - "cmd/object-handlers.go": "61897a260f3f5f660f41edcb50956c60e914ef98f9a987da824f16d78171fde2", - "cmd/object-multipart-handlers.go": "d9622c69c540ab32dd23916e3f534b6886473a98370c9dd17673e69a423b2a7e", - "cmd/replication-delete-marker_test.go": "d967787804d558ac6266b113228fdf4a4f9fcb7cab39138a4fb07558814ccca4", - "cmd/replication-delete-mrf_test.go": "5e160f7e19cbbb8cd5fa4e7ffd9cff9e09361b3fc4c5ee5ae61458f99c777781", - "cmd/replication-delete-operation_test.go": "2888a04a543324776041316de2821f388d28c3c1a6f5d0031e5ac9d34f58d504", - "cmd/replication-tagging-order_test.go": "c8260b4ccf82fa615e1e24b35a07f2d1aacbcf776e5c6f9dadffea4a09ad6ea8", - "cmd/replication-tagging-sender_test.go": "3770a1a48a6efe58fe8127e1e4fdf6bd7cf171e17db20f15222ea2f7b85db1af", - "cmd/server-main.go": "04c265de211412ba0297396928096d7f2d971244a957a3126154846035263514", - "cmd/server_deadline_config_test.go": "1013157f83baa5f7882ec2d41c7b1fccb9e05fb418d0fa61263953037c9698c4", - "internal/deadlineconn/deadlineconn.go": "b9272ef640f1d4403b3d0af6cdbaba9186c51ad9a0226dfe449e8ef738e1ec4b", - "internal/deadlineconn/deadlineconn_strict_test.go": "f405690c9ff044595f48323d68f4a9b33ce695b3ad6820f54f17151067bfae5e", - "internal/http/dial_deadline_linux_test.go": "0939d05b72a09760d53fcdf249775989e3f89bca824b9961d0b2a657ebfdf41e", - "internal/http/listener.go": "49628575367f6ab9b6986caf594726d74d370f7d2ac4eed582903600b6eb3fa2", - "internal/http/server.go": "b7b0355f2781f8c5f7c77bc910cd4180cd3e5f22a87de41bd35ef119d36b4cdf", - "internal/http/server_deadline_test.go": "a6e687b3904a876fa92a4c5b86453159f3e5a38a4b9412dc213c7772f47fbf0b" - }, - "diff_sha256": "d8c4e60f9e4a5b1338f3e6e07b758b1bb279db80eec26847e3b35fde0d049485", - "prompt_sha256": "f95edcf71afedceab190ff57bfbec812b06c8c18b5370d887131a760228c2b2c", - "started_at": "2026-09-15T16:41:37.757693+00:00", - "status": "completed", - "exit_code": 0, - "actual_models": [ - "claude-opus-5" - ], - "finished_at": "2026-09-15T16:53:09.761932+00:00", - "raw_sha256": "3e1a45ebdf3f4420fb05647bb383c00d0a86452a6c357a92679485c99d8f4eb3", - "stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "result_subtype": "success", - "is_error": false -} diff --git a/docs/investigations/r4-r8-integration/evidence/opus-integration.prompt.md b/docs/investigations/r4-r8-integration/evidence/opus-integration.prompt.md deleted file mode 100644 index 0a0dc3ffa..000000000 --- a/docs/investigations/r4-r8-integration/evidence/opus-integration.prompt.md +++ /dev/null @@ -1,18 +0,0 @@ -Independently review the complete SILO R4-R8 integration candidate for a user-authorized merge to main. You are the real Claude Opus reviewer; provide your own conclusion from source inspection. Read-only: no edits, no GitHub actions, no test execution claims. - -Exact candidate: 055030ea53ca92ee22ce1e601ef4757c247edde8; integration branch codex/merge-r4-r8. Base: 9f3037e941a49ab4cd8a0eed7c0f01083fbe4bbe, already contains separately reviewed and CI-accepted R4 (SSE-KMS tag timestamp) and R7 (replication metadata/aws-chunked). This candidate adds the final R5, R6, and R8 local repairs, cherry-picked without conflicts and with provenance/DCO preserved. - -Read /Users/vonng/pgsty/silo/AGENTS.md and CONTRIBUTING.md. The maintained PGSTY stack is the release target; upstream MinIO compatibility is best effort. Read /Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/integration-code.diff and /Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/reviewed-source.json, then inspect complete relevant functions and tests in this worktree. Each repair already has real same-version Opus plan consensus: docs/investigations/r5/plan-v2.md and consensus.md; r6/plan-v3.md and consensus.md; r8/plan-v2.md and consensus.md. Prior implementation reviews and validation reports are supporting evidence, not substitutes for this integration review. The R6 v2 multi-target parser proof was disproved by a real storage counterexample and fixed only after v3 consensus; verify the final empty creation-update invariant. - -Focus on concrete integration correctness: -1. R5 tag revision persistence and empty/nonempty ordering through COPY, PUT, multipart, retries and source ACK, with R6 purge/MRF state writes and shared bucket-replication.go functions. -2. R7 restores only six replication-specific fields. Verify tag values/timestamps remain handled correctly and aws-chunked is not reintroduced, including R4 KMS options. -3. R6 marker creation versus canonical/legacy purge, all exits, per-target statistics, disk creation/replica metadata preservation, identity-checked marker 405 recovery, retry counts and bounded scanner fallback. -4. R8 absolute request-header deadlines and CLI/env propagation, HTTP/1 streaming bodies, keep-alive/TLS/h2 boundaries, default DeadlineConn callers and effects on replication I/O. -5. Full-package test global state, cleanup/initialization ordering and any compile/dependency conflicts that separate scoped tests would miss. - -Current local make verifiers is running; full candidate runtime tests and actual PR CI are still pending. Do not label them as passed. The host previously had ENOSPC; it now has more than 700 GiB free, so standard full-package tests can run without capacity overlays. Historical reports accurately retain earlier environment limits and incomplete multi-process deployment acceptance. - -Two new R6 and four new R8 test files have boilerplate copyright headers queued for repository-policy normalization, after confirming original authorship. They will be header-only changes with package-and-body byte equivalence checked; do not conflate them with production semantic edits. Any actual implementation blocker will be fixed and re-reviewed as needed. - -Return GO_WITH_NONBLOCKING_NOTES or REQUEST_CHANGES, number of blocking findings, exact candidate identity and inspected scope. For each blocker give path/line, real triggering conditions, impact and smallest compatible correction. Clearly distinguish inherited out-of-scope limitations from newly introduced defects and required CI from optional further deployment tests. Do not invent a blocker merely from absence of production deployment. This request is main merge, not release/deploy. diff --git a/docs/investigations/r4-r8-integration/evidence/opus-review.md b/docs/investigations/r4-r8-integration/evidence/opus-review.md deleted file mode 100644 index 4130b73d6..000000000 --- a/docs/investigations/r4-r8-integration/evidence/opus-review.md +++ /dev/null @@ -1,49 +0,0 @@ -The plan file can't be written (Write is disabled this session), so here is the complete review inline. - -# SILO R4–R8 integration review — independent, source-only - -**Verdict: GO_WITH_NONBLOCKING_NOTES — 0 blocking findings.** - -**Candidate identity:** head `055030ea53ca92ee22ce1e601ef4757c247edde8`, branch `codex/merge-r4-r8`, base `9f3037e941a49ab4cd8a0eed7c0f01083fbe4bbe`, worktree `/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/silo`. Scope of this review is a **main merge**, not a release or deployment. I executed nothing — no tests, no builds; every statement below is from reading source. - -**Inspected scope:** `integration-code.diff` in full (3367 lines) and all 22 files in `reviewed-source.json`, then the complete surrounding functions in the worktree: `cmd/bucket-replication.go` (`replicateDelete`, `replicateDeleteToTarget`, `replicateObject`, `replicateAll`, `putReplicationOpts`, `getReplicationAction`, `queueMRFHeal`, `queueReplicaDeleteTask`, `queueMRFSave`, `queueReplicationHeal`), `bucket-replication-utils.go`, `bucket-replication-stats.go`, `xl-storage-format-v2.go DeleteVersion`, `erasure-object.go`, `erasure-multipart.go`, `erasure-server-pool.go`, `erasure-server-pool-consistency.go`, `object-handlers.go`, `object-handlers-common.go`, `object-multipart-handlers.go`, `handler-utils.go`, `object-api-options.go`, `bucket-handlers.go`, `common-main.go`, `server-main.go`, `internal/http/{server,listener}.go`, `internal/deadlineconn/deadlineconn.go`, all six new test files plus the base tests they interact with, `Makefile`, `.golangci.yml`, `AGENTS.md`, `CONTRIBUTING.md`, and the r5/r6/r8 plan + consensus records. Cross-checked against Go 1.27.1 `net/http/server.go` and pinned `minio-go/v7 v7.3.1-0.20260910142817-60bd07042d49`. - -## Independent confirmation of the key claims - -**R6 final empty creation-update invariant (the disproved-v2 point).** The v2 proof fails exactly as recorded: `replStatusRegex` (`bucket-replication-utils.go:168`) matches `arn1=;arn2=;` yielding `{arn1: ";arn2="}` — non-empty — while single-target `arn1=;` does not match at all. The v3 fix at `bucket-replication.go:575-582` sets `ReplicationStatusInternal=""`, `Targets=nil`, `ReplicaStatus=""`; `CompositeReplicationStatus` (`bucket-replication-utils.go:356-379`) then returns empty via both the internal string and the replica fallback, so `xlMetaV2.DeleteVersion` (`xl-storage-format-v2.go:1396-1405`, `1438-1447`) skips the creation/replica write while still writing `VersionPurgeStatusKey` (`1406-1408`, `1448-1450`). `ReplicationTimeStamp` is therefore inert (consensus N5 holds). I also confirmed the counterexample's precondition independently: `erasure-object.go:2099-2105` leaves `deleteMarker=true` when the stored marker carries no purge status, which is what sets `fi.Deleted=true` and reaches the rewriting branch. COMPLETE purges still remove the version (`1379-1393`, `1457-1459`). - -**R6 classification / all exits.** `isVersionPurge()` (`1954-1956`) parses as `VersionID != "" || (DeleteMarkerVersionID != "" && !VersionPurgeStatus().Empty())`. Every live producer emits only one shape (`object-handlers.go:3232-3248`, `bucket-replication.go:3362-3378`, `3814-3836`), and the dir-object `nullVersionID` re-add (`bucket-handlers.go:552-555`, `679-681`) classifies as a purge under both old and new code, so no wire-form change there. All exits of `replicateDeleteToTarget` select the purge field consistently, HEAD probing is creation-only, `ReplicationDeleteMarker` is false for purges. `purgeReplicationStatus` maps only the legacy `COMPLETE` spelling; `ReplicationStats.Update` (`bucket-replication-stats.go:179-237`) consumes the passed status and never `rinfo.ReplicationStatus`, with `ri.Size == 0` giving count-only/zero-byte deltas. The `ResetStatusesMap` nil guard fixes a genuine nil-map assignment panic, since `ObjectToDelete.ReplicationState()` never initialises that map. - -**R6 MRF recovery / budget.** `queueMRFHeal:4118-4125` parses as `(err != nil && !validMarker) || oi.Name == ""`; `decodeDirObject` is identity for both `obj` and `dir/`, matching `GetObjectInfo`'s decoded name, and `erasure-server-pool-consistency.go:143-145` is what returns a populated marker `ObjectInfo` with `MethodNotAllowed`. `RetryCount int` matches the persisted `MRFReplicateEntry.RetryCount` and `QueueReplicationHeal`'s parameter — no on-disk format change. All three increment sites feed the existing `> mrfRetryLimit` drop accounting (`3926-3931`), and the scanner fallback restarts with a fresh budget. - -**R5 tag revision flow.** Sender: `replicationTaggingTimestamp` (`804-812`) serves both the full retransmit and the metadata-COPY branch, the latter now failing closed symmetrically (`1720-1725`). `getCopyObjMetadata:765-766` always emits `X-Amz-Tagging` (possibly empty) + `REPLACE`; minio-go `copyObjectDo` forwards empty header values; the receiver's `getRequestHeaderOrQueryValue` (`handler-utils.go:160-171`) treats presence-with-empty-value as authoritative — so an ordered deletion is genuinely representable on the wire. Receiver: `CopyObjectHandler:1799-1840` captures the stored stamp before REPLACE rebuilds the map, every branch writes or deletes the key explicitly, and the unconditional `delete(encMetadata, …)` is safe *because* of that, blocking the SSE-C rotation snapshot (`1655-1659`) from re-merging at `1910`. `PutObjectHandler:2323-2325` and `NewMultipartUploadHandler:315-318` mutate the same map that becomes `opts.UserDefined` (`object-api-options.go:451`), and the header is parsed only under trusted replication (`388-396`). Ordering is re-applied under the write lock by the existing `reconcileStoredObjectTags` callers (`erasure-object.go:136-139`, `1312-1315`; `erasure-multipart.go:1161-1189`; `erasure-server-pool.go:1443-1456`) — which is also what keeps the base R4 KMS test's `missing-timestamp` expectation intact. Dropping the `ri.UserTags` re-injection in the source ACK (`1294-1304`) is right: `cleanMetadata` strips the tagging key from `UserDefined`, so stored tags are now left alone, and the pools path re-derives them from merged `UserTags`. - -**R7 boundary.** `replicationToInternalHeaders` has exactly six entries (`handler-utils.go:106-114`); `extractReplicationMetadataFromMime` restores only those and re-extracts no ordinary metadata, so the `aws-chunked` normalisation in `extractMetadata:225-241` is not undone. R5 touches neither, and R4's KMS options (`object-api-options.go:449-460`) still carry the three replication timestamps unmodified. - -**R8 deadlines.** Against Go 1.27.1: header window set at `server.go:2038`/`2177`, whole-request deadline unconditionally at `1103`, `StateActive` at `2056-2058` firing after *every* successful `readRequest` because `readRequest` calls `setInfiniteReadLimit()` at `1067`. That is the one place where the naive reading of the `c.r.remain` comment is wrong — the pipelined/fully-buffered request does get the strict→rolling flip, so consensus N2 is correct. `startBackgroundRead` (`741`) and `hijackLocked` zero the deadline, which `infReads` honours — that is why background reads and hijacked grid/websocket conns still work. The strict cap only shortens, never extends; zero/past semantics unchanged; `readExplicit`/`readDeadlineStrict` only touched under `mu`. The h2 skip is defensive rather than load-bearing (net/http uses `skipHooks` for ALPN h2; the h2 server zeroes the conn deadline), and a nil `raw` fails the type assertion safely. Strict mode is opt-in, so every other `DeadlineConn` caller — the optional Linux internode dialer (`dial_linux.go:126-131`, currently disabled at `server-main.go:422`) and all outbound replication transports — keeps legacy rolling reads. The real fix is propagation: flag, field and `UseReadHeaderTimeout` already existed; `ctxt.ReadHeaderTimeout` was simply never populated before `common-main.go:448`. - -**Full-package test state.** No duplicate symbols (`tagTestCapacityDisk` defined once in base `erasure-server-pool-tags_test.go:258`); no helper collisions in `internal/http`; `testdata/config/1.yaml`, `fmtGenFlags`, `serverCmd.Flags` all exist; `buildServerCtxt` mutates no globals. Globals are swapped/restored, and `prepareFS`/`prepareErasure`/`initAPIHandlerTest` re-run `initAllSubsystems` between backends, so leaked target-sys entries can't cross a fixture boundary. `logger.UpdateAuditWebhooks(ctx, nil)` really clears the list (`targets.go:227-273`), so the audit fixture is re-enterable across the SD and Erasure passes. `make verifiers` = lint + check-gen + rebrand-guard, and `.golangci.yml` enables no header linter. - -## Blocking findings - -**None.** - -## Non-blocking notes (newly introduced here) - -1. `bucket-replication.go:804-812` — an unparseable stored tagging timestamp now fails replication for *untagged* objects too. Trigger: a corrupt/legacy value, or the empty value `erasure-server-pool-consistency.go:211-214` can persist when a non-primary pool holds a malformed stamp. Fail-closed (FAILED + MRF retry), never silent divergence. Optional hardening: treat missing-or-empty as absent, keeping the hard error for non-empty malformed values. -2. `bucket-replication.go:1031-1036` — any version with a tag revision now resolves to `replicateMetadata` instead of `replicateNone`; since `object-handlers.go:1835-1836` gives every non-replica COPY a revision, large resyncs issue one extra metadata COPY per object. Steady state is unaffected (COMPLETED gate at `3810`). Accepted in r5 consensus; flagged as operational cost. -3. `bucket-replication.go:435-445`, `551-566`, `1958-1964` — purge audit status changes from legacy `COMPLETE` to `COMPLETED`, and per-target purge outcomes now reach `globalReplicationStats` where the empty creation status previously recorded nothing. Intended (r6 dispositions 1–2); worth one release-note line for audit/metric consumers. -4. `common-main.go:448` — now that `ReadHeaderTimeout` is populated it also participates in `net/http`'s `tlsHandshakeTimeout()` minimum. No change at defaults (30s vs 30s); a small configured header timeout also shortens the TLS handshake window. Documented in r8 consensus N1. -5. `replication-tagging-order_test.go:1805-1814` — `TestAPIPoolsTaggingReplicaDeletion` calls `globalBucketMetadataSys.Update` after `consistencyPools`, which (unlike `initAPIHandlerTest`) does not run `initAllSubsystems`; run in isolation with `-run` it would nil-dereference. Harmless in full-package CI. Smallest fix: use `initAPIHandlerTest`, as the neighbouring consistency tests do. -6. `replication-delete-mrf_test.go:1208-1215` — the audit cleanup cancels targets twice; non-panicking because `SafeClose(nil)` only prints a stack (`internal/ioutil/ioutil.go:463-471`), but it can add stderr noise. -7. Headers (already queued, header-only): the two new R6 test files use `Copyright (c) 2026 PGSTY` + SPDX-only, and the four new R8 test files carry `Copyright (c) 2015-2021 MinIO, Inc.` despite being new original work; CONTRIBUTING.md asks new original files to name their actual holders with AGPL boilerplate (the two new R5 files already comply). No verifier enforces headers, so this does not gate CI and is distinct from production semantics. - -## Inherited / out-of-scope limitations (not introduced by this candidate) - -Purge-target subset merging under narrowed fan-out; nil/missing target clients cannot restore already-lost tracking; the `ResetStatusesMap` key asymmetry between `targetResetHeader(arn)` writes and bare-`arn` reads (`bucket-replication-utils.go:392-399` vs `419-426`) — the candidate only adds the panic-preventing nil guard; the shared `replStatusRegex` still mis-parses serialized empty statuses (R6 deliberately works around it at the write site); unqualified (no `versionId`) replica PUT has no stored-tag reconcile (`object-handlers.go:2442`); TLS handshake *write* deadlines keep rolling behaviour (r8 N9); historical objects without a revision, tag-filter target selection and real multi-site clock skew remain unsolved (r5 consensus). - -## Verification still owed (I ran nothing; nothing below is "passed") - -**Required for this merge — repository gates only:** `make verifiers` (lint, check-gen, rebrand-guard), `make build` producing `silo`, and full-package tests, at minimum `./cmd`, `./internal/http`, `./internal/deadlineconn`; a Linux CI leg to compile and run the build-tagged `TestInternodeDialReadDeadline`; DCO sign-off/authorship checks on the cherry-picked commits. With >700 GiB free, no capacity overlay is needed. - -**Optional, explicitly not a merge gate:** `SILO_TEST_LONG_UPLOAD=1` (>30s default-idle transfer regressions), multi-process/multi-node deployment acceptance, cross-region replication mesh. Their absence is a coverage boundary, not a defect; I do not treat missing production deployment as a blocker. diff --git a/docs/investigations/r4-r8-integration/evidence/runtime-probe.json b/docs/investigations/r4-r8-integration/evidence/runtime-probe.json deleted file mode 100644 index ec291b4c7..000000000 --- a/docs/investigations/r4-r8-integration/evidence/runtime-probe.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "binary": "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/silo/silo", - "binary_sha256": "1cc536f1a3c8d8372ff2d5b140b1fd2bc98a299324fea0f73f67288d48104ce4", - "expected_rejection": true, - "cases": [ - { - "source": "flag", - "header_timeout_ms": 100, - "idle_timeout_ms": 2000, - "header_completion_delay_ms": 400, - "rejected": true, - "status": "", - "elapsed_seconds": 0.403, - "still_alive": true - }, - { - "source": "environment", - "header_timeout_ms": 100, - "idle_timeout_ms": 2000, - "header_completion_delay_ms": 400, - "rejected": true, - "status": "", - "elapsed_seconds": 0.402, - "still_alive": true - } - ] -} diff --git a/docs/investigations/r4-r8-integration/evidence/runtime-probe.result.json b/docs/investigations/r4-r8-integration/evidence/runtime-probe.result.json deleted file mode 100644 index b9fb764a6..000000000 --- a/docs/investigations/r4-r8-integration/evidence/runtime-probe.result.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "command": [ - "python3", - "docs/investigations/r8/evidence/runtime_probe.py", - "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/silo/silo", - "fixed", - "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/runtime-probe" - ], - "exit_code": 0, - "seconds": 1.638, - "stdout_sha256": "e493d9757c130c2531072dc0eaee42b8fc1fa0f45fc43d0d0e098aac55f0d387" -} diff --git a/docs/investigations/r4-r8-integration/evidence/silo-version.log b/docs/investigations/r4-r8-integration/evidence/silo-version.log deleted file mode 100644 index 345279356..000000000 --- a/docs/investigations/r4-r8-integration/evidence/silo-version.log +++ /dev/null @@ -1,6 +0,0 @@ -silo version DEVELOPMENT.2026-09-15T16-44-34Z (commit-id=80684fed59f556d579e268c5a855d936c1347b68) -Runtime: go1.27.1 darwin/arm64 -License: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html -Copyright: 2015-2025 MinIO, Inc. -Modifications: Copyright 2025-2026 PGSTY -Source compatibility: based on MinIO technology diff --git a/docs/investigations/r4-r8-integration/evidence/targeted-race.log b/docs/investigations/r4-r8-integration/evidence/targeted-race.log deleted file mode 100644 index aa23bb22f..000000000 --- a/docs/investigations/r4-r8-integration/evidence/targeted-race.log +++ /dev/null @@ -1,3 +0,0 @@ -ok github.com/minio/minio/cmd 13.654s -ok github.com/minio/minio/internal/deadlineconn 2.528s -ok github.com/minio/minio/internal/http 14.301s diff --git a/docs/investigations/r4-r8-integration/evidence/validation-results.json b/docs/investigations/r4-r8-integration/evidence/validation-results.json deleted file mode 100644 index fadd64e5d..000000000 --- a/docs/investigations/r4-r8-integration/evidence/validation-results.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "head": "80684fed59f556d579e268c5a855d936c1347b68", - "baseline": "9f3037e941a49ab4cd8a0eed7c0f01083fbe4bbe", - "source_sha256": { - "cmd/bucket-replication-utils.go": "365641c760901641e8320cc5123697ab92a46f1add612048b991ed2d1cfad43b", - "cmd/bucket-replication.go": "2e766c5946dcabaea79455b50a8e426f404e55c92dcbccbe28d55906e2e43843", - "cmd/common-main.go": "f8777fe8a07d175aceee07b4dd13792b2384449c404c004c38a06893be997843", - "cmd/erasure-object.go": "4bc848685ea714d88cabbd5d1b8585fbcc06f7b19c775e1a811030e783d0e1a4", - "cmd/erasure-server-pool-consistency.go": "d2736ef6bffbb5c5758eba8df38f8d4ecb888a838ab0de8ad3cf015c051f8ad7", - "cmd/erasure-server-pool.go": "87ad0b25dfa3081d0e63d0073b788614a9c88e2498a2ce0956b93f8a0a03ef53", - "cmd/object-handlers-common.go": "101bd7d7447072d13fed50983b69b562e4725632645e623d7fdd490f388ecdec", - "cmd/object-handlers.go": "61897a260f3f5f660f41edcb50956c60e914ef98f9a987da824f16d78171fde2", - "cmd/object-multipart-handlers.go": "d9622c69c540ab32dd23916e3f534b6886473a98370c9dd17673e69a423b2a7e", - "cmd/replication-delete-marker_test.go": "d967787804d558ac6266b113228fdf4a4f9fcb7cab39138a4fb07558814ccca4", - "cmd/replication-delete-mrf_test.go": "6fcfaf505d28f98e42dff9c0d965895be2c2d112e9e996220d424aea1f76d691", - "cmd/replication-delete-operation_test.go": "2e674cab5ca4dbc38cb2c1ddcca117269276e6419e1869c154c3bd6a05676715", - "cmd/replication-tagging-order_test.go": "c8260b4ccf82fa615e1e24b35a07f2d1aacbcf776e5c6f9dadffea4a09ad6ea8", - "cmd/replication-tagging-sender_test.go": "3770a1a48a6efe58fe8127e1e4fdf6bd7cf171e17db20f15222ea2f7b85db1af", - "cmd/server-main.go": "04c265de211412ba0297396928096d7f2d971244a957a3126154846035263514", - "cmd/server_deadline_config_test.go": "a8259d273922a8973b44d9a468791761d373fe265fff8b2b53871e4626b11405", - "internal/deadlineconn/deadlineconn.go": "b9272ef640f1d4403b3d0af6cdbaba9186c51ad9a0226dfe449e8ef738e1ec4b", - "internal/deadlineconn/deadlineconn_strict_test.go": "b4aec28c5daddb36e6ebb98dd8af2a44b7a2c48f0660068534f69669609f67a7", - "internal/http/dial_deadline_linux_test.go": "c4c83bda92ba9bac53166453920456134b3d8265cd59101b4203067c67eca59e", - "internal/http/listener.go": "49628575367f6ab9b6986caf594726d74d370f7d2ac4eed582903600b6eb3fa2", - "internal/http/server.go": "b7b0355f2781f8c5f7c77bc910cd4180cd3e5f22a87de41bd35ef119d36b4cdf", - "internal/http/server_deadline_test.go": "87303cc389bf1cf759898489f06b001073de2dd9c4b3687c4eca14aa186b62ab" - }, - "no_capacity_overlay": true, - "race_test_selection": [ - "TestAPILocalTaggingAlwaysAdvancesRevision", - "TestAPIPoolsTaggingReplicaDeletion", - "TestAPITaggingMultipartCommitRechecksRevision", - "TestAPITaggingReplicationOrdering", - "TestAPITaggingReplicationOrderingKMS", - "TestAPITaggingSSECRotationPreservesDeletionRevision", - "TestAPITaggingUnqualifiedCopyOrdering", - "TestConcurrentStrictReadDeadline", - "TestDefaultReadDeadlineStillRenews", - "TestInternodeDialReadDeadline", - "TestLocalTaggingCommitCannotRegressRevision", - "TestReplicateDeleteMarkerPurge", - "TestReplicateDeleteMarkerTargetSemantics", - "TestReplicateDeleteOperationExits", - "TestReplicateDeletePurgeMissingTargetState", - "TestReplicationDeleteQueueFullRetryBudget", - "TestReplicationMRFMarkerRecovery", - "TestServerBackgroundReadNoDeadline", - "TestServerConnStateHook", - "TestServerContinuousDownload", - "TestServerContinuousUpload", - "TestServerDefaultIdleLongDownload", - "TestServerDefaultIdleLongUpload", - "TestServerEarlyBodyClose", - "TestServerHTTP2Deadlines", - "TestServerHijackedDeadline", - "TestServerIdleBodyDeadline", - "TestServerKeepAliveDeadline", - "TestServerPipelinedDeadline", - "TestServerReadHeaderDeadline", - "TestServerReadHeaderTimeoutConfig", - "TestServerTLSHandshakeReadDeadline", - "TestStrictExpiredFutureReadDeadline", - "TestStrictReadDeadline", - "TestStrictReadDeadlineRepeatedRenewal", - "TestTaggingProductionCopyWireShape", - "TestTaggingRepeatedValueNeedsRevisionDelivery", - "TestTaggingReplicaContentDuplicateGuard", - "TestTaggingReplicationSenderRetryAndAcknowledgment", - "TestTaggingTimestampWire" - ], - "started_at": "2026-09-15T16:45:09.640646+00:00", - "checks": [ - { - "name": "make-verifiers-final", - "command": [ - "make", - "verifiers", - "GOLANGCI=/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/golangci-serial" - ], - "env": { - "GOMAXPROCS": "4", - "GOFLAGS": "-p=2", - "MINIO_API_REQUESTS_MAX": "10000" - }, - "exit_code": 0, - "seconds": 76.933, - "log": "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/make-verifiers-final.log", - "log_sha256": "54c906cff1d33d0148fbc4cac918c0785a3bcc8f7a4eb7e04a2f774c2f010bb4" - }, - { - "name": "make-build", - "command": [ - "make", - "build" - ], - "env": { - "GOMAXPROCS": "4", - "GOFLAGS": "-p=2", - "MINIO_API_REQUESTS_MAX": "10000" - }, - "exit_code": 0, - "seconds": 19.366, - "log": "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/make-build.log", - "log_sha256": "6ba9b545236be964861749c72e7609edf12b8f470df30d1ede8fd62f497e629b" - }, - { - "name": "silo-version", - "command": [ - "./silo", - "--version" - ], - "env": { - "GOMAXPROCS": "4", - "GOFLAGS": "-p=2", - "MINIO_API_REQUESTS_MAX": "10000" - }, - "exit_code": 0, - "seconds": 1.259, - "log": "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/silo-version.log", - "log_sha256": "ada27f2be570c33df5712e86782a7be2ce3acfef54c8bb22a9230db3606513af" - }, - { - "name": "full-tests", - "command": [ - "go", - "test", - "-p", - "2", - "./cmd", - "./internal/...", - "-count=1", - "-timeout=30m" - ], - "env": { - "GOMAXPROCS": "4", - "GOFLAGS": "-p=2", - "MINIO_API_REQUESTS_MAX": "10000", - "CGO_ENABLED": "0" - }, - "exit_code": 0, - "seconds": 340.193, - "log": "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/full-tests.log", - "log_sha256": "24b986e2f5aef0e668116abaab5024bf19ac664aec010ca7aa5ef56886f47ad1" - }, - { - "name": "targeted-race", - "command": [ - "go", - "test", - "-race", - "-p", - "2", - "./cmd", - "./internal/deadlineconn", - "./internal/http", - "-run", - "^(TestAPILocalTaggingAlwaysAdvancesRevision|TestAPIPoolsTaggingReplicaDeletion|TestAPITaggingMultipartCommitRechecksRevision|TestAPITaggingReplicationOrdering|TestAPITaggingReplicationOrderingKMS|TestAPITaggingSSECRotationPreservesDeletionRevision|TestAPITaggingUnqualifiedCopyOrdering|TestConcurrentStrictReadDeadline|TestDefaultReadDeadlineStillRenews|TestInternodeDialReadDeadline|TestLocalTaggingCommitCannotRegressRevision|TestReplicateDeleteMarkerPurge|TestReplicateDeleteMarkerTargetSemantics|TestReplicateDeleteOperationExits|TestReplicateDeletePurgeMissingTargetState|TestReplicationDeleteQueueFullRetryBudget|TestReplicationMRFMarkerRecovery|TestServerBackgroundReadNoDeadline|TestServerConnStateHook|TestServerContinuousDownload|TestServerContinuousUpload|TestServerDefaultIdleLongDownload|TestServerDefaultIdleLongUpload|TestServerEarlyBodyClose|TestServerHTTP2Deadlines|TestServerHijackedDeadline|TestServerIdleBodyDeadline|TestServerKeepAliveDeadline|TestServerPipelinedDeadline|TestServerReadHeaderDeadline|TestServerReadHeaderTimeoutConfig|TestServerTLSHandshakeReadDeadline|TestStrictExpiredFutureReadDeadline|TestStrictReadDeadline|TestStrictReadDeadlineRepeatedRenewal|TestTaggingProductionCopyWireShape|TestTaggingRepeatedValueNeedsRevisionDelivery|TestTaggingReplicaContentDuplicateGuard|TestTaggingReplicationSenderRetryAndAcknowledgment|TestTaggingTimestampWire)$", - "-count=1", - "-timeout=15m" - ], - "env": { - "GOMAXPROCS": "4", - "GOFLAGS": "-p=2", - "MINIO_API_REQUESTS_MAX": "10000", - "CGO_ENABLED": "1" - }, - "exit_code": 0, - "seconds": 46.286, - "log": "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/targeted-race.log", - "log_sha256": "7d021e57e513ea81617df44a6253703622145bf5e72e6180de84c2bd3c7186d3" - } - ], - "source_unchanged": true, - "finished_at": "2026-09-15T16:53:13.682876+00:00" -} diff --git a/docs/investigations/r4-r8-integration/manifest.json b/docs/investigations/r4-r8-integration/manifest.json deleted file mode 100644 index cdf0b950f..000000000 --- a/docs/investigations/r4-r8-integration/manifest.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "baseline": "9f3037e941a49ab4cd8a0eed7c0f01083fbe4bbe", - "reviewed_code_commit": "055030ea53ca92ee22ce1e601ef4757c247edde8", - "tested_commit": "80684fed59f556d579e268c5a855d936c1347b68", - "integration_branch": "codex/merge-r4-r8", - "current_source_matches_tested_commit": true, - "production_or_test_body_changes_after_review": false, - "source_sha256": { - "cmd/bucket-replication-utils.go": "365641c760901641e8320cc5123697ab92a46f1add612048b991ed2d1cfad43b", - "cmd/bucket-replication.go": "2e766c5946dcabaea79455b50a8e426f404e55c92dcbccbe28d55906e2e43843", - "cmd/common-main.go": "f8777fe8a07d175aceee07b4dd13792b2384449c404c004c38a06893be997843", - "cmd/erasure-object.go": "4bc848685ea714d88cabbd5d1b8585fbcc06f7b19c775e1a811030e783d0e1a4", - "cmd/erasure-server-pool-consistency.go": "d2736ef6bffbb5c5758eba8df38f8d4ecb888a838ab0de8ad3cf015c051f8ad7", - "cmd/erasure-server-pool.go": "87ad0b25dfa3081d0e63d0073b788614a9c88e2498a2ce0956b93f8a0a03ef53", - "cmd/object-handlers-common.go": "101bd7d7447072d13fed50983b69b562e4725632645e623d7fdd490f388ecdec", - "cmd/object-handlers.go": "61897a260f3f5f660f41edcb50956c60e914ef98f9a987da824f16d78171fde2", - "cmd/object-multipart-handlers.go": "d9622c69c540ab32dd23916e3f534b6886473a98370c9dd17673e69a423b2a7e", - "cmd/replication-delete-marker_test.go": "d967787804d558ac6266b113228fdf4a4f9fcb7cab39138a4fb07558814ccca4", - "cmd/replication-delete-mrf_test.go": "6fcfaf505d28f98e42dff9c0d965895be2c2d112e9e996220d424aea1f76d691", - "cmd/replication-delete-operation_test.go": "2e674cab5ca4dbc38cb2c1ddcca117269276e6419e1869c154c3bd6a05676715", - "cmd/replication-tagging-order_test.go": "c8260b4ccf82fa615e1e24b35a07f2d1aacbcf776e5c6f9dadffea4a09ad6ea8", - "cmd/replication-tagging-sender_test.go": "3770a1a48a6efe58fe8127e1e4fdf6bd7cf171e17db20f15222ea2f7b85db1af", - "cmd/server-main.go": "04c265de211412ba0297396928096d7f2d971244a957a3126154846035263514", - "cmd/server_deadline_config_test.go": "a8259d273922a8973b44d9a468791761d373fe265fff8b2b53871e4626b11405", - "internal/deadlineconn/deadlineconn.go": "b9272ef640f1d4403b3d0af6cdbaba9186c51ad9a0226dfe449e8ef738e1ec4b", - "internal/deadlineconn/deadlineconn_strict_test.go": "b4aec28c5daddb36e6ebb98dd8af2a44b7a2c48f0660068534f69669609f67a7", - "internal/http/dial_deadline_linux_test.go": "c4c83bda92ba9bac53166453920456134b3d8265cd59101b4203067c67eca59e", - "internal/http/listener.go": "49628575367f6ab9b6986caf594726d74d370f7d2ac4eed582903600b6eb3fa2", - "internal/http/server.go": "b7b0355f2781f8c5f7c77bc910cd4180cd3e5f22a87de41bd35ef119d36b4cdf", - "internal/http/server_deadline_test.go": "87303cc389bf1cf759898489f06b001073de2dd9c4b3687c4eca14aa186b62ab" - }, - "compatibility_inventory_sha256": "208c78a9e9fc98d6de7f1e0f03cfec97d8491df65ad4de3d88f04c36c555f819", - "evidence_sha256": { - "evidence/full-tests.log": "24b986e2f5aef0e668116abaab5024bf19ac664aec010ca7aa5ef56886f47ad1", - "evidence/header-equivalence.json": "0aee56aa978515579aa59215152b685f614cd5bda323fe26690a4c21f837f109", - "evidence/integration-equivalence.json": "473711fa8660504275242b70e80622277a32c73e0c7df1de6f5ba2d6bbfcc54e", - "evidence/isolated-pools-before.log": "f014a1a72dda7b973cd1e0b7e77c70b470eeebca0d04805d98f07c2622c87b1e", - "evidence/isolated-pools-before.result.json": "3a761d8a6fd1869a9a9d2b3d506ddec4fdaaad098da2b6a2f85252acf9561774", - "evidence/make-build.log": "6ba9b545236be964861749c72e7609edf12b8f470df30d1ede8fd62f497e629b", - "evidence/make-verifiers-final.log": "54c906cff1d33d0148fbc4cac918c0785a3bcc8f7a4eb7e04a2f774c2f010bb4", - "evidence/opus-integration.metadata.json": "c468e952e2364625ffe04a7221489185705857ad073f6fdb8dee3fe6aad9345c", - "evidence/opus-integration.prompt.md": "f95edcf71afedceab190ff57bfbec812b06c8c18b5370d887131a760228c2b2c", - "evidence/opus-review.md": "463f6b97e8d19929187242724e6bcfc2406217cd82702739716b65e19a8f2360", - "evidence/runtime-probe.json": "e493d9757c130c2531072dc0eaee42b8fc1fa0f45fc43d0d0e098aac55f0d387", - "evidence/runtime-probe.result.json": "d8703c201493cf865d758d53cbc6d92b71535345dafe3c2f27ca2a29ad53e353", - "evidence/silo-version.log": "ada27f2be570c33df5712e86782a7be2ce3acfef54c8bb22a9230db3606513af", - "evidence/targeted-race.log": "7d021e57e513ea81617df44a6253703622145bf5e72e6180de84c2bd3c7186d3", - "evidence/validation-results.json": "29182a752a8ffb75367e8cce51e1f2ef080e453a059248ae2c868aa66627aede" - }, - "original_raw_review": { - "path": "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/opus-integration.jsonl", - "sha256": "3e1a45ebdf3f4420fb05647bb383c00d0a86452a6c357a92679485c99d8f4eb3" - }, - "original_review_diff": { - "path": "/Users/vonng/tmp/silo-r4-r8-main-20260916-01a0a5ab/integration-code.diff", - "sha256": "d8c4e60f9e4a5b1338f3e6e07b758b1bb279db80eec26847e3b35fde0d049485" - }, - "scope": "Local integration validation and independent source review. Final PR checks and remote merge are recorded separately." -} diff --git a/docs/investigations/r4/consensus.md b/docs/investigations/r4/consensus.md deleted file mode 100644 index 7fe4e4222..000000000 --- a/docs/investigations/r4/consensus.md +++ /dev/null @@ -1,33 +0,0 @@ -# R4 plan consensus and review disposition - -## Agreed version - -- Plan: [plan v1](plan-v1.md), SHA-256 `ad539f2071155de6955b583991684ed33c4bfe2e29660005840cdc97d7e1a754`. The frozen file remains unchanged. -- Baseline: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`. -- Actual reviewer: Claude Code 2.1.270, every assistant model in the review stream is `claude-opus-5`; explicit `--effort max`. -- Opus: **GO_WITH_NONBLOCKING_NOTES**, zero blockers; explicitly agrees that this exact plan can enter local implementation. [Unedited returned review](opus-v1-review.md), [machine-readable provenance](opus-v1.metadata.json). -- Codex: agrees that adding the already-parsed timestamp to the KMS literal fixes R4, and accepts the nonblocking dispositions below. **No blocking disagreement remains on plan v1.** No production source edits were made before this record was saved. -- The agreement permits the planned local implementation and tests; it is not implementation acceptance, a merge decision or production release approval. - -## Item-by-item disposition - -| Opus ID | Disposition | -|---|---| -| R4-01 | Accepted citation correction here, leaving the agreed hash frozen: `ReplicaLockReconcile` is at baseline `object-handlers.go:1847`; encryption merge is at `:1903`. | -| R4-02 | Accepted scope clarification: ErasureSD and Erasure16 are both single-pool local backends. KMS rewrites use PutObject under-lock reconciliation. Multi-pool and multi-site validation are optional and deferred to the wider integration gate. Test comments and the final report will identify this boundary. | -| R4-03 | Accepted wording clarification: source encryption alone does not request destination encryption. Source-only SSE-C copy headers do not prevent destination bucket/default auto-KMS from selecting KMS. The three destination trigger categories stay unchanged. | -| R4-04 | Accepted intent. The regression matrix uses identical expected mtime, ETag, trust and all three source timestamps across all encryption modes, giving field-by-field equivalence without constructing expected values through the production function. The temporary expanded baseline matrix fails only trusted valid KMS tag timestamps. | -| R4-05 | Accepted optional test within the existing scope: a signed KMS COPY with nonempty tags and no source tag timestamp must preserve the stored value/time. This adds evidence, not production behavior. | -| R4-06 | Registered as a separate unverified-impact finding: KMS construction also omits `ProxyHeaderSet`, `ProxyRequest`, `Speedtest` relative to `getDefaultOpts`. No R4 fix or correctness claim for those flags. Send the observation to the parent for separate triage; do not assign it to R5. | -| R4-07 | Accepted. Assertions target final disk state; the REPLACE handler rebuilds metadata, while final stored-tag rejection occurs under the storage write lock. HTTP 200 alone is not acceptance. | -| R4-08 | Resolved provenance uncertainty by Codex: SHA-256 recomputed before/after review, baseline identity and current GitHub main/PR query captured in `baseline-identity.txt`. History was inspected locally with `git blame` / `git show`. Opus's read-only tools did not independently recompute the hash or check GitHub; those facts remain attributed to the local commands. | - -## Raw evidence - -Directory: `/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/`. - -- `review-prompt-v1.md`, `opus-review-v1.jsonl`, `opus-review-v1.stderr.log`, `opus-review-v1.exit`. -- `baseline-identity.txt`, `r4_repro_test.go`, `overlay.json`, `baseline-repro.log`. -- `options_repro_test.go`, `options-overlay.json`, `baseline-options.log`. - -The stream includes an attempted Write to Claude's own plan file. Its tool was disabled; the reviewer returned the full result in text and did not edit production source. The successful result and actual assistant models are checked separately from rate-limit status and auxiliary-model usage. diff --git a/docs/investigations/r4/implementation-review.md b/docs/investigations/r4/implementation-review.md deleted file mode 100644 index 67f5e7bae..000000000 --- a/docs/investigations/r4/implementation-review.md +++ /dev/null @@ -1,39 +0,0 @@ -已完成独立复核:读了三份改动文件在 HEAD 的实际内容、`putOptsFromHeaders` 全函数与两条返回路径、`CopyObjectHandler` 的默认加密与标签排序段、`reconcileStoredObjectTags` 的三处生产调用点,以及全部指定日志与证据文件。 - -# R4 实现复核结论 - -**Verdict: GO_WITH_NONBLOCKING_NOTES(0 阻断项)** - -- 复核 HEAD:`dbcf8dec589deb5d91e17d295cb70997635f5b55` -- 代码/测试 diff SHA-256(按提供值记录):`c8cd6648f8ecea835ec74a038cdeaa82acaa3f36250395f97ead3260dc2fc0a5` - - 本会话无 shell,未重算该哈希;改为逐行比对 diff 与工作树三份文件,内容完全一致(`cmd/object-api-options.go`、`cmd/object-api-options-replication_test.go`、`cmd/object-copy-replication-tagging_test.go`)。 - -## 核验到的事实 - -- 生产改动确为一个字段 + 相邻注释:`cmd/object-api-options.go:459` 的 `ReplicationSourceTaggingTimestamp: taggingtimestmp`,变量来自 `:419-425` 已解析值,与非 KMS 路径 `:473` 对齐。未动解析、信任判定、KMS key/context、返回结构。 -- 影响面封闭:全仓该字段唯一消费点是 `cmd/object-handlers.go:1820`(COPY 标签排序)。PUT/POST/multipart 虽同经 `putOptsFromReq`,但无消费者,故不可能回归——与 R4/R5 切分一致。 -- 三条 KMS 触发路径真实可达:`object-handlers.go:1428-1433` 在 `copyDstOpts`(`:1454`)之前套用目的端默认;`bucket-sse-config.go:139-151` 在 `nil 配置 + AutoEncrypt` 与桶默认 KMS 两种情况下都写入 `aws:kms`,因此 explicit / auto / bucket 三种模式均进入 KMS 分支。 -- 回归证明成立:`baseline-final.log` 用 `-overlay` 换回未修复 constructor,失败面精确为「trusted × 有效标签时间戳 × SSE-KMS / SSE-KMS-context」和 6 个 KMS COPY 子测试(`tags="key=old"`、`kms=true`、HTTP 200),`none`/`SSE-S3`/`SSE-C`/非 trusted 全通过。修复后 `focused.log:194-204` 全 PASS。 -- 测试确实覆盖被要求的维度:信任边界(trusted=false 时 mtime/ETag/三时间戳全归零)、错误路径(trusted + 畸形值必须报错且错误串含头名)、SSE 序列化回环(KMS keyID/context 原样还原)、磁盘终态(每事件 `obj.GetObjectInfo` 读真实盘)、版本一致性、签名 GET 明文可读。全局 `GlobalKMS`/`globalAutoEncryption`/`set.getDisks` 均 defer 还原。 -- `race` exit 0、`vet` 空输出、`golangci-lint` 0 issues,均记录了与 HEAD 一致的三文件哈希。 -- 未发现 `verification.md` / `verification.json` / `consensus.md` 中与日志矛盾的陈述。(评审者版本/模型/effort 这类 provenance 声明不在我可验证范围,未作背书。) - -## 发现清单 - -| ID | 内容 | 阻断 | -|---|---|---| -| IMPL-01 | 单字段修复正确且充分,位置、变量、注释与 `:473` 语义一致 | 否(确认项) | -| IMPL-02 | 基线失败/修复通过的判别力成立,对照组不误报 | 否(确认项) | -| IMPL-03 | KMS 字面量相对 `getDefaultOpts` 仍缺 `ProxyHeaderSet`/`ProxyRequest`/`Speedtest`(`object-api-options.go:40-44` vs `:449-460`)。R4 范围外,已登记为 R4-06 | 否,不设为新合并门槛 | -| IMPL-04 | `metadata-directive: REPLACE` 下 `getCpObjMetadataFromHeader`(`:1143-1156`)返回全新 map,故 `:1818` 的 `lastTaggingTimestamp` 为空、`:1822` 解析失败使 handler 侧比较恒「incoming 胜」;真正的 stale 拒绝发生在写锁内的 `reconcileStoredObjectTags`(`erasure-object.go:1312-1315`)。测试终态断言仍正确,文档 R4-07 已明示此分工 | 否(R5 上下文) | -| IMPL-05 | 测试卫生:`bucket-kms` 模式写入的 `bucketSSEConfig` 未还原,仅因它是最后一个 mode、且 `ExecObjectLayerAPITest` 每后端重建对象层并 `resetTestGlobals()` 才安全;后续若在其后追加 mode 会继承默认 KMS | 否 | -| IMPL-06 | `object-api-options-replication_test.go:35` 局部变量名 `context` 遮蔽标准包名(本文件未导入该包),纯观感 | 否 | -| IMPL-07 | `focused.log` exit 1 的唯一失败是既有 `TestAPICopyObjectReplicaRetentionRemovalUnderBucketKMS`(`replication-trust_test.go:1284`,"Storage reached its minimum free drive threshold"),属本机磁盘余量环境问题,非本次引入;容量 overlay 是测试专用、未提交。新增 COPY 测试自带 `tagTestCapacityDisk` 包装,不受该阈值影响 | 否 | - -**没有发现阻断性正确性问题。** 生产语义、存储格式、API 与既有排序规则均未改变,无任何既有测试断言旧(缺陷)行为。 - -## 合并适配性 - -`dbcf8dec5` 直接位于实时 main `9ebe81c1b` 之上,可快进合并。按仓库 CI 通过为前提,本实现适合合入 main。 - -*(我未运行任何测试,也未查询 GitHub;以上仅基于源码阅读与所提供日志。)* diff --git a/docs/investigations/r4/implementation-review.metadata.json b/docs/investigations/r4/implementation-review.metadata.json deleted file mode 100644 index f81bef75e..000000000 --- a/docs/investigations/r4/implementation-review.metadata.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "reviewed_head": "dbcf8dec589deb5d91e17d295cb70997635f5b55", - "requested_model": "claude-opus-5", - "requested_effort": "max", - "cli_version": "2.1.270", - "diff_sha256": "c8cd6648f8ecea835ec74a038cdeaa82acaa3f36250395f97ead3260dc2fc0a5", - "started_at": "2026-09-15T15:59:41.004963+00:00", - "status": "completed", - "command": "/opt/homebrew/bin/claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --add-dir /Users/vonng/tmp/silo-r4-evidence-20260915-a9cb --output-format stream-json --verbose", - "completed_at": "2026-09-15T16:03:52.331640+00:00", - "assistant_models": [ - "claude-opus-5" - ], - "observed_model": "claude-opus-5", - "verdict": "GO_WITH_NONBLOCKING_NOTES", - "blocking_findings": 0, - "session_id": "c09bc4fb-85f1-4af8-a1de-c453398e4a20", - "duration_ms": 161469, - "subtype": "success", - "is_error": false, - "used_tools": { - "Read": 20, - "Glob": 3, - "Grep": 14, - "ExitPlanMode": 1 - }, - "raw_stream": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/merge-review-1/opus.jsonl", - "stream_sha256": "875f617454b27e15ab44d9b777de89643ee352e0ccb948faad570fc546261acc", - "review_sha256": "962ff88d2ffcb75cd692ec17017411d624de80dc120f8dedc675e0fe25009335", - "prompt_sha256": "e1341c721161229431b943ba18d89b740e94470803c099b9ae3d597fd50544a4", - "review_extraction": "The substantive review is an earlier assistant text block; result.result only repeats CLI plan-mode merge limitations. Full raw stream and all assistant text are retained." -} diff --git a/docs/investigations/r4/merge-verification.json b/docs/investigations/r4/merge-verification.json deleted file mode 100644 index 51deb4a3e..000000000 --- a/docs/investigations/r4/merge-verification.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "original_reviewed_head": "dbcf8dec589deb5d91e17d295cb70997635f5b55", - "dco_signed_equivalent_head": "03027727d1d1b97d8beb83ac55569ea9a83dab23", - "notice_equivalence": { - "cmd/object-api-options-replication_test.go": { - "before_sha256": "1ea2a060987e32a4c76fce96ee974df475944c2d6ab482a4893e33daf7bca849", - "after_sha256": "c21fc8889a079085d9a882499a1cbe868278a3517580651f3bed1102e2a6aef8", - "package_body_sha256": "096f143c0b0a068581f9bb892f35ded0d65b6b60ab711f043236d27fbf51ca33", - "body_unchanged": true - }, - "cmd/object-copy-replication-tagging_test.go": { - "before_sha256": "5437a77e68736b4ce69de9c777675251fef24b0352dfe30bd8a836fc7ee810e3", - "after_sha256": "73f066ed7258d430f078ecc90e551ece878bd3d4672bc762094d434ff8fec23d", - "package_body_sha256": "6b5173db2ded2d54055073c3259be208a4d7c8eac0367687082877f1fd3bef15", - "body_unchanged": true - } - }, - "checks": { - "verifiers": { - "command": [ - "make", - "verifiers", - "GOLANGCI=/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/merge-review-1/golangci-serial" - ], - "exit_code": 0, - "started_at": "2026-09-15T16:04:55.536605+00:00", - "finished_at": "2026-09-15T16:07:03.252820+00:00", - "cwd": "/Users/vonng/.codex/worktrees/a9cb/silo", - "env_override": { - "GOMAXPROCS": "2", - "GOFLAGS": "-p=2" - }, - "source_sha256": { - "cmd/object-api-options.go": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "cmd/object-api-options-replication_test.go": "c21fc8889a079085d9a882499a1cbe868278a3517580651f3bed1102e2a6aef8", - "cmd/object-copy-replication-tagging_test.go": "73f066ed7258d430f078ecc90e551ece878bd3d4672bc762094d434ff8fec23d" - }, - "log_sha256": "e42a5bb55f5c1ebfcf02cebebf6d82cf1ec5a2d74590cdf838deba16dd80bfdf" - }, - "build": { - "command": [ - "make", - "build" - ], - "exit_code": 0, - "started_at": "2026-09-15T16:07:03.253715+00:00", - "finished_at": "2026-09-15T16:07:35.594062+00:00", - "cwd": "/Users/vonng/.codex/worktrees/a9cb/silo", - "env_override": { - "GOMAXPROCS": "2", - "GOFLAGS": "-p=2" - }, - "source_sha256": { - "cmd/object-api-options.go": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "cmd/object-api-options-replication_test.go": "c21fc8889a079085d9a882499a1cbe868278a3517580651f3bed1102e2a6aef8", - "cmd/object-copy-replication-tagging_test.go": "73f066ed7258d430f078ecc90e551ece878bd3d4672bc762094d434ff8fec23d" - }, - "log_sha256": "6ba9b545236be964861749c72e7609edf12b8f470df30d1ede8fd62f497e629b" - }, - "binary-version": { - "command": [ - "./silo", - "--version" - ], - "exit_code": 0, - "started_at": "2026-09-15T16:07:35.594918+00:00", - "finished_at": "2026-09-15T16:07:37.616706+00:00", - "cwd": "/Users/vonng/.codex/worktrees/a9cb/silo", - "env_override": { - "GOMAXPROCS": "2", - "GOFLAGS": "-p=2" - }, - "source_sha256": { - "cmd/object-api-options.go": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "cmd/object-api-options-replication_test.go": "c21fc8889a079085d9a882499a1cbe868278a3517580651f3bed1102e2a6aef8", - "cmd/object-copy-replication-tagging_test.go": "73f066ed7258d430f078ecc90e551ece878bd3d4672bc762094d434ff8fec23d" - }, - "log_sha256": "36317d06b691593fe0d74f88d053a24485500c15fc2001e857f2fc6fa5ba752a" - } - }, - "binary_version": "silo version DEVELOPMENT.2026-09-15T16-03-52Z (commit-id=03027727d1d1b97d8beb83ac55569ea9a83dab23)\nRuntime: go1.27.1 darwin/arm64\nLicense: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html\nCopyright: 2015-2025 MinIO, Inc.\nModifications: Copyright 2025-2026 PGSTY\nSource compatibility: based on MinIO technology\n", - "raw_evidence_directory": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/merge-review-1", - "all_function_and_test_bodies_identical_to_opus_reviewed_version": true -} diff --git a/docs/investigations/r4/merge-verification.md b/docs/investigations/r4/merge-verification.md deleted file mode 100644 index 2cc353368..000000000 --- a/docs/investigations/r4/merge-verification.md +++ /dev/null @@ -1,36 +0,0 @@ -# R4 合并前复核 - -用户已明确追加授权:使用 Opus 5 max 核实最终实现,确认无误后合并 main。本轮授权取代此前只交付本地补丁的范围限制。 - -## 真实实现评审 - -- 独立新调用:Claude Code 2.1.270,`--model claude-opus-5 --effort max`。 -- 复核代码提交:`dbcf8dec589deb5d91e17d295cb70997635f5b55`;当时实时 main 与 fetch 结果均为 `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`。 -- 实际 assistant 模型只有 `claude-opus-5`。结论 **GO_WITH_NONBLOCKING_NOTES,0 阻断项**,明确表示仓库 CI 通过后适合合入 main。 -- [原始实现评审正文](implementation-review.md)、[实际模型与输出哈希](implementation-review.metadata.json) 已保存。 -- 原始流、全部 assistant 正文与最终 result 位于 `/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/merge-review-1/`。实质评审出现在较早的 assistant 消息;最终 result 只重复 Claude 只读会话不能自行合并的工具限制,不是对修复结论的撤回。本任务由 Codex 按用户明确授权完成合并。 - -## 意见处置 - -| 条目 | 处置 | -|---|---| -| IMPL-01 / IMPL-02 | 确认单字段修复和基线失败/修复通过的测试判别力,无需追加修改。 | -| IMPL-03 | Proxy/Speedtest 选项遗漏已交父任务单独核验,维持范围外,不纳入 R4 合并。 | -| IMPL-04 | REPLACE 请求的旧标签拒绝由写锁内对账完成,测试断言真实落盘状态,已有文档准确说明。 | -| IMPL-05 | 当前测试固定以 bucket-kms 为最后一种模式,且每后端重新初始化;现有执行顺序安全。后续增添模式需同步隔离桶默认配置,本次保持已评审测试逻辑。 | -| IMPL-06 | 局部变量 context 命名建议为可选观感项,不改动已评审逻辑。 | -| IMPL-07 | 既有锁测试的磁盘余量限制及仅测试容量 overlay 已如实记录;新测试和 race 不使用生产代码 overlay。 | - -## 提交规范调整 - -按 `CONTRIBUTING.md` 补齐提交作者对应的 DCO sign-off,并将两个新原创测试文件的文件头改为 `Copyright (c) 2026 Feng Ruohang`,保留 AGPL-3.0-or-later。原有生产文件的继承声明保持原样。 - -生产函数和测试的 `package cmd` 之后内容与 Opus 审查版本逐字节相同。`merge-review-1/notice-equivalence.json` 记录了旧/新文件哈希及不变的代码正文哈希。原 `verification.json` 保留当时原始验证记录,不覆盖历史哈希;本轮 PR 的 CI 对最终提交重新验证。 - -## 合并门槛 - -`make verifiers` 已通过:全仓 lint 为 0 issues,生成文件检查通过,rebrand 兼容性清单未变化,交付/运行时标识检查和 entrypoint 参数兼容性测试通过。首次执行曾遇到其他任务持有 golangci-lint 进程锁;使用工具自带 `--allow-serial-runners` 串行等待后完成全部检查。可选 typos 工具未安装,由仓库 Makefile 按既有规则跳过。 - -`make build` 通过,已生成本地 `silo` 并成功执行 `./silo --version`。最终三个源文件哈希与本轮校验记录一致,详情见 [本轮验证清单](merge-verification.json)。 - -接下来由 PR CI 验证最终候选,并在合并前再次核对 main 和精确 PR head。CI 与合并事实以 GitHub PR 状态和本机原始合并证据为准,评审意见不等同于合并或发布。 diff --git a/docs/investigations/r4/opus-v1-review.md b/docs/investigations/r4/opus-v1-review.md deleted file mode 100644 index 070def5dd..000000000 --- a/docs/investigations/r4/opus-v1-review.md +++ /dev/null @@ -1,46 +0,0 @@ -## R4 独立评审(Opus 5.0,只读复核) - -**计划**:plan v1 — `docs/investigations/r4/plan-v1.md` -**哈希(按任务给定)**:`ad539f2071155de6955b583991684ed33c4bfe2e29660005840cdc97d7e1a754` -**Baseline**:`9ebe81c1b3611f9cc73e676b5b741c2be62c467a` - -### 裁定:GO_WITH_NONBLOCKING_NOTES - -无阻断项。**我同意这份 exact plan(上述哈希)可以进入本地实现。** 下列 R4-01…R4-08 均为非阻断;其中 R4-02/04/05 的测试建议是**可选**的,不自动并入必做范围。 - -(说明:本会话 Write 工具被禁用,评审仅以正文返回,未写入任何文件,也未改动任何源码。) - -### 我实际核验到的关键事实(支撑"单字段补丁正确且充分") - -1. **缺陷确认**:`cmd/object-api-options.go:449-460` 的 KMS 字面量带了 MTime/PreserveETag/ReplicationRequest + 两个 Object Lock 时间戳,独缺 tagging;默认路径 `:473` 有。补丁片段中的变量名 `taggingtimestmp` 与 `:419` 完全一致,可直接编译;gofmt 对齐由更长的两个 Lock 键决定,不会扰动他行。 -2. **影响面封闭**:全仓 `ReplicationSourceTaggingTimestamp` 只在 `cmd/object-handlers.go:1820` 被读取(定义于 `object-api-interface.go:99`)。因此该字段对 PUT/分段路径天然无效果——既印证 R4/R5 的切分合理,也说明补丁不可能回归其他路径。 -3. **充分性的关键点(我重点查证的风险)**:`encMetadata` 只有在 SSE-C 轮换分支 `object-handlers.go:1648-1659` 才批量快照全部保留键,而该分支与 KMS options 分支互斥(目的端是 SSE-C 时 `crypto.S3KMS.IsRequested` 为假)。故 `:1903` 的 `maps.Copy(srcInfo.UserDefined, encMetadata)` **不会**覆盖 KMS COPY 新写入的 tags/时间戳 —— 单字段补丁在 R4 边界内充分。 -4. **三个触发点准确**:`bucket-sse-config.go:135-153`(显式请求优先 → nil 配置 + AutoEncrypt → KMS → bucket 默认 KMS 写 header+keyID;默认 AES 走 AES 分支),配合 `object-handlers.go:1428-1433` 仅在非联邦时套用目的端默认。 -5. **REPLACE 副本路径准确**:`reconcileStoredObjectTags`(`erasure-server-pool-consistency.go:232-243`)语义即"存量有效时间戳胜过缺失/更旧/相等的 incoming,并连同 tag 值一起还原"。KMS 目的端因 `isTargetEncrypted` 使 `metadataOnly=false`,实际落到 `erasure-server-pool.go:1499-1513`(`ReplicaLockReconcile` 经 `:1509` 透传)→ `erasure-object.go:1276-1316`,在 `cloneMSS`(:1324) 之前于写锁内完成对账;纯元数据路径走 `erasure-object.go:136-139`。计划同时引用 `:136` 与 `:1509`,判断正确。 -6. **证据可信**:`baseline-repro.log` 中 options 用例非 KMS 保留 `...123456789Z`、KMS 返回零值;COPY 用例 6/6(ErasureSD + Erasure16 × explicit/auto/bucket KMS)失败,且均为 200、`kms=true`、明文 GET 通过、tags 停在 `key=old`。即"请求成功、加密正常,但复制标签被静默丢弃",与计划表述一致,未夸大。 -7. **修复后推演**:newer/stale/duplicate/newer-again 在 handler(:1817-1833) 与写锁对账的双重排序下分别得到 new/new/new/latest,与测试期望吻合;旧发送端不带 `X-Minio-Source-Tagging-Timestamp` 时仍为零值 → 行为不变,兼容性主张成立。 - -### 问题清单 - -| ID | 阻断 | 内容与建议 | -|---|---|---| -| **R4-01** | 否 | 行号漂移:计划写的 `1851/1910`,实际是 `object-handlers.go:1847`(`ReplicaLockReconcile`)与 `:1903`(encMetadata merge)。建议更正引用。 | -| **R4-02** | 否(建议可选) | `ExecObjectLayerAPITest` 两种后端均为**单 pool**(`test-utils_test.go:216` `mustGetPoolEndpoints(0, ...)`),故 `erasure-server-pool.go:1443` 多池分支未被覆盖;且 KMS 目的端命中的是 PutObject 重写对账而非 `CopyObject:136`。建议在计划或测试注释中点明"单盘/16 盘均为单池";补多池覆盖**可选**,不必进必做范围。 | -| **R4-03** | 否 | 措辞:`crypto.Requested`(`internal/crypto/sse.go:74`)只检查**目的端** SSE 头,因此仅带 SSE-C *copy-source* 头的请求在 KMS 默认桶/自动加密下仍会进入 KMS 分支(归入触发点 2/3,枚举仍完整)。建议澄清 "source encryption alone…" 一句。 | -| **R4-04** | 否(**可选**) | 建议在 options 矩阵里加一条 KMS 分支 vs 默认分支的**逐字段等价断言**(MTime/PreserveETag/ReplicationRequest/三个复制时间戳)。这是阻止第三次复发最廉价的护栏(2021 漏、2026 补了两个 Lock 时间戳仍漏此项)。计划第 1 条已基本覆盖,此为结构化建议。 | -| **R4-05** | 否(**可选**) | 建议加一例"KMS 目的端 + 有 tags 但无 tagging 时间戳头 → 存量不变",把兼容性主张钉在 handler 层而不仅在 options 层。 | -| **R4-06** | 否(范围外,仅登记) | 同一 KMS 字面量相对 `getDefaultOpts`(`object-api-options.go:40-44`) 还遗漏 `ProxyHeaderSet/ProxyRequest/Speedtest`;`opts.Speedtest` 在 `erasure-object.go:1625` 被读取,全局自动加密下 speedtest PUT 会丢该标志。**不要在 R4 修**,且当前也不在 R5 声明范围内,建议单列条目登记。 | -| **R4-07** | 否 | REPLACE 时 `getCpObjMetadataFromHeader:1143-1156` 会重建 map,`lastTaggingTimestamp` 为空 → handler 对 stale 事件**恒接受**,真正的拒绝来自写锁内对账。因此回归测试必须断言**最终落盘状态**(现有复现已如此),不要改为断言 handler 层行为。 | -| **R4-08** | 否(不确定性) | 本会话无 shell,无法独立复算计划 SHA-256、验证 `c4373ef290 / b2dca43fda / cfefc049c` 历史归属与 PR #184/#187。可由 `shasum -a 256 docs/investigations/r4/plan-v1.md` 与 `git log -L` 输出消解;均不影响补丁正确性。`cfefc049c` 的 KMS context 编码修复实体(`:436-444` 的 `sdkContext`)仍在,补丁不触碰。 | - -### 对计划各主张的逐项裁定 - -- 单字段补丁**正确且对本 bounded issue 充分**:同意(依据 2/3/7)。 -- 三个目的端 KMS 触发点**描述准确**:同意(R4-03 仅措辞澄清)。 -- 当前 REPLACE 副本路径**描述准确**:同意(R4-01/02 属引用精度)。 -- 回归矩阵与存量状态说明**充分**:同意;存量部分"不自动回填、丢失源时间不可重建、并列/更旧事件不保证修复"的表述与 `reconcileStoredObjectTags` 实际语义一致。 -- 信任边界、错误行为、加密 key/context、tie 语义、兼容性:补丁均未触碰,维持不变。 - -### 交付 - -本轮为**计划共识**,非实现验收。我未作任何源码或文件修改;R4 可按 plan v1 在本地实施,实施后的差异与测试证据需另行验收。 diff --git a/docs/investigations/r4/opus-v1.metadata.json b/docs/investigations/r4/opus-v1.metadata.json deleted file mode 100644 index 76ffb154c..000000000 --- a/docs/investigations/r4/opus-v1.metadata.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "plan": "docs/investigations/r4/plan-v1.md", - "plan_sha256": "ad539f2071155de6955b583991684ed33c4bfe2e29660005840cdc97d7e1a754", - "requested_model": "claude-opus-5", - "requested_effort": "max", - "cli_version": "2.1.270", - "started_at": "2026-09-15T15:45:46.438630+00:00", - "status": "completed", - "raw_output": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/opus-review-v1.jsonl", - "raw_stderr": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/opus-review-v1.stderr.log", - "command": "/opt/homebrew/bin/claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --add-dir /Users/vonng/tmp/silo-r4-evidence-20260915-a9cb --output-format stream-json --verbose", - "completed_at": "2026-09-15T15:49:48.150062+00:00", - "assistant_models": [ - "claude-opus-5" - ], - "observed_model": "claude-opus-5", - "verdict": "GO_WITH_NONBLOCKING_NOTES", - "blocking_findings": 0, - "result_subtype": "success", - "is_error": false, - "session_id": "63e14a68-8565-41fa-9746-3e405fb63e9f", - "duration_ms": 161784, - "num_turns": 35, - "used_tools": { - "Read": 18, - "Glob": 4, - "Grep": 11, - "Write": 1 - }, - "stream_sha256": "eb0918d8a6185b180dddcfc664a96682f05502ecf3b686b08a0547f09879d57d", - "review_sha256": "e1dc12dd99326ae432623ff8de201813e6e84e7ed16a5556c21f9c514d663676", - "prompt_sha256": "07c225beff1523e056c154b3a387cf1ae345def4b4d0173065b882140ac5abdf", - "tool_scope_note": "Read/Grep/Glob allowed. Claude attempted Write to its own plan; the tool was disabled and no file was written. git diff before consensus showed no production source changes.", - "auxiliary_model_note": "assistant_models records actual reviewing assistant messages. Auxiliary usage is distinct. --effort max is explicit in the command, not inferred from model usage." -} diff --git a/docs/investigations/r4/plan-v1.md b/docs/investigations/r4/plan-v1.md deleted file mode 100644 index 0ea961c66..000000000 --- a/docs/investigations/r4/plan-v1.md +++ /dev/null @@ -1,65 +0,0 @@ -# R4 plan v1: preserve the replicated tag timestamp for SSE-KMS - -## Baseline and ownership - -- Baseline: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`, verified against GitHub main on 2026-09-15. -- Branch: `codex/r4-kms-tag-timestamp`; worktree: `/Users/vonng/.codex/worktrees/a9cb/silo`. -- Live open PRs at inspection: #184 and #187, neither owns this options change. -- The worktree lacks the ignored `AGENTS.md`; the parent explicitly confirms `/Users/vonng/pgsty/silo/AGENTS.md` applies. Maintain the PGSTY product graph and inexpensive compatibility. -- R4 owns only the missing field in `cmd/object-api-options.go` and its regression tests. R5 owns DELETE/empty tags, PUT/multipart receiving, sender propagation and full receiver ordering. R4 will supply a standalone source patch to R5; neither task edits the other's worktree. - -## Proven defect and actual trigger - -`putOptsFromHeaders` parses the trusted source tag timestamp before selecting encryption. The SSE-KMS branch constructs and returns another `ObjectOptions` carrying mtime, ETag, replication trust and both Object Lock timestamps, but omits `ReplicationSourceTaggingTimestamp`. The normal path retains it. The parser accepts and preserves RFC3339 fractional seconds even though its layout is `time.RFC3339`; the reproduction uses nanoseconds. - -`CopyObjectHandler` applies local destination encryption configuration before `copyDstOpts` → `putOptsFromReq` → `putOpts` → `putOptsFromHeaders`. The omission is reached by: - -1. Explicit destination SSE-KMS request headers (with or without a key ID/context). -2. A destination bucket with default SSE-KMS, when the request has no explicit SSE choice. -3. Global automatic encryption with no bucket SSE override and no explicit SSE choice. - -Explicit AES256/SSE-C takes its existing branch; source encryption alone does not select the destination KMS branch. Remote federation skips local destination defaults. The relevant trigger is trusted metadata entering the destination KMS branch, not every SSE-KMS object or every tag operation. - -At `CopyObjectHandler`'s tag decision, a zero source timestamp skips the tag update. Current under-lock reconciliation can preserve the stored tag/timestamp when metadata REPLACE reconstructs the map with no timestamp. In the observed same-version replica COPY, the request succeeds, destination encryption is valid, and the old tags/timestamp remain. A missing field in the options layer is not itself proof of a content-read failure. - -PUT and multipart consumers' independent failure to persist a parsed tag timestamp remain R5's responsibility. R4 does not claim to fix all tag replication by correcting this constructor. - -## Source and reproduction evidence - -- `cmd/object-api-options.go`: trusted parsing at 383–426; KMS construction at 433–460; normal assignments at 469–475. -- `cmd/object-handlers.go`: destination default encryption at 1425–1435; `copyDstOpts` at 1454; tag timestamp consumption at 1807–1834; replica reconciliation enabled at 1851; encryption metadata merge at 1910. -- `internal/bucket/encryption/bucket-sse-config.go:135`: explicit request wins, absent config + auto encryption selects KMS, otherwise configured bucket algorithm/key ID applies. -- `cmd/erasure-server-pool-consistency.go:232`: stored valid timestamp wins over absent, older or equal incoming timestamp; writes preserve the stored tag value alongside its timestamp. -- `cmd/erasure-object.go:136` and `cmd/erasure-server-pool.go:1509`: same-version replica COPY reaches existing under-lock tag reconciliation, including object-data rewrites. -- History: the omission exists in `c4373ef290` (2021-09-18); `b2dca43fda` (2026-09-05) added the two Object Lock timestamps but not the tag timestamp. `cfefc049c` fixed KMS context encoding independently and must remain intact. -- Fresh temporary reproduction: `/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/r4_repro_test.go` and `baseline-repro.log` (overlay; no production edits). -- Command: `GOMAXPROCS=2 go test -p 2 -overlay /Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/overlay.json ./cmd -run '^TestReviewR4' -count=1 -timeout 5m -v`. -- Result: expected failure. Unencrypted and AES256 options preserve `2026-09-15T01:00:00.123456789Z`; KMS returns zero. Signed metadata REPLACE COPY on ErasureSD and Erasure (16 disks), across explicit/default/automatic KMS, returns 200 but retains `key=old` and the old timestamp for newer events. Actual encrypted metadata and plaintext GET roundtrips pass. Test deltas are 1–3 nanoseconds. -- These are in-process signed HTTP router and real local disk tests. `kms.NewStub` replaces the remote key service; the normal server encryption/decryption code still runs. Existing `tagTestCapacityDisk` avoids the host's free-space percentage threshold; it delegates all object data/metadata I/O to real test disks. - -## Proposed production change - -Add exactly this field to the existing KMS `ObjectOptions` literal: - -```go -ReplicationSourceTaggingTimestamp: taggingtimestmp, -``` - -Update the neighboring explanatory comment to include tagging alongside retention/legal hold. Do not refactor the common return paths, change parsing/fallback/equal-timestamp semantics, change encryption context encoding, modify trust decisions, add SDK dependencies, or change storage/wire format. Those changes are unnecessary to restore the missing existing contract. - -## Required validation after consensus - -1. Add an options regression matrix covering unencrypted, SSE-S3, SSE-KMS with no context, SSE-KMS with a context, and SSE-C. Validate trusted/untrusted requests, missing/valid/malformed tag timestamps, nanosecond and timezone/whitespace handling, all three replication timestamps, mtime/ETag/trust, nonnil metadata, and unchanged SSE header serialization (including KMS key/context). -2. Promote the temporary COPY reproduction into a named, isolated regression test. Use actual signed same-version metadata COPY with REPLACE metadata and tagging directives, on single-disk and 16-disk backends. For explicit, bucket-default and automatic SSE-KMS, check newer update, older delivery, duplicate replay, and a second newer update. Verify stored tags, exact timestamp, version ID, encryption kind and plaintext GET after each operation. Include an unencrypted/SSE-S3 control if the fixture can do so without expanding implementation scope. -3. Fail the final regression tests against unmodified baseline using an overlay. Then run them on the fixed source, alongside existing replication-trust/options and bucket-KMS Object Lock tests. Check `gofmt`, `git diff --check`, and `go vet ./cmd`. -4. Run the new focused tests under `-race`. Use `GOMAXPROCS=2` and `-p 2` while sibling tasks share the host. A one-field pure option fix does not justify concurrent full-repository suites in all five tasks; full Linux CI and multi-site validation remain separate delivery gates. -5. If a test exposes a separate handler/storage defect, report evidence and coordinate with R5. Do not broaden R4's production patch to make unrelated tests pass. - -## Compatibility, existing state, effort and delivery - -- Public API, header names, stored key names, KMS context/key handling and supported dependencies remain unchanged. Untrusted source headers stay ignored; malformed trusted timestamps continue to fail; absent timestamp remains zero. Existing non-KMS behavior remains unchanged. -- No automatic rewrite/backfill. Lost source tag times cannot be reconstructed from the receiver alone. Upgrading permits subsequent properly timestamped events to be consumed. Review source-of-truth and target state before any targeted resync; full historical convergence also depends on R5. Repeated events subject to existing timestamp/tie semantics are not a universal repair guarantee. -- The source fix can land independently; complete deletion/empty-tag and mixed-encryption convergence needs R5 plus its integration evidence. -- Expected effort: approximately 0.5–1 engineer-day including reproduction, review and local validation; key-service deployment, multi-site failures and existing-state remediation are separate. -- After actual Opus 5.0/max agreement on this exact plan hash, implement locally without another user permission prompt. Preserve raw review, assistant model identity, request effort, baseline and plan hash, issue-by-issue disposition and explicit consensus before source edits. -- Deliver a reviewable local diff, tests and evidence. No main merge, remote publication/release, deployment or existing-state rewrite is authorized by this plan. diff --git a/docs/investigations/r4/research.md b/docs/investigations/r4/research.md deleted file mode 100644 index d6ca3c2ac..000000000 --- a/docs/investigations/r4/research.md +++ /dev/null @@ -1,18 +0,0 @@ -# R4 research log - -## Verified baseline - -2026-09-15: local clean HEAD and GitHub main both `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`. Branch created as `codex/r4-kms-tag-timestamp`. Live GitHub open PRs #184 (`6addf9eb916b5a4b837480cf534cd1efa5407d3c`) and #187 (`b8f2fdde41dff3dc3b8db669c1d42d30ca5c1d3d`) concern other tasks. Claude Code reports `2.1.270`; Go reports `go1.27.1 darwin/arm64`. - -## Coordination - -- Parent task: `01a0a5ab-ee43-7911-bddd-1aca6f8afcc8`. -- R5: `01a0a5b9-602d-7470-9882-4817cf5fdcd1`, `/Users/vonng/.codex/worktrees/77ad/silo`. -- Parent and R5 acknowledged the ownership boundary: R4 options constructor and nonempty KMS COPY tests; R5 producer/receiver ordering and empty values. R5 will consume R4's minimal patch for combined KMS acceptance. -- Initial conservative expectation separated metadata COPY from REPLACE ordering. Inspection of current `ReplicaLockReconcile` and `reconcileStoredObjectTags` shows that stored timestamps are also reconciled under the write lock for REPLACE. The temporary reproduction therefore uses REPLACE directly; the fix must demonstrate the actual sender-shaped path without changing the handler. - -## Baseline reproduction - -Temporary overlay test source and output are in `/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/`. The options and HTTP/disk reproductions fail for the expected missing timestamp. Every KMS HTTP request completed with 200; newer tags remained old; encrypted object metadata and subsequent ordinary plaintext GET succeeded. The result is narrower than claiming all KMS replication fails, and stronger than merely comparing options. - -The temporary source is not a production implementation. See [plan v1](plan-v1.md) for exact scope and required acceptance. The plan is frozen by SHA-256 before invoking real Opus. diff --git a/docs/investigations/r4/verification.json b/docs/investigations/r4/verification.json deleted file mode 100644 index 076c40107..000000000 --- a/docs/investigations/r4/verification.json +++ /dev/null @@ -1,358 +0,0 @@ -{ - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "branch": "codex/r4-kms-tag-timestamp", - "source_sha256": { - "cmd/object-api-options.go": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "cmd/object-api-options-replication_test.go": "1ea2a060987e32a4c76fce96ee974df475944c2d6ab482a4893e33daf7bca849", - "cmd/object-copy-replication-tagging_test.go": "5437a77e68736b4ce69de9c777675251fef24b0352dfe30bd8a836fc7ee810e3" - }, - "source_files_match_all_test_runs": true, - "checks": [ - { - "name": "baseline-final", - "command": [ - "go", - "test", - "-p", - "2", - "-overlay", - "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/baseline-final-overlay.json", - "./cmd", - "-run", - "^Test(PutOptsFromHeadersReplicationTimestamps|APICopyObjectReplicaTaggingTimestampUnderKMS)$", - "-count=1", - "-timeout=5m", - "-v" - ], - "cwd": "/Users/vonng/.codex/worktrees/a9cb/silo", - "env_override": { - "GOMAXPROCS": "2" - }, - "started_at": "2026-09-15T15:50:49.430860+00:00", - "finished_at": "2026-09-15T15:51:20.637630+00:00", - "exit_code": 1, - "log": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/baseline-final.log", - "source_sha256": { - "cmd/object-api-options.go": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "cmd/object-api-options-replication_test.go": "1ea2a060987e32a4c76fce96ee974df475944c2d6ab482a4893e33daf7bca849", - "cmd/object-copy-replication-tagging_test.go": "5437a77e68736b4ce69de9c777675251fef24b0352dfe30bd8a836fc7ee810e3" - }, - "log_sha256": "f849c2082235213764e3db7a314d52af75c4859102478a1e8f837afcaa8f1ea8", - "overlay_sha256": "f4cbc16e4ffccaf63191de2e8476162876055796adb4c38cda2d8c569a3bbabc", - "overlay_sources": { - "/Users/vonng/.codex/worktrees/a9cb/silo/cmd/object-api-options.go": { - "path": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/object-api-options.baseline.go", - "sha256": "16a560d0990ae929393f682f22b32ecd2e7f4d9390484b03b54e176fcd00cff5" - } - }, - "assessment": "Expected baseline regression failure; KMS timestamp loss. Non-KMS controls pass." - }, - { - "name": "focused", - "command": [ - "go", - "test", - "-p", - "2", - "./cmd", - "-run", - "^Test(PutOptsFromHeadersReplicationTimestamps|APICopyObjectReplicaTaggingTimestampUnderKMS|ReplicationTrustControlsInternalOptionsAndEvents|GetAndValidateAttributesOpts.*|APICopyObjectReplicaRetentionRemovalUnderBucketKMS)$", - "-count=1", - "-timeout=5m", - "-v" - ], - "cwd": "/Users/vonng/.codex/worktrees/a9cb/silo", - "env_override": { - "GOMAXPROCS": "2" - }, - "started_at": "2026-09-15T15:51:20.638633+00:00", - "finished_at": "2026-09-15T15:51:48.382212+00:00", - "exit_code": 1, - "log": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/focused.log", - "source_sha256": { - "cmd/object-api-options.go": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "cmd/object-api-options-replication_test.go": "1ea2a060987e32a4c76fce96ee974df475944c2d6ab482a4893e33daf7bca849", - "cmd/object-copy-replication-tagging_test.go": "5437a77e68736b4ce69de9c777675251fef24b0352dfe30bd8a836fc7ee810e3" - }, - "log_sha256": "9a64cbc46e9fd6186b1d3031034b720857851ce70b1466b1da5da6d1a52b76ba", - "assessment": "New tests and options/trust pass; pre-existing KMS lock fixture blocked by host disk free-space percentage." - }, - { - "name": "focused-capacity-adapted", - "command": [ - "go", - "test", - "-p", - "2", - "-overlay", - "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/capacity-overlay.json", - "./cmd", - "-run", - "^Test(PutOptsFromHeadersReplicationTimestamps|APICopyObjectReplicaTaggingTimestampUnderKMS|ReplicationTrustControlsInternalOptionsAndEvents|GetAndValidateAttributesOpts.*|APICopyObjectReplicaRetentionRemovalUnderBucketKMS)$", - "-count=1", - "-timeout=5m", - "-v" - ], - "cwd": "/Users/vonng/.codex/worktrees/a9cb/silo", - "env_override": { - "GOMAXPROCS": "2" - }, - "started_at": "2026-09-15T15:52:20.113475+00:00", - "finished_at": "2026-09-15T15:52:50.842773+00:00", - "exit_code": 0, - "log": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/focused-capacity-adapted.log", - "source_sha256": { - "cmd/object-api-options.go": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "cmd/object-api-options-replication_test.go": "1ea2a060987e32a4c76fce96ee974df475944c2d6ab482a4893e33daf7bca849", - "cmd/object-copy-replication-tagging_test.go": "5437a77e68736b4ce69de9c777675251fef24b0352dfe30bd8a836fc7ee810e3" - }, - "log_sha256": "13f7aa54564a433bef4dddcf2c5ad1fe46fa03869527fb902a255ce4c3263bc9", - "overlay_sha256": "76a7e6fb364bdaaa3469b1dc79f9ea318059f287a4888f322639920c76dfe53a", - "overlay_sources": { - "/Users/vonng/.codex/worktrees/a9cb/silo/cmd/replication-trust_test.go": { - "path": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/replication-trust-capacity_test.go", - "sha256": "c81b526ae51983881bbf464199e6b90f074fa695300fa8ff005e427e4d3c8208" - } - }, - "assessment": "PASS" - }, - { - "name": "race", - "command": [ - "go", - "test", - "-p", - "2", - "-race", - "./cmd", - "-run", - "^Test(PutOptsFromHeadersReplicationTimestamps|APICopyObjectReplicaTaggingTimestampUnderKMS)$", - "-count=1", - "-timeout=5m", - "-v" - ], - "cwd": "/Users/vonng/.codex/worktrees/a9cb/silo", - "env_override": { - "GOMAXPROCS": "2" - }, - "started_at": "2026-09-15T15:52:50.843898+00:00", - "finished_at": "2026-09-15T15:53:43.789325+00:00", - "exit_code": 0, - "log": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/race.log", - "source_sha256": { - "cmd/object-api-options.go": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "cmd/object-api-options-replication_test.go": "1ea2a060987e32a4c76fce96ee974df475944c2d6ab482a4893e33daf7bca849", - "cmd/object-copy-replication-tagging_test.go": "5437a77e68736b4ce69de9c777675251fef24b0352dfe30bd8a836fc7ee810e3" - }, - "log_sha256": "ae66a8c9e569a5c4b57ae56e75afc85c06a8b76f1567187519da0726605a4de4", - "assessment": "PASS" - }, - { - "name": "vet", - "command": [ - "go", - "vet", - "-p", - "2", - "./cmd" - ], - "cwd": "/Users/vonng/.codex/worktrees/a9cb/silo", - "env_override": { - "GOMAXPROCS": "2" - }, - "started_at": "2026-09-15T15:53:43.790197+00:00", - "finished_at": "2026-09-15T15:53:51.129773+00:00", - "exit_code": 0, - "log": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/vet.log", - "source_sha256": { - "cmd/object-api-options.go": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "cmd/object-api-options-replication_test.go": "1ea2a060987e32a4c76fce96ee974df475944c2d6ab482a4893e33daf7bca849", - "cmd/object-copy-replication-tagging_test.go": "5437a77e68736b4ce69de9c777675251fef24b0352dfe30bd8a836fc7ee810e3" - }, - "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "assessment": "PASS" - }, - { - "name": "lint", - "command": [ - "/Users/vonng/pgsty/silo/.bin/golangci/v2.13.1/golangci-lint", - "run", - "--build-tags", - "kqueue", - "--timeout=10m", - "--config", - "./.golangci.yml", - "./cmd/..." - ], - "cwd": "/Users/vonng/.codex/worktrees/a9cb/silo", - "env_override": { - "GOMAXPROCS": "2" - }, - "started_at": "2026-09-15T15:53:51.130456+00:00", - "finished_at": "2026-09-15T15:55:39.114303+00:00", - "exit_code": 0, - "log": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/lint.log", - "source_sha256": { - "cmd/object-api-options.go": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "cmd/object-api-options-replication_test.go": "1ea2a060987e32a4c76fce96ee974df475944c2d6ab482a4893e33daf7bca849", - "cmd/object-copy-replication-tagging_test.go": "5437a77e68736b4ce69de9c777675251fef24b0352dfe30bd8a836fc7ee810e3" - }, - "log_sha256": "e92606b0bf483111dff0a120c315ea165821348f31365020e2468a0059095c47", - "assessment": "PASS" - } - ], - "format_checks": [ - { - "command": [ - "gofmt", - "-l", - "cmd/object-api-options.go", - "cmd/object-api-options-replication_test.go", - "cmd/object-copy-replication-tagging_test.go" - ], - "exit_code": 0, - "output": "" - }, - { - "command": [ - "git", - "diff", - "--check" - ], - "exit_code": 0, - "output": "" - } - ], - "evidence_directory": "/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb", - "evidence_files": { - "baseline-final-overlay.json": { - "size": 177, - "sha256": "f4cbc16e4ffccaf63191de2e8476162876055796adb4c38cda2d8c569a3bbabc" - }, - "baseline-final.json": { - "size": 1584, - "sha256": "5bf7be51e5a0eb41a40dc5fc5d3a1aba5df7733ad5dcb001f8d870a01c4233ba" - }, - "baseline-final.log": { - "size": 22493, - "sha256": "f849c2082235213764e3db7a314d52af75c4859102478a1e8f837afcaa8f1ea8" - }, - "baseline-identity.txt": { - "size": 1676, - "sha256": "9b21841e19a0cbb8ded18c2597488a527a27bedc65109d05d4ff598103073b68" - }, - "baseline-options.log": { - "size": 11395, - "sha256": "d692f0a4bc9c58e2ac0087afa356ddf48f86e1040ea8138d68ffc4d0992bf3cb" - }, - "baseline-repro.log": { - "size": 5693, - "sha256": "aa89b76f4723c6a3ce224faa7796403628d978a8707544bd97848b8887de2113" - }, - "capacity-fixture.diff": { - "size": 870, - "sha256": "8d01e0b0068441f37ecee37125b81424d1f30d7c4fb37d435ea0cfe2e4617e5e" - }, - "capacity-overlay.json": { - "size": 185, - "sha256": "76a7e6fb364bdaaa3469b1dc79f9ea318059f287a4888f322639920c76dfe53a" - }, - "final_copy_repro_test.go": { - "size": 5942, - "sha256": "8949e07d96d2949a79f5a9e83c7a7c0473733d77b407e9c51da477d4ab74f1a8" - }, - "focused-capacity-adapted.json": { - "size": 1661, - "sha256": "1a599b41caabfc5eb44db8d89c8b7e4f4f84f5f036d008369155fd337f65bdf9" - }, - "focused-capacity-adapted.log": { - "size": 20384, - "sha256": "13f7aa54564a433bef4dddcf2c5ad1fe46fa03869527fb902a255ce4c3263bc9" - }, - "focused.json": { - "size": 1253, - "sha256": "d9bb4979ea8aeaabb809cdc6e400a8673530bc83abf3dc2b2a06853a8523d0d9" - }, - "focused.log": { - "size": 20475, - "sha256": "9a64cbc46e9fd6186b1d3031034b720857851ce70b1466b1da5da6d1a52b76ba" - }, - "format-checks.json": { - "size": 352, - "sha256": "7569260900a799d5efdfb39db1f575ab1dadbbb04ace222e036968e66b6b59e7" - }, - "lint.json": { - "size": 991, - "sha256": "a7144713b069f470a94b1ebe6fca6683a4b866a891a2756e15f28c280666ca14" - }, - "lint.log": { - "size": 10, - "sha256": "e92606b0bf483111dff0a120c315ea165821348f31365020e2468a0059095c47" - }, - "object-api-options.baseline.go": { - "size": 16653, - "sha256": "16a560d0990ae929393f682f22b32ecd2e7f4d9390484b03b54e176fcd00cff5" - }, - "options-overlay.json": { - "size": 185, - "sha256": "5506b9c3b998b32f01c45af3cf01605eae9e4fb262c9ff3a6b0040abe719d4d9" - }, - "options_repro_test.go": { - "size": 4192, - "sha256": "1a57a47bdd370042fa0f0d2d90efe447abedee9b9ef48a938d4bed631d83ec0b" - }, - "opus-review-v1.exit": { - "size": 2, - "sha256": "9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa" - }, - "opus-review-v1.jsonl": { - "size": 410466, - "sha256": "eb0918d8a6185b180dddcfc664a96682f05502ecf3b686b08a0547f09879d57d" - }, - "opus-review-v1.stderr.log": { - "size": 0, - "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - }, - "overlay.json": { - "size": 158, - "sha256": "95f7c3f7e206fe36731e6d7e4a48f90c7403f07ae8155c125c6b86d2f1c2d487" - }, - "r4-kms-tag-timestamp.patch": { - "size": 874, - "sha256": "2d4806d986bbd94ba4bc3951f3aeee48401ee1921c28ded0988fa09ca76ca26f" - }, - "r4_repro_test.go": { - "size": 5508, - "sha256": "9bcefb6da2416b577b58085485cad60f677c2265e9dfa84d02e465e1b203766b" - }, - "race.json": { - "size": 1026, - "sha256": "50b73e4acbc2426f3dcfadde78d0f0a86f10702345d2939a30204600bc750a13" - }, - "race.log": { - "size": 18566, - "sha256": "ae66a8c9e569a5c4b57ae56e75afc85c06a8b76f1567187519da0726605a4de4" - }, - "replication-trust-capacity_test.go": { - "size": 61271, - "sha256": "c81b526ae51983881bbf464199e6b90f074fa695300fa8ff005e427e4d3c8208" - }, - "review-prompt-v1.md": { - "size": 2770, - "sha256": "07c225beff1523e056c154b3a387cf1ae345def4b4d0173065b882140ac5abdf" - }, - "run-checks.py": { - "size": 2266, - "sha256": "ddecea5220bc9c286df18c0e9eca101f3d8ab731c307cd4acef937f3ecd11e65" - }, - "vet.json": { - "size": 853, - "sha256": "e0964444bc91640ed6cf78229050bad5b94af4210bdf44b11d1a848f1ee930a6" - }, - "vet.log": { - "size": 0, - "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - } - }, - "scope": "Darwin arm64; real signed HTTP + disk I/O; KMS service stub; single-pool single/16-disk fixtures; no remote CI, multi-site, release or deployment." -} diff --git a/docs/investigations/r4/verification.md b/docs/investigations/r4/verification.md deleted file mode 100644 index 6242ae644..000000000 --- a/docs/investigations/r4/verification.md +++ /dev/null @@ -1,52 +0,0 @@ -# R4 修复与本地验收 - -这是 2026-09-15 的本地验收快照。用户后续授权的实现级复核、提交规范调整与合并流程见 [合并前复核](merge-verification.md);以下原始测试记录及哈希保留当时状态。 - -## 结果 - -在 `putOptsFromHeaders` 的 SSE-KMS 选项构造中补齐 `ReplicationSourceTaggingTimestamp`。目的端使用显式 SSE-KMS、桶默认 KMS 或自动加密时,可信复制 COPY 现在能消费来源标签时间戳,并在现有存储锁内完成排序。 - -生产修改只有一个字段和相邻注释。API、存储格式、KMS key/context、信任判断和既有排序规则保持兼容。R5 的删除/空标签及 PUT/multipart 时间戳传播独立交付。 - -## 方案与 Opus 共识 - -- 基线:`9ebe81c1b3611f9cc73e676b5b741c2be62c467a`,已重新查询 GitHub main。 -- 分支:`codex/r4-kms-tag-timestamp`。 -- [冻结方案 v1](plan-v1.md):SHA-256 `ad539f2071155de6955b583991684ed33c4bfe2e29660005840cdc97d7e1a754`。 -- 真实评审为本机 Claude Code 2.1.270,实际 assistant 模型 `claude-opus-5`,显式 `--effort max`。结论 **GO_WITH_NONBLOCKING_NOTES,0 个阻断项**。 -- [逐条意见处置与双方共识](consensus.md)、[原始返回评审正文](opus-v1-review.md)、[模型与哈希记录](opus-v1.metadata.json) 已保存。先保存共识,再修改生产源码。 - -## 变更与测试 - -| 文件 | 内容 | -|---|---| -| `cmd/object-api-options.go` | 在 KMS 字面量中保留已解析的来源标签时间戳。 | -| `cmd/object-api-options-replication_test.go` | 无加密、SSE-S3、SSE-KMS、带 key/context 的 KMS、SSE-C;可信/非可信;缺失、有效、无效标签时间;纳秒、时区与空格;mtime/ETag/三个时间戳、metadata 与 SSE 序列化。 | -| `cmd/object-copy-replication-tagging_test.go` | 两种单池后端 × 五种目的端加密模式 × 五个有序事件,共 50 次签名 COPY 和 50 次普通 GET。每步检查最终标签、精确时间戳、对象版本、加密类型及明文内容。 | - -COPY 使用 `metadata=REPLACE`、`tagging=REPLACE` 和可信复制身份。事件为较新更新、乱序旧更新、重复事件、再次更新,以及不带来源标签时间戳的请求。更新间隔仅 1–3 纳秒,防止时间精度退化被秒级测试掩盖。无加密与 AES256 是对照;KMS 覆盖显式、桶默认和自动加密入口。 - -## 验证状态 - -| 检查 | 结果 | 证据文件 | -|---|---|---| -| 最终测试 + 未修复基线 constructor overlay | 预期失败;只有可信 KMS 有效标签时间戳及 KMS COPY 更新失败,对照通过 | `baseline-final.log/json` | -| 修复后最终新增测试与既有 trust/options 测试 | 通过;未使用生产源码 overlay | `focused.log` | -| 既有 KMS Object Lock 回归 | 首次受宿主机磁盘余量阈值阻挡;仅适配测试容量报告后,与上述定向测试一起通过 | `focused-capacity-adapted.log/json`、`capacity-fixture.diff` | -| 新增测试 `-race` | 通过 | `race.log/json` | -| `go vet -p 2 ./cmd` | 通过 | `vet.log/json` | -| 仓库配置的 golangci-lint,范围 `./cmd/...`、`kqueue` build tag | 通过,0 issues | `lint.log/json` | -| gofmt、git diff --check | 通过 | `format-checks.json` | - -原始日志和每条命令的运行记录位于 `/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/`。每份检查 JSON 都记录命令、退出码、时间和三个源码/测试文件的 SHA-256;最终交付已逐一确认文件哈希一致。[验证清单](verification.json) 另记录 overlay 的实际替换文件哈希,避免混淆基线与修复版执行代码。 - -测试使用真实签名 HTTP 路由、实际本地对象数据/元数据读写、服务器加解密代码;远程密钥服务由 `kms.NewStub` 代替。ErasureSD 与 16 盘 Erasure 均为单池。容量适配只使用已有 `tagTestCapacityDisk`,避免本机磁盘使用比例触发防写阈值,所有对象 I/O 仍由真实测试磁盘承担;未调整生产容量保护。 - -## 交付与剩余边界 - -- 本地实现和要求的定向验证均已完成,将源码、回归、研究、共识和验收记录作为一个本地提交交付。 -- R4 的独立生产补丁已提供给 R5:`/Users/vonng/tmp/silo-r4-evidence-20260915-a9cb/r4-kms-tag-timestamp.patch`,SHA-256 `2d4806d986bbd94ba4bc3951f3aeee48401ee1921c28ded0988fa09ca76ca26f`。 -- Opus 共识为方案级共识;本地测试结论来自实际运行,不把它记作 Opus 执行了测试。 -- 多池/多站点故障恢复、外部 KMS 服务、完整 Linux CI、主干合并、远端发布和部署尚未执行。 -- 没有改写存量。丢失的来源时间戳不能仅从接收端推导;后续重放/重同步须核对来源权威性及 R5 的全链路处理,不保证旧事件重放可以修复全部历史状态。 -- 另登记 KMS 字面量缺少 Proxy/Speedtest 标志的范围外观察,已交父任务单独核验,本次未扩大修复。 diff --git a/docs/investigations/r5/baseline.md b/docs/investigations/r5/baseline.md deleted file mode 100644 index 7211fd88c..000000000 --- a/docs/investigations/r5/baseline.md +++ /dev/null @@ -1,17 +0,0 @@ -# R5 investigation baseline - -- Worktree: `/Users/vonng/.codex/worktrees/77ad/silo` -- HEAD: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`; GitHub main checked live on 2026-09-15. -- Branch: `codex/r5-tag-deletion-ordering`. -- WORKFLOW: `/Users/vonng/tmp/silo-r4-r8-20260915-01a0a5ab/WORKFLOW.md` read completely. -- This isolated worktree has no AGENTS.md. Read `/Users/vonng/pgsty/silo/AGENTS.md`: PGSTY supported stack, minimal compatible changes, separate local/merge/release gates. -- Existing tag storage reconciliation is already in HEAD; inspect and reuse it. -- No open R5 PR in live `gh pr list`; unrelated open PRs #184 and #187 belong to R6/R7. -- Parent reproduction: `/Users/vonng/tmp/silo-r4-r8-20260915-01a0a5ab/baseline-evidence/r5-handler.log`. -- Current reproduction overlay and raw output: `/Users/vonng/tmp/silo-r5-20260915-77ad/`. -- Claude Code actual version: 2.1.270 at `/opt/homebrew/bin/claude`. Required model `claude-opus-5`, effort `max`; model identity must be checked in assistant messages. -- Toolchain: go1.27.1 darwin/arm64. Targeted tests use GOMAXPROCS=2 and -p 1 to share the host. - -## Ownership - -R4 owns `cmd/object-api-options.go` KMS common-field preservation and option tests. R5 does not edit that file. R5 owns tag state generation, wire propagation, COPY/PUT/multipart persistence and ordered replay tests. Coordination requested through parent while R4 actual task ID is pending. diff --git a/docs/investigations/r5/consensus.md b/docs/investigations/r5/consensus.md deleted file mode 100644 index 913ef144e..000000000 --- a/docs/investigations/r5/consensus.md +++ /dev/null @@ -1,32 +0,0 @@ -# R5 plan consensus - -Date: 2026-09-15. Research base: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`. - -## Accepted plan - -- Version: **v2**, `plan-v2.md`. -- SHA256: `5a782acf3f285b23d1ae43a73481c4eb772a9a6d917fc5a550ecfc7cbf7446ca`. -- Actual reviewer: **claude-opus-5**, explicitly invoked **--effort max** through `/opt/homebrew/bin/claude` 2.1.270. All assistant messages in both reviews identify this model. The auxiliary Haiku usage in CLI bookkeeping is separately retained in modelUsage and is not the reviewer. -- Actual result: **APPROVE_WITH_NONBLOCKING_NOTES; 0 blocking items** in opus-v2-review.md. -- Codex accepts this exact v2 and its bounded per-hop scope. Plan hash was checked locally immediately before implementation. Opus's read-only tools did not run hashing; the original caveat is retained in raw review. -- Workflow permission: after this written consensus, local implementation and verification proceed without another user approval. No main merge, remote push, release, deployment or production state rewrite. - -## Discussion and resolved differences - -V1 was REQUEST_CHANGES with five blockers. See opus-v1-response.md for individual treatment and source evidence. V2 resolves all five. Opus explicitly withdrew its empty-only transfer proposal after the same-value re-addition counterexample, corrected its KMS COPY statement after inspecting bucket-default/auto encryption, and accepted that per-pool-only local clock guards are insufficient for ordinary source reads. - -## Nonblocking notes accepted during implementation - -- Extra metadata I/O occurs on scheduled metadata/heal/existing-object work with a recorded revision; ordinary object replication dispatches straight to full transfer. Completed scanner gates and incoming replication suppression avoid a feedback loop. Test the incoming no-reschedule decision. -- Pin unchanged object ModTime for local tagging changes. -- Keep a single-set monotonic guard and one uniform multi-pool candidate; direct-to-set writes outside the pool lock can transiently differ and re-converge on the next pooled mutation. -- Check actual failed COPY status/action and subsequent retry. Malformed timestamps fail both PUT and metadata COPY construction. -- Preserve scope limitations: tag-filter target selection, historical missing revisions, arbitrary unversioned content overwrites, and real multi-site/host-clock skew are not solved or production-accepted here. - -## R4 dependency - -Reuse reviewed local R4 commit `dbcf8dec589deb5d91e17d295cb70997635f5b55` on this isolated branch before implementation. Its only production change is the KMS options field, already examined against the provided patch SHA256 `2d4806d986bbd94ba4bc3951f3aeee48401ee1921c28ded0988fa09ca76ca26f`. R5 does not reimplement or modify that field. This makes R4+R5 tests run on actual combined source, with R5's eventual commit measured against the R4 dependency. - -## Raw records - -`/Users/vonng/tmp/silo-r5-20260915-77ad/opus-v1.jsonl`, `opus-v1.stderr.log`, `opus-v1-request.json`, and matching `opus-v2.*`. In-repository review texts, prompts and metadata preserve plan hashes, model identity, usage and verdicts. V1 failure is not treated as approval. diff --git a/docs/investigations/r5/dependency-handoff.json b/docs/investigations/r5/dependency-handoff.json deleted file mode 100644 index 8c97d6ee2..000000000 --- a/docs/investigations/r5/dependency-handoff.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "tested_dependency": "dbcf8dec589deb5d91e17d295cb70997635f5b55", - "merged_dependency": "af2b1794d38d9e70e1d2c3ee692426e4b6cab4bd", - "pr": "https://github.com/pgsty/silo/pull/193", - "files": { - "cmd/object-api-options.go": { - "tested_sha256": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "merged_sha256": "25e2e9484fafd94d1b2c857b94373758e481893ad93fb1a063edf7746277accc", - "package_body_sha256": "23fa2a25307bf1e41b665217a3f39aa7a8b860686a54209092fa9c0f1f13243a", - "package_body_identical": true - }, - "cmd/object-api-options-replication_test.go": { - "tested_sha256": "1ea2a060987e32a4c76fce96ee974df475944c2d6ab482a4893e33daf7bca849", - "merged_sha256": "c21fc8889a079085d9a882499a1cbe868278a3517580651f3bed1102e2a6aef8", - "package_body_sha256": "1a57a47bdd370042fa0f0d2d90efe447abedee9b9ef48a938d4bed631d83ec0b", - "package_body_identical": true - }, - "cmd/object-copy-replication-tagging_test.go": { - "tested_sha256": "5437a77e68736b4ce69de9c777675251fef24b0352dfe30bd8a836fc7ee810e3", - "merged_sha256": "73f066ed7258d430f078ecc90e551ece878bd3d4672bc762094d434ff8fec23d", - "package_body_sha256": "ef77de91cd91d1bd1c3cb10e4fee171c724c4f548749a23c7e48dcfcfb6a1585", - "package_body_identical": true - } - }, - "other_changes": [ - "cmd/object-api-options-replication_test.go", - "cmd/object-copy-replication-tagging_test.go", - "docs/investigations/r4/implementation-review.md", - "docs/investigations/r4/implementation-review.metadata.json", - "docs/investigations/r4/merge-verification.json", - "docs/investigations/r4/merge-verification.md", - "docs/investigations/r4/verification.md" - ], - "scope": "R5 local branch will rebase onto this exact R4 merge; no R5 push or merge." -} diff --git a/docs/investigations/r5/evidence-manifest.json b/docs/investigations/r5/evidence-manifest.json deleted file mode 100644 index 8d9e0558a..000000000 --- a/docs/investigations/r5/evidence-manifest.json +++ /dev/null @@ -1,340 +0,0 @@ -{ - "raw_files": [ - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-ack.log", - "bytes": 607, - "sha256": "ef698b8f46c850928057a31bf4e92f64f9a0dcebb8753bab00e9dcef2b44ffb6" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-extended.log", - "bytes": 2764, - "sha256": "a52bb6644b82a1986c233deeb9fb7b6cd3f4975337aa11446a1b27c459355db9" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-matrix.log", - "bytes": 20974, - "sha256": "5348c2ae4a20238ae50f70bcaea3aa55169b3479f60eab522692bdabe3420ab0" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-pools-rotation.log", - "bytes": 2249, - "sha256": "1d47acbe4d759b0f413f90589ff51b1f844f1885885d45d11c72a6295f5a4653" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-pools.log", - "bytes": 358, - "sha256": "56dd5efc0c833070576c4c7e2cb2abca8a82380060596be58871067526557c32" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-production/cmd/bucket-replication.go", - "bytes": 137081, - "sha256": "1e4d27c9eb2bff51eb28460d167faa3279b541d43c77ce35ad010dcab58bf7c5" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-production/cmd/erasure-object.go", - "bytes": 84089, - "sha256": "1012ae265e2453db125c6f2d16f866c72760b58d61c18f79dff4a551c208e3ce" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-production/cmd/erasure-server-pool-consistency.go", - "bytes": 14240, - "sha256": "78e63ca1117ea2d3e3e93864a4365dfa9bb303b4707de5087bdc144d0502a0db" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-production/cmd/erasure-server-pool.go", - "bytes": 99706, - "sha256": "2bfe0899fe3e42840fa4f078887184e4d7d2332d780ce63c31c9495af7056406" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-production/cmd/object-handlers-common.go", - "bytes": 19143, - "sha256": "00bf8409d25f9cf6a098a90f9e0bd7d2b237be7adc12622f0b9a66146af0a828" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-production/cmd/object-handlers.go", - "bytes": 146644, - "sha256": "27d47a17e12088f41b35de51da875f28a89a7e821bc27d0f88e6064ec386c993" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-production/cmd/object-multipart-handlers.go", - "bytes": 48935, - "sha256": "c818ce72d9115ed4f9cbe51571e7737957010a3934a4d000000895e45100edc7" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-production/empty_test.go", - "bytes": 12, - "sha256": "9c78355c4da37df8f708f143fe19173dc146adcd99d1636594d265c5407755bf" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-production-overlay.json", - "bytes": 1508, - "sha256": "874d728abc9cb67c5db17ef4c3ce875d789ae4b5000dcbb593fe8dcd47094533" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-related-suite.log", - "bytes": 3521, - "sha256": "68b23b21c4de8b8252689f841376dec990257d929f0277d3087f467a246728e8" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline-resync.log", - "bytes": 116, - "sha256": "a620c0ecd112aceac9fd17b989b6283604a3866928ed51e9ed0b16079284fcbf" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline.log", - "bytes": 1629, - "sha256": "29d80ad52b0302d4eb4993db7a63c88bbc920923afa9775634d3c2fe000c066f" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/baseline_test.go", - "bytes": 18743, - "sha256": "ce609764fa53f7a86e77dc8f2c00c6d8b878c4c9b6f885fb2618bf8f932cad04" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/build-result.json", - "bytes": 729, - "sha256": "2df4873188d94c3745631b6e774e76a7646b3366fab7a3badf7ac1be136f7bd9" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/cache-reclaim.json", - "bytes": 2482128, - "sha256": "8dc7866b30bfd7fed339a3cd4a2dd40c0ec8f3b471cb004fc14f303a41642ad8" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/capacity-fixture/cmd/erasure-server-pool-consistency_test.go", - "bytes": 54157, - "sha256": "c3c1bf441e5f97fd2648c5fc9b89cb11679018e349daa4d0eef4ebbfada322db" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/capacity-fixture/cmd/post-policy_test.go", - "bytes": 33420, - "sha256": "697f8a08dceae481fd1aae7b5e7f3906b55b34fe9928688456eaa943eba79020" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/capacity-fixture/cmd/test-utils_test.go", - "bytes": 79596, - "sha256": "fb4847b10d3d59c7d62ee79a61d54e8c0bc93d6eb32cb520f5662bb7b52750f5" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/capacity-fixture-manifest.json", - "bytes": 426, - "sha256": "8940a56a6f46b7e9c236c3a8d39d927735954ae42601ca52c4a190339fb1f21f" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/capacity-fixture-overlay.json", - "bytes": 368, - "sha256": "3b8586973d426f78145aa25f0c3c9d48faf93678ffe9410fc9aa089cb6167af0" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/capacity-fixture.patch", - "bytes": 842, - "sha256": "3e3732c7fab2b95b9f2e80a6ee973a588600f84eeb2b74c2f15a09373228247f" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/capacity-post-manifest.json", - "bytes": 205, - "sha256": "34ae2c860a5cc1a9615d7b410eff781f5f01d54dba65f66edae0f724d3d232c7" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/capacity-post-overlay.json", - "bytes": 522, - "sha256": "8d0b7594481fa9028fbc4284e1e94cb853828925423d2a7b3cd6f5cbabb82e3c" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/capacity-post.patch", - "bytes": 358, - "sha256": "f4aa03d4a0a9f0bb220fa3b3b988a8dda1ad7d6764daaeb4e60eb4ee4e996674" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/discussion-baseline.log", - "bytes": 1029, - "sha256": "9581de36ec9a403d304c32192d17265b1e9c9414c60e9f2cb57321faebbe23dc" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/final-check-results.json", - "bytes": 939, - "sha256": "6652d2db1ac98d65232e37eda7738972a74f9569ac63b534f1af798ebf19f642" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/final-post-and-pools.log", - "bytes": 1280, - "sha256": "e7e5fec0761976470eafbf31bcefe4abc241525514f6c3b9a2baa035354144fa" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/fixed-resync-isolated.log", - "bytes": 116, - "sha256": "bed15b381379998bbe2a0aa1be19c2cfec1b0063886143dbe82918f9652c28ff" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/fixed-targeted-final.log", - "bytes": 25221, - "sha256": "32283f5d7eea5ce4974fefa0724a1c4de565bb0ef9a0f4fbcad68140bccc32b1" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/fixed-targeted-latest.log", - "bytes": 32772, - "sha256": "45f362258e21b631cb5ebcd15dec98bd2fa3186f509f18fe216d7cd0ebdb5a9c" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/fixed-targeted.log", - "bytes": 27709, - "sha256": "a30f7754f90cfade50ed80a066c5e2bd53faf225bd615cd07b9cf1350b2e3139" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/golangci-lint-serial", - "bytes": 103, - "sha256": "b2a00c2702468165a7851ee3a6addbef9e581833a33492d44ac66d531cfc4fff" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/implementation-files.json", - "bytes": 936, - "sha256": "7bd1279e1c99da9da4562cda6e0265d36d53a9bb546f10d42c4174c1a34b5c70" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/implementation-v1.patch", - "bytes": 48399, - "sha256": "8f6f76ee874c43b0827fde272e8a947f118efb1c1af92bf4a02ef88f93554c1b" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/make-build.log", - "bytes": 55, - "sha256": "6ba9b545236be964861749c72e7609edf12b8f470df30d1ede8fd62f497e629b" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/make-verifiers-final.log", - "bytes": 217, - "sha256": "d973482061716daa245e7d7162765dec0e6ecd5fee41249d2702a2d8bfce1c32" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/make-verifiers-serial.log", - "bytes": 410, - "sha256": "e42a5bb55f5c1ebfcf02cebebf6d82cf1ec5a2d74590cdf838deba16dd80bfdf" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/make-verifiers-success.log", - "bytes": 162, - "sha256": "b4982b6a7302e733c7bec4a5fb36b8ee8865fe95f1595e7079ce7f8455406210" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/make-verifiers.log", - "bytes": 285, - "sha256": "42f4b147ef4aac45ba91457e7932db30f9b57f285466ee3f10bbaa9a911e5bc7" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/matrix-overlay.json", - "bytes": 153, - "sha256": "a0fb5eb71ebb2bdb3374813752de5867848454794bcbf302ba0f6d7d4f6c0519" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/matrix_test.go", - "bytes": 22996, - "sha256": "c9cf527c63800fa045bdf0b8e95d740b81a3d5be3a08c74811fa5c06bed56d4f" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-implementation-request.json", - "bytes": 909, - "sha256": "9fab15ce1cfb5bad102b1880968e4731a7b5cb02d6d01e6cb2caf8bc9029a150" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-implementation.jsonl", - "bytes": 1184150, - "sha256": "fef155a7382c8f66f69b7afd5fd94559edcc6f72fc13aeb7ef01319c22c09861" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-implementation.stderr.log", - "bytes": 0, - "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-v1-request.json", - "bytes": 216, - "sha256": "175e013154c241805e00368f7841d41faf48ee847ff5251aad96ae57831e244a" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-v1.jsonl", - "bytes": 1017882, - "sha256": "63c00d8362f236a18293e1a637eff3b7c7e38b0bbd11805f75d91e8774efcdb2" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-v1.stderr.log", - "bytes": 0, - "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-v2-request.json", - "bytes": 216, - "sha256": "ed710eff03d3ebabab277c9e453048097a8649582df4b01f99d0ee0ad3a7c831" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-v2.jsonl", - "bytes": 414265, - "sha256": "e888fbf38ba7fd49891e0757c18006875d02a6bbb325906a31fc8d98e54d0e39" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-v2.stderr.log", - "bytes": 0, - "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/overlay.json", - "bytes": 142, - "sha256": "d51934eb99e2b19d149478e090ec327ed2753a5ad2a026c8745b8e2554962a00" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/related-filter.txt", - "bytes": 5771, - "sha256": "f022bc24ae0fe391ae51a5095db1d2e415a994934327c7508e6c65edc313cc78" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/related-names.txt", - "bytes": 5767, - "sha256": "369ed4b6742d15e8ab4d790615842304a7178fbdc598e231e9205fc096c0785a" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/related-suite-capacity-final.log", - "bytes": 137077, - "sha256": "322d4854ba909bd99d5c7740abeeac05eb76cb2d39d2c7541642335ae6a1ffe9" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/related-suite-capacity.log", - "bytes": 3362, - "sha256": "1e4f1bc6be2b4339a0d9b7a2e951774fc24ec5521be9fcf53b2ce02031f5cdbe" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/related-suite-rest.log", - "bytes": 172665, - "sha256": "846d10084299a77253153c0eed8546e275c789a0289a4198052773e49a73423f" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/related-suite.log", - "bytes": 3521, - "sha256": "38e3e8e4b7ae815fce40931009a0d4755601a4f3f7f9f44a569edff024c9239b" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/sender_test.go", - "bytes": 5140, - "sha256": "a1a58fd6968b41cf6c565d9f63a1d0fa1c907f008f70acfd13c7a6c525376357" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/silo-version.log", - "bytes": 317, - "sha256": "8ce9c5d15082a78e696aa79f8ec007f72ce969ce6ebd7dab2f7db69b20b51f8b" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/targeted-race-final.log", - "bytes": 39753, - "sha256": "127cfb73f415bad43e2fd79c05150ab765322dcadb29e687b752b6174d4ad850" - }, - { - "path": "/Users/vonng/tmp/silo-r5-20260915-77ad/verifiers-result.json", - "bytes": 417, - "sha256": "6faea89c420685ccae0642be88ddf86938bb25e24f066fd9e57138d16c9e9856" - } - ], - "binary": { - "path": "/Users/vonng/.codex/worktrees/77ad/silo/silo", - "bytes": 93070802, - "sha256": "dd789126966d4a42bc0a9bcd8b8eab9714e3eadc7a524505a6224ea6d76c750f" - }, - "scope": "Local development build and exact raw verification/review records; no publication or production acceptance." -} diff --git a/docs/investigations/r5/final-implementation-manifest.json b/docs/investigations/r5/final-implementation-manifest.json deleted file mode 100644 index d1d167aeb..000000000 --- a/docs/investigations/r5/final-implementation-manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "research_base": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "tested_dependency": "dbcf8dec589deb5d91e17d295cb70997635f5b55", - "reviewed_patch_sha256": "8f6f76ee874c43b0827fde272e8a947f118efb1c1af92bf4a02ef88f93554c1b", - "plan_version": "v2", - "plan_sha256": "5a782acf3f285b23d1ae43a73481c4eb772a9a6d917fc5a550ecfc7cbf7446ca", - "files": { - "cmd/bucket-replication.go": "cbab22ffe316fabc076e7f4a1fa5b1417d07b265ae9dc1b27454689355926d35", - "cmd/erasure-object.go": "4bc848685ea714d88cabbd5d1b8585fbcc06f7b19c775e1a811030e783d0e1a4", - "cmd/erasure-server-pool-consistency.go": "d2736ef6bffbb5c5758eba8df38f8d4ecb888a838ab0de8ad3cf015c051f8ad7", - "cmd/erasure-server-pool.go": "87ad0b25dfa3081d0e63d0073b788614a9c88e2498a2ce0956b93f8a0a03ef53", - "cmd/object-handlers-common.go": "101bd7d7447072d13fed50983b69b562e4725632645e623d7fdd490f388ecdec", - "cmd/object-handlers.go": "61897a260f3f5f660f41edcb50956c60e914ef98f9a987da824f16d78171fde2", - "cmd/object-multipart-handlers.go": "d9622c69c540ab32dd23916e3f534b6886473a98370c9dd17673e69a423b2a7e", - "cmd/replication-tagging-order_test.go": "c8260b4ccf82fa615e1e24b35a07f2d1aacbcf776e5c6f9dadffea4a09ad6ea8", - "cmd/replication-tagging-sender_test.go": "3770a1a48a6efe58fe8127e1e4fdf6bd7cf171e17db20f15222ea2f7b85db1af" - }, - "production_unchanged_after_review": true, - "delivery_dependency": "af2b1794d38d9e70e1d2c3ee692426e4b6cab4bd" -} diff --git a/docs/investigations/r5/implementation-manifest.json b/docs/investigations/r5/implementation-manifest.json deleted file mode 100644 index af39a0b5e..000000000 --- a/docs/investigations/r5/implementation-manifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "base_commit": "dbcf8dec589deb5d91e17d295cb70997635f5b55", - "plan_version": "v2", - "plan_sha256": "5a782acf3f285b23d1ae43a73481c4eb772a9a6d917fc5a550ecfc7cbf7446ca", - "patch_sha256": "8f6f76ee874c43b0827fde272e8a947f118efb1c1af92bf4a02ef88f93554c1b", - "files": { - "cmd/bucket-replication.go": "cbab22ffe316fabc076e7f4a1fa5b1417d07b265ae9dc1b27454689355926d35", - "cmd/erasure-object.go": "4bc848685ea714d88cabbd5d1b8585fbcc06f7b19c775e1a811030e783d0e1a4", - "cmd/erasure-server-pool-consistency.go": "d2736ef6bffbb5c5758eba8df38f8d4ecb888a838ab0de8ad3cf015c051f8ad7", - "cmd/erasure-server-pool.go": "87ad0b25dfa3081d0e63d0073b788614a9c88e2498a2ce0956b93f8a0a03ef53", - "cmd/object-handlers-common.go": "101bd7d7447072d13fed50983b69b562e4725632645e623d7fdd490f388ecdec", - "cmd/object-handlers.go": "61897a260f3f5f660f41edcb50956c60e914ef98f9a987da824f16d78171fde2", - "cmd/object-multipart-handlers.go": "d9622c69c540ab32dd23916e3f534b6886473a98370c9dd17673e69a423b2a7e", - "cmd/replication-tagging-order_test.go": "64d6dfe3436970caeafcb914157bdedac5982a2105fe72c1753a8d68cf7ed6ef", - "cmd/replication-tagging-sender_test.go": "3770a1a48a6efe58fe8127e1e4fdf6bd7cf171e17db20f15222ea2f7b85db1af" - } -} diff --git a/docs/investigations/r5/implementation-review-response.md b/docs/investigations/r5/implementation-review-response.md deleted file mode 100644 index 40f47d5af..000000000 --- a/docs/investigations/r5/implementation-review-response.md +++ /dev/null @@ -1,26 +0,0 @@ -# R5 implementation review disposition - -Real reviewer: `claude-opus-5`, explicit `--effort max`, session `599b4759-add2-4a41-b5b2-865af7a2c096`. -Verdict: **GO_WITH_NONBLOCKING_NOTES; 0 blockers**. Raw review is preserved verbatim in `opus-implementation-review.md`; model usage, original plan/patch hashes and raw log location are in `opus-implementation-metadata.json`. - -The accepted v2 plan remains immutable. The following implementation notes supplement it; they do not retroactively change the hash on which plan consensus was reached. - -## Nonblocking notes - -- **N1 accepted:** a scheduled metadata COPY can rewrite object data when the receiver applies bucket-default/automatic KMS encryption. Its cost can therefore exceed metadata I/O. The existing completed-object/scanner and incoming-replica scheduling gates still prevent a feedback loop. No new transfer optimization or HEAD protocol is introduced. -- **N2 accepted:** a malformed recorded source tag timestamp fails sender construction and remains a retry failure until an explicit correct tag mutation/repair supplies a valid revision. A missing revision is different from a present invalid/empty value. No historical time is fabricated, and no automatic production rewrite is performed. -- **N3 retained scope:** existing marker/trust/REPLICA/version predicates are preserved. Production sender requests satisfy the relevant predicates; R5 does not broaden replication trust. -- **N4 accepted compatibility change:** a trusted metadata COPY without a source tag revision preserves stored tags, including the metadata-REPLACE shape. This is the deliberate missing-revision rule in plan C, and is tested under UUID/null versions and unqualified COPY. -- **N5 accepted:** ordinary COPY records its chosen tag state, including an empty REPLACE and unchanged tags during key rotation, as a fresh local event. This is consistent with the accepted last-writer-wins scheme. -- **N6 no change:** all production writers use the lowercase reserved timestamp key. Case-insensitive sender lookup is compatible with those writers and existing lock timestamp handling. - -## Coverage notes - -- **L1:** the review was supplied a passing run with **13**, not 12, top-level R5 tests. Its verdict explicitly did not claim execution of the wider tests. The wider selection reproduced the same `TestReplicationResync` order-dependent initialization panic on the unmodified production baseline; that test passes in isolation on both baseline and R5. Host-capacity and actual ENOSPC failures are retained, not reported as passes. Final related, race and static/build results are recorded separately in `verification.md`. An unfiltered full `cmd` package run remains an integration check before any later merge; this task delivers a local patch and does not claim that full-package or multi-site production gate passed. -- **L2 addressed:** after every incoming multi-pool replay, the R5 test now rereads the addressed version through normal pool routing and checks its empty value and deletion revision. The per-pool checks still inspect every retained copy. This prevents a vacuous pass if all copies disappear. The test deliberately allows existing duplicate suppression to retain both identical copies; existing pool cleanup/retry tests separately exercise retirement. -- **L3 accepted boundary:** the combined KMS cases exercise destination encryption and plaintext readback; source fixtures are populated through storage APIs. They do not establish encrypted-source-to-encrypted-destination replication across two running sites. SSE-C key rotation has its own signed HTTP and decrypted GET test. -- **L4 confirmed:** both the actual SDK default metadata directive and peer metadata-REPLACE shapes are exercised. - -## Changes after review - -Production code is unchanged from reviewed patch SHA256 `8f6f76ee874c43b0827fde272e8a947f118efb1c1af92bf4a02ef88f93554c1b`. Test-only follow-up adds the L2 normal-routing read and applies the repository's gofumpt formatting. `implementation-manifest.json` records the exact reviewed files; the final verification manifest records the final files, so the two versions are distinguishable. diff --git a/docs/investigations/r5/opus-implementation-metadata.json b/docs/investigations/r5/opus-implementation-metadata.json deleted file mode 100644 index 760cb1128..000000000 --- a/docs/investigations/r5/opus-implementation-metadata.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "model": "claude-opus-5", - "effort": "max", - "baseline": "dbcf8dec589deb5d91e17d295cb70997635f5b55", - "plan_version": "v2", - "plan_sha256": "5a782acf3f285b23d1ae43a73481c4eb772a9a6d917fc5a550ecfc7cbf7446ca", - "patch_sha256": "8f6f76ee874c43b0827fde272e8a947f118efb1c1af92bf4a02ef88f93554c1b", - "actual_assistant_models": [ - "claude-opus-5" - ], - "session_id": "599b4759-add2-4a41-b5b2-865af7a2c096", - "is_error": false, - "modelUsage": { - "claude-haiku-4-5-20251001": { - "inputTokens": 2125, - "outputTokens": 15, - "cacheReadInputTokens": 0, - "cacheCreationInputTokens": 0, - "webSearchRequests": 0, - "costUSD": 0.0022, - "contextWindow": 200000, - "maxOutputTokens": 32000, - "thinkingTokens": 0, - "canonicalModel": "claude-haiku-4-5", - "provider": "firstParty", - "costBasis": "list" - }, - "claude-opus-5": { - "inputTokens": 106, - "outputTokens": 64414, - "cacheReadInputTokens": 7275193, - "cacheCreationInputTokens": 236959, - "webSearchRequests": 0, - "costUSD": 7.618066499999999, - "contextWindow": 1000000, - "maxOutputTokens": 64000, - "thinkingTokens": 45128, - "canonicalModel": "claude-opus-5", - "provider": "firstParty", - "costBasis": "list" - } - }, - "result": "GO_WITH_NONBLOCKING_NOTES", - "blocking_items": 0, - "raw_output": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-implementation.jsonl" -} diff --git a/docs/investigations/r5/opus-implementation-prompt.md b/docs/investigations/r5/opus-implementation-prompt.md deleted file mode 100644 index 140acfa7d..000000000 --- a/docs/investigations/r5/opus-implementation-prompt.md +++ /dev/null @@ -1,27 +0,0 @@ -Review the actual R5 implementation independently for correctness and regressions, using Claude Opus 5 at max effort. This is a read-only final code review after an already recorded two-round plan consensus. Do not edit files. Do not simulate tests or claim you executed them. Read the relevant source and evidence yourself; focus on material blockers and minimal compatible fixes. - -Working tree: /Users/vonng/.codex/worktrees/77ad/silo -Base dependency commit: dbcf8dec589deb5d91e17d295cb70997635f5b55 (R4 KMS timestamp field, one production addition) -R5 plan v2 SHA256: 5a782acf3f285b23d1ae43a73481c4eb772a9a6d917fc5a550ecfc7cbf7446ca -R5 implementation patch SHA256: 8f6f76ee874c43b0827fde272e8a947f118efb1c1af92bf4a02ef88f93554c1b -Manifest: /Users/vonng/.codex/worktrees/77ad/silo/docs/investigations/r5/implementation-manifest.json -Frozen review patch (7 production files + 2 new tests): /Users/vonng/tmp/silo-r5-20260915-77ad/implementation-v1.patch -Plan: /Users/vonng/.codex/worktrees/77ad/silo/docs/investigations/r5/plan-v2.md -Prior actual review: /Users/vonng/.codex/worktrees/77ad/silo/docs/investigations/r5/opus-v2-review.md -Consensus and disagreements: /Users/vonng/.codex/worktrees/77ad/silo/docs/investigations/r5/consensus.md, /Users/vonng/.codex/worktrees/77ad/silo/docs/investigations/r5/opus-v1-response.md -Baseline reproduction: /Users/vonng/.codex/worktrees/77ad/silo/docs/investigations/r5/reproduction.md -Latest new regression run: /Users/vonng/tmp/silo-r5-20260915-77ad/fixed-targeted-latest.log (PASS, 9.161s; signed HTTP and actual storage on single/16 disks, null/UUID, COPY default and REPLACE, PUT/multipart, KMS plaintext GET, SSE-C rotation, multi-pool, retry and stale source ACK; test names and details in source.) -Additional related suite and race/static validation are ongoing and are not yet accepted. An expanded suite hit existing TestReplicationResync initialization panic before R5 tests; baseline isolation is ongoing. Do not treat that as a proven R5 regression or a passing test. - -Research and implementation points to scrutinize: -1. Empty tag values are ordered states only with recorded timestamp; no fabricated tombstone for empty legacy object. Nonempty legacy sender falls back to ModTime. Malformed stored timestamp fails PUT and metadata COPY sender construction. -2. Local PUT/DELETE tagging timestamps are unconditional, advance under existing storage locks; pooled mutation computes a single revision > every copy; do not mutate caller map. Replicas keep source ordering and equal timestamp stored-wins. -3. Actual sender getCopyObjMetadata + minio Core.CopyObject sends tagging REPLACE with no metadata directive; peers may also send metadata REPLACE. Capture stored timestamp before reconstruction, accept incoming empty value, prevent stale SSE-C encMetadata snapshot overwrite, keep existing storage rechecks. -4. PUT/multipart init persist parsed trusted timestamp. Matching ETag/version no-op is relaxed only for trusted newer tag revision, retaining explicit client preconditions and SSE-C behavior. Multipart completion rechecks revision under lock. -5. Equal visible tag values can hide a newer deletion/re-addition revision, so scheduled metadata/heal work with recorded timestamp sends metadata. Preserve existing Completed/scanner and incoming no-reschedule gates. -6. Replication status ACK no longer writes old queued ri.UserTags over the current tags. Preserve current value/timestamp under metadata lock including multi-pool. -7. Scope explicitly excludes tag-filter target eligibility changes, historical tombstone recovery, arbitrary independent unversioned content replacements, clock skew consensus, and production multi-site acceptance. R4 owns object-api-options.go; it is not in this R5 diff. - -There was an earlier KMS test-only fixture error: it assigned a single-PUT ETag to a multipart-layout retransmission; corrected by seeding a real multipart source and using its actual ETag/layout (full latest run passes plaintext read). Inspect the fixture rather than inferring production encryption correctness solely from prior failed test output. - -Return a clear GO / GO_WITH_NONBLOCKING_NOTES / REQUEST_CHANGES verdict; blocking count; concrete source locations with cause, trigger and effect for any blockers; tests/coverage limitations if material. State which baseline, plan hash and patch hash you reviewed (you may quote supplied hash, but cannot independently compute with Read/Grep/Glob). Do not demand unrelated broad redesign. Distinguish actual source defect from nonblocking improvement or scope limitation. diff --git a/docs/investigations/r5/opus-implementation-review.md b/docs/investigations/r5/opus-implementation-review.md deleted file mode 100644 index a15e68b61..000000000 --- a/docs/investigations/r5/opus-implementation-review.md +++ /dev/null @@ -1,58 +0,0 @@ -Write is disabled in this session, so I'll deliver the review directly rather than to the plan file. - -# R5 implementation review — tag deletion and ordered replication - -## Verdict: **GO_WITH_NONBLOCKING_NOTES** — blocking count: **0** - -Read-only review. No files edited, no tests run, no results simulated. Every pass I cite is quoted from the supplied log. - -## What I reviewed - -| Item | Value | -|---|---| -| Working tree | `/Users/vonng/.codex/worktrees/77ad/silo`, branch `codex/r5-tag-deletion-ordering` | -| Baseline dependency | `dbcf8dec589deb5d91e17d295cb70997635f5b55` (R4 SSE-KMS field) | -| Plan | `plan-v2.md`, SHA256 `5a782acf…7fc7446ca` *(quoted)* | -| Patch | `implementation-v1.patch`, SHA256 `8f6f76ee…f88f93554c1b` *(quoted)* | - -**Hash caveat:** with Read/Grep/Glob I **cannot compute** either SHA256 — both are quoted from the manifest/task. What I did verify by reading is that every hunk in the frozen patch is present verbatim in the working tree across all seven production files. That is source identity by inspection, not by digest. I also confirmed R5 does not touch `cmd/object-api-options.go`; the R4 branch carrying `ReplicationSourceTaggingTimestamp` (`object-api-options.go:449-460`) is unmodified. - -## Per-claim findings - -**1. Empty values are ordered states; no fabricated legacy tombstone.** Confirmed. `replicationTaggingTimestamp` (`bucket-replication.go:786-794`) returns the recorded stamp even with empty tags, falls back to `ModTime` only for non-empty tags, zero otherwise. Used by both `putReplicationOpts` (`:861-870`) and the metadata sender (`:1702-1707`). The SDK omits the header for a zero time (`minio-go@…60bd07042d49/api-put-object.go:236-238`, `api-compose-object.go:286-288`), so "no revision" really travels as absence. Malformed stamps fail both constructions. - -**2. Local revisions unconditional and monotonic.** Confirmed. Both handlers mint one `UTCNow()` outside the `dsc.ReplicateAny()` branch (`object-handlers.go:3773-3778`, `:3876-3881`); `getOpts` leaves `opts.UserDefined` nil (`object-api-options.go:110`,`:39`), so the unconditional map replacement drops nothing. `er.PutObjectTags` applies the guard under the existing NS lock (`erasure-object.go:2273-2282`, `:2330-2334`); an absent stamp yields `""` and preserves legacy direct-storage semantics. `z.PutObjectTags` folds one candidate strictly beyond every copy and **clones** first (`erasure-server-pool.go:3054-3062`) — `opts` is a value parameter and `er.PutObjectTags` never writes `opts.UserDefined`, so no caller map is mutated. No replica path reaches `PutObjectTags` (the only two production callers are the tagging handlers), so replicas keep strict source ordering via `reconcileStoredObjectTags`, stored-wins on ties (`erasure-server-pool-consistency.go:238-242`). - -**3. COPY receiver.** Confirmed. Stored pair captured before reconstruction (`object-handlers.go:1800`); `srcInfo.UserTags` is never reassigned between the source read and the decision, so it genuinely is stored state. The decision block (`:1818-1837`) accepts an incoming empty value with a stamp and rechecks the captured state; all existing in-lock rechecks still run (`erasure-object.go:136-138`, `:1312-1315`; `erasure-multipart.go:1161-1190`; `erasure-server-pool.go:1443-1450`). The `encMetadata` fix (`:1840`) is safe and correctly placed — `encMetadata` receives reserved keys only on the SSE-C rotation path (`:1655-1659`), and the delete lands after `rotateKey`/`newEncryptReader` and before the merge at `:1910`. - -**4. PUT/multipart persistence and the duplicate exception.** Confirmed. `putOptsFromHeaders` aliases `opts.UserDefined = metadata` in both branches (`object-api-options.go:451`,`:464`), so post-build writes reach storage (`object-handlers.go:2323-2325`; `object-multipart-handlers.go:315-318`, correctly *after* `maps.Copy(metadata, encMetadata)` at `:300`). The relaxation (`object-handlers-common.go:243-246`) sits below the explicit `If-Match`/`If-None-Match` checks, is gated on `isReplicaTrusted` + `olderThan` (zero source never wins, `bucket-object-lock.go:370-376`), and leaves the SSE-C exemption intact. It cannot loop: once the write lands the stamps are equal and the next attempt 412s. `completeMultipartOpts` sets neither `PreserveETag` nor a tagging timestamp (`object-api-options.go:501-550`), so completion needs no new exception and reconciles under the lock (`object-multipart-handlers.go:1201`). - -**5. Equal values can hide a newer revision.** Confirmed and correctly scoped. The gate (`bucket-replication.go:1013-1018`) sits after the null-version resync exclusion and after **every** branch that can return `replicateAll`; from there only `replicateMetadata`/`replicateNone` are reachable, so it can never downgrade a needed full transfer. It is reached only from `replicationActionForTarget` → `replicateAll` (`:1608`), not from the object-replication fast path (`:1328-1343`). The Completed gate (`:3775`) and failures-only requeue (`:1316`) bound the work, and an incoming replica COPY schedules no outgoing event. Existing fixtures carry no tagging stamp (`bucket-replication_test.go:716-739`), so they are unaffected. - -**6. ACK no longer overwrites current tags.** Confirmed removed (`bucket-replication.go:1276-1286`). Preservation holds on both write-backs: `er.PutObjectMetadata` copies from `ObjectInfo.UserDefined`, which `cleanMetadata` strips of `x-amz-tagging` (`object-api-utils.go:403-407`; `erasure-object.go:2254-2260`); `updatePoolMetadata` falls back to merged `UserTags` and rewrites the merged newest stamp (`erasure-server-pool-consistency.go:194-214`). Both under the object lock (`erasure-object.go:2196-2205`; `erasure-server-pool.go:3020-3029`). The sender also re-reads current state first (`bucket-replication.go:1527-1550`). - -**7. Scope.** Respected — no tag-filter eligibility change, no historical tombstone invention, no clock-skew consensus, no `object-api-options.go` change. - -**Trust boundary re-checked:** the reserved key cannot be injected from the wire — `containsReservedMetadata` rejects the whole `X-Minio-Internal-` class outside the SSE allowlist (`generic-handlers.go:75-85`), and `extractMetadataFromMimeWithReplication` maps only `replicationToInternalHeaders` (`handler-utils.go:258-298`). - -**KMS fixture inspected directly**, not inferred from prior output: the multipart case now seeds a real multipart source and reuses its actual ETag/part layout (`replication-tagging-order_test.go:472-489`). The earlier single-PUT-ETag mismatch is gone. See L3 for what it still does not cover. - -## Non-blocking notes (no change required) - -- **N1 — on encrypted destinations the forced metadata COPY is not metadata-only.** The gate at `bucket-replication.go:1013-1018` yields a replica COPY; with bucket-default/auto KMS the destination applies SSE before `copyDstOpts` (`object-handlers.go:1428-1433`) and then clears `srcInfo.metadataOnly` (`:1669-1677`) — so it **rewrites object data**. Bounded to one COPY per object entering heal and one per object per explicit resync (not a loop), but the plan's "extra metadata I/O" understates this case. Worth a sentence in the cost note. -- **N2 — fail-closed on a malformed stored revision is terminal for that object** (`:786-794` → `:1702-1707`/`:867-870`, requeued by MRF at `:1316-1322`). No production writer can produce such a value, so this is a defensive tail. Note the asymmetry: storage self-heals the same corruption (invalid *stored* ⇒ incoming wins, `erasure-server-pool-consistency.go:238-242`) while the sender refuses to proceed. The minimal hardening, if ever wanted, is to treat a present-but-**empty** value as absent — I traced no reachable path producing one, and the current behavior is what plan and consensus chose, so I am not asking for it. -- **N3 — trusted-marker vs REPLICA asymmetry (pre-existing).** `object-handlers.go:2323` / `object-multipart-handlers.go:316` persist on `opts.ReplicationRequest`, while the in-lock recheck needs `isReplicaTrusted` **and** a version ID (`:2442`). Production sets both; the new precondition exception uses the stricter predicate. Accepted in plan §D. -- **N4 — a trusted metadata COPY with no source revision now ignores the request's tag value** (`object-handlers.go:1826-1833`). For a peer sending `x-amz-metadata-directive: REPLACE` without a revision, the value used to land (`X-Amz-Tagging` is in `supportedHeaders`, `handler-utils.go:271-283`). No MinIO sender produces that shape, and the R4 case `object-copy-replication-tagging_test.go:89` already expects stored-wins there, reaching it via the storage reconcile. Deliberate per plan §C. -- **N5 — ordinary COPY always writes an explicit tag value plus a fresh revision** (`:1834-1837`). (a) `x-amz-tagging-directive: REPLACE` with no tags now genuinely clears the destination, where the default-metadata path used to carry source tags forward — an S3 conformance improvement, covered by `TestAPILocalTaggingAlwaysAdvancesRevision`. (b) An in-place key-rotation COPY advances the revision without changing any value, re-asserting current tags against an older in-flight remote deletion. Both follow from last-writer-wins as specified. -- **N6 — cosmetic.** The key is read case-insensitively at `bucket-replication.go:787`/`:1016`, exactly elsewhere. `TaggingTimestamp` is lowercase (`:74`) and storage writes only lowercase, so they agree; the same mix already exists for lock timestamps in that file (`:896` vs `:1708`). - -## Tests and coverage limitations (material) - -- **L1 — the only established green result is the 12 R5 tests** (`fixed-targeted-latest.log`, `ok … 9.161s`): signed HTTP through real single-disk and 16-disk storage, null/UUID, COPY default and REPLACE, PUT/multipart, KMS, SSE-C rotation, multi-pool, sender retry and stale ACK. The wider `cmd` package, `-race`, `gofmt` and `git diff --check` are ongoing, and the `TestReplicationResync` panic is unattributed. I treat that as an **open verification item**, not a regression and not a pass. My static read found no existing test whose expectations R5 flips — I checked the `getReplicationAction` fixtures, the R4 KMS COPY table including its `missing-timestamp` case (R5 satisfies it via the handler instead of the storage reconcile), and confirmed no pre-existing test calls `PutObjectTags` with a supplied revision. -- **L2 — multi-pool convergence after an *incoming* replica write is asserted weakly.** `replication-tagging-order_test.go:688-698` skips any pool whose version is absent and only asserts a retained copy is correct; it pins neither which pool is retained nor that retirement ran. The case that matters for §A — one revision strictly greater than every copy, in the response *and* every stored copy — is pinned by `TestLocalTaggingCommitCannotRegressRevision`. -- **L3 — the KMS variant's source objects are plaintext.** Seeds use `obj.PutObject` (`:468`, `:477-488`), bypassing handler encryption, so encryption enters only via the incoming request and the destination bucket default. Real coverage of the R4 field on the receive side, but not encrypted-source-to-encrypted-destination end to end. The trailing plaintext GET (`:520-523`) does establish the final object is readable. -- **L4 — wire shape is well pinned.** `TestTaggingProductionCopyWireShape` asserts the real SDK request (`metadata-directive=""`, `tagging-directive=REPLACE`) against a live peer, and `r5Receive`'s `"copy"` operation covers peer metadata-REPLACE independently. Both required shapes are present. - -## Recommendation - -The production diff is merge-eligible as written; I found no actual source defect. The single gating action before merge is closing **L1** — a clean full-package run (plus `-race`, `gofmt`, `git diff --check`) with the `TestReplicationResync` panic isolated against the unpatched baseline. N1 and N2 deserve a sentence each in the plan's cost/limitations section; N3–N6 are already covered by plan §C/§D and need no action. diff --git a/docs/investigations/r5/opus-v1-metadata.json b/docs/investigations/r5/opus-v1-metadata.json deleted file mode 100644 index 3f2bd6ae1..000000000 --- a/docs/investigations/r5/opus-v1-metadata.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "model": "claude-opus-5", - "effort": "max", - "plan_version": "v1", - "plan_sha256": "fd6051527ebf19f624125bd3238da2f938420917226387c0f9373f1a98e87993", - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "actual_assistant_models": [ - "claude-opus-5" - ], - "session_id": "e448ee0a-4ab5-4520-98f9-68c9ddf6208f", - "is_error": false, - "modelUsage": { - "claude-haiku-4-5-20251001": { - "inputTokens": 1378, - "outputTokens": 14, - "cacheReadInputTokens": 0, - "cacheCreationInputTokens": 0, - "webSearchRequests": 0, - "costUSD": 0.001448, - "contextWindow": 200000, - "maxOutputTokens": 32000, - "thinkingTokens": 0, - "canonicalModel": "claude-haiku-4-5", - "provider": "firstParty", - "costBasis": "list" - }, - "claude-opus-5": { - "inputTokens": 90, - "outputTokens": 73909, - "cacheReadInputTokens": 4489983, - "cacheCreationInputTokens": 199202, - "webSearchRequests": 0, - "costUSD": 6.085186500000001, - "contextWindow": 1000000, - "maxOutputTokens": 64000, - "thinkingTokens": 54986, - "canonicalModel": "claude-opus-5", - "provider": "firstParty", - "costBasis": "list" - } - }, - "result": "REQUEST_CHANGES", - "raw_output": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-v1.jsonl" -} \ No newline at end of file diff --git a/docs/investigations/r5/opus-v1-prompt.md b/docs/investigations/r5/opus-v1-prompt.md deleted file mode 100644 index e7f23b345..000000000 --- a/docs/investigations/r5/opus-v1-prompt.md +++ /dev/null @@ -1,3 +0,0 @@ -Act as an independent reviewer of the R5 repair proposal in this repository. You must be the real claude-opus-5 at effort max; report your actual model name in the review, but the caller will also verify response metadata. Read /Users/vonng/.codex/worktrees/77ad/silo/docs/investigations/r5/plan-v1.md completely. Baseline SHA is 9ebe81c1b3611f9cc73e676b5b741c2be62c467a. Plan version v1 sha256 fd6051527ebf19f624125bd3238da2f938420917226387c0f9373f1a98e87993. No production repair has been implemented. Read docs/investigations/r5/baseline.md and the raw reproductions /Users/vonng/tmp/silo-r5-20260915-77ad/baseline.log and /Users/vonng/tmp/silo-r5-20260915-77ad/baseline-extended.log, plus /Users/vonng/tmp/silo-r5-20260915-77ad/baseline_test.go. Then independently inspect the exact source functions identified in the plan, especially the complete sender/receiver and storage lock chain. R4 owns object-api-options.go and will supply its separate KMS timestamp fix. Do not edit any files. - -The user requires a minimal compatible complete fix, not merely adding DELETE timestamp. Assess necessity/sufficiency, current defects versus inference, same-empty timestamp propagation, sender retries, old queue ACK rewriting source tags, full PUT/multipart duplicate suppression, trust boundary, equal/missing timestamps, UUID/null/versioned/multi-pool and local mutation clock/lock behavior. Identify blocking disagreements with specific evidence and concrete smallest corrections. Explicitly say APPROVE or REQUEST_CHANGES for this exact v1 hash and list accepted/nonblocking/required changes. Do not claim consensus if any blocking issue remains. Be exact about limitations and whether proposed test coverage can establish the scope. Use the permitted Read/Grep/Glob tools for source verification. Return a substantive review, not just a summary. diff --git a/docs/investigations/r5/opus-v1-response.md b/docs/investigations/r5/opus-v1-response.md deleted file mode 100644 index 0282af778..000000000 --- a/docs/investigations/r5/opus-v1-response.md +++ /dev/null @@ -1,30 +0,0 @@ -# Response to actual Opus v1 review - -The original review is retained verbatim in opus-v1-review.md, with actual model identity/usage in opus-v1-metadata.json and the full stream in the raw evidence directory. Result was REQUEST_CHANGES, five blockers. No implementation was started and no consensus is implied by the response below. - -## Blocking items - -| Item | Disposition for v2 | -|---|---| -| R1 empty/no-revision ModTime fallback | Accepted. Never synthesize a revision for empty tags without one. Keep only existing nonempty ModTime fallback. Add no-revision wire regression. | -| R2 force metadata only for empty values | Disagree with the proposed restriction; accept the I/O cost warning. Same nonempty values can carry different revisions: X@T1, delete@T2, re-add X@T3. Skipping T3 lets delayed delete T2 incorrectly win. A recorded revision requires delivery for either value. v2 explicitly accepts extra COPY per scheduled/resync invocation. queueReplicationHeal already skips Completed unless resync requested; replicateObject only requeues Failed. Thus the predicate is permanently conservative, but it does not create perpetual background work. Avoiding a new HEAD protocol is the smaller implementation. Re-review required. | -| R3 actual metadata COPY request shape | Accepted after inspecting the pinned minio-go Core.CopyObject/copyObjectDo. getCopyObjMetadata supplies only tagging REPLACE; the SDK adds no metadata directive. Update provenance and test both actual SDK shape and peers using metadata REPLACE. | -| R4 local revision inversion | Accepted and strengthened for uniform multi-pool persistence. er.PutObjectTags advances a valid supplied revision beyond stored time under lock. z.PutObjectTags computes one value beyond every addressed copy before writing, so the response, ordinary source read and all copies agree. Per-pool-only guards can produce different times; mergedPoolObjectInfo is not every ordinary read path, so relying on later merge is insufficient for a precise source revision. Direct calls without valid supplied revisions keep old semantics. | -| R5 duplicate suppression wording | Accepted. Explicitly use strictly-newer-than-stored, with existing olderThan semantics. Preserve client preconditions; only trusted REPLICA source timestamps can relax version/ETag duplicate suppression. Document possible data re-upload cost. | - -## Nonblocking items - -- Equal times: document stored-wins consistency across COPY and storage, including null/unqualified requests. -- Invalid COPY sender timestamp: fail the metadata send with Failed, as PUT option construction does; no silent fallback. -- Tag trust versus replica trust: preserve existing predicates, document production supplies both. No permission relaxation. -- KMS nuance: agree PUT/multipart depend on R4, but disagree that metadata COPY never depends on R4. Destination bucket defaults and globalAutoEncryption inject KMS before copyDstOpts at object-handlers.go 1425–1435. R4 independently reproduced all three explicit/default/auto entrypoints. Do not adopt the inaccurate broader exclusion. Combined tests required. -- Ordinary whole-object replacement/no-version clocks: document unchanged semantics. This plan addresses local tagging mutation and selected per-hop replicated version updates; it does not create a new conflict model for independent unversioned content overwrites. -- Tag-filtered target eligibility: document the pre-existing scope limitation; no selection/rule protocol redesign in R5. Final result must not claim arbitrary configuration convergence. -- ACK: additionally reproduced on both real storage backends in baseline-ack.log. The source revives `key=queued` with the deletion's timestamp after old queue event completion. Remove the stale assignment; storage preserves current state. -- Storage lock recheck: reuse and keep existing error behavior. The tests establish per-hop behavior, not a production multi-site deployment or physical clock-skew experiment. - -## Added evidence - -`/Users/vonng/tmp/silo-r5-20260915-77ad/matrix_test.go` contains temporary signed HTTP UUID/null tests, exact SDK COPY wire capture, local timestamp inversion, multi-pool deletes and SSE-C rotation. The original matrix fails baseline as expected. `discussion-baseline.log` isolates R2's equal nonempty case, R3's real SDK shape, and R4's commit inversion. A short first compile missed a test import and was corrected; only the subsequent compile/run is behavioral evidence. - -The v2 plan, not this commentary, is the next consensus target. Production diff remains empty. diff --git a/docs/investigations/r5/opus-v1-review.md b/docs/investigations/r5/opus-v1-review.md deleted file mode 100644 index f2157c242..000000000 --- a/docs/investigations/r5/opus-v1-review.md +++ /dev/null @@ -1,111 +0,0 @@ -Write is disabled in this session, so the review is delivered here in full. - -# Independent review — R5 repair plan v1 - -**Reviewer model: `claude-opus-5` (Opus 5), effort max.** I read `docs/investigations/r5/plan-v1.md` completely, `docs/investigations/r5/baseline.md`, both raw logs, `baseline_test.go`, and then independently inspected the sender/receiver/storage chain at `9ebe81c1b`. - -**VERDICT: REQUEST_CHANGES** for plan v1 sha256 `fd6051527ebf19f624125bd3238da2f938420917226387c0f9373f1a98e87993`. Five blocking corrections (R1–R5). **No consensus is claimed.** - -Binding caveat, stated exactly: this session has read-only tools, so I did not execute `sha256sum`. I reviewed the file content at that path at the baseline SHA and cite the plan's own line numbers so you can bind findings to the hash. - ---- - -## 1. Claims I confirmed from source (necessity, not inference) - -| Plan | Claim | Evidence | Verdict | -|---|---|---|---| -| L14 | PUT tagging stamps only when replication is selected; DELETE never stamps | `cmd/object-handlers.go:3762-3768`, `:3865-3870` | **correct** (and PUT uses two separate `UTCNow()` calls, 3765/3767) | -| L14 | both write under the existing object lock; multi-pool updates the addressed version in each copy | `cmd/erasure-object.go:2272-2337`; `cmd/erasure-server-pool.go:3033-3075` | **correct** | -| L15 | `putReplicationOpts` stamps only inside nonempty `UserTags`; multipart clears SourceMTime | `cmd/bucket-replication.go:849-863`, `:1776` | **correct** | -| L16 | metadata COPY carries an explicit empty tag; ModTime default only for nonempty | `:747`, `:1692-1700`; SDK `copyObjectDo:262-264` writes the map verbatim | **correct** | -| L18 | PUT / multipart initiation parse but never persist the source stamp | `cmd/object-api-options.go:473`; no writer anywhere in `cmd/` | **correct** (matches `baseline-extended.log:19-26`) | -| L18 | multipart completion already rechecks the persisted upload under the object lock | `cmd/erasure-multipart.go:1161-1190` | **correct** | -| L19 | `getReplicationAction` compares values/counts, not ordering time | `cmd/bucket-replication.go:1000-1005` | **correct** | -| L20 | `checkPreconditionsPUT` skips matching version/ETag for non-SSE-C replicas | `cmd/object-handlers-common.go:233-247` | **correct**; and senders treat 412 as delivered (`:1466`; multipart `:1786-1788` returns `nil`) | -| L21 | ACK callback copies stale `ri.UserTags` | `cmd/bucket-replication.go:1272-1274` | **correct, and worse than stated** | -| L23 | `reconcileStoredObjectTags` gates as described | `cmd/erasure-server-pool-consistency.go:232-243` | **correct** | - -Two amplifiers the plan does not name, both strengthening it: - -- The ACK callback writes stale tags **without** a timestamp. The revived tag set therefore inherits the *deletion's newer* revision and propagates downstream as authoritative. Removal is the right fix and is sufficient: `er.PutObjectMetadata` preserves `fi.Metadata`'s tag key (`cmd/erasure-object.go:2260`) and `updatePoolMetadata` falls back to merged `UserTags` (`cmd/erasure-server-pool-consistency.go:194-214`). -- The `encMetadata` merge at `cmd/object-handlers.go:1903` restores every reserved key snapshotted at `:1655-1659`; the guard at `:1855-1864` covers only the two Object Lock stamps. Tag revision is genuinely exposed, so L47 is justified. - ---- - -## 2. Blocking disagreements - -### R1 — Do not synthesize a ModTime revision for objects with no tags and no revision -**Where:** L35 ("otherwise object ModTime (also for empty legacy objects)") composed with L49 ("persist a nonzero parsed trusted source timestamp"). - -**Evidence:** `PutObjectOptions.Header()` emits the header whenever `TaggingTimestamp` is non-zero (SDK `api-put-object.go:236-238`). If L35 moves selection outside the nonempty branch *and* defaults to ModTime, every replicated object — including every object that has never carried a tag — ships a non-zero stamp, and L49 persists it. Every object on the destination then owns a tag revision. Composed with L39 (recorded revision ⇒ force metadata replication), **every object at the next hop always selects metadata replication.** It also contradicts L10 ("not a reason to change the storage format") and L57 ("we do not invent historical deletion times"). - -**Smallest correction:** send a stamp only when `objInfo.UserTags != ""` **or** a recorded revision exists. That keeps the tombstone case (empty + revision — the entire point), keeps the existing nonempty ModTime fallback, and drops only empty + no-revision, which L57 already declares unrecoverable. This makes §B consistent with §D. - -### R2 — Bound the forced metadata replication in `getReplicationAction` -**Where:** L39. - -**Evidence:** the destination's revision is invisible to HEAD, so the condition never becomes false. Any object carrying a revision never returns `replicateNone` again: every heal, MRF retry and `ExistingObjectReplicationType` resync re-COPIES its metadata, rewriting `xl.meta` on the destination (and, multi-pool, running `retireReplicaCopies`) each pass. L39's "extra COPY only for already-scheduled work" understates a permanent non-convergence. Existing tests won't catch it — `newMatchingReplicationPair` (`cmd/bucket-replication_test.go:716-739`) carries no revision. - -**Smallest correction:** fire only when `oi1.UserTags == ""` and a revision is recorded — exactly the empty-to-empty tombstone L19 names and `TestReviewR5SameEmptyTagsMustTransferTimestamp` asserts. Nonempty states are already caught by the existing value/count comparison at `:1003`. Then state the residual: tag-deleted objects still never converge to `replicateNone`. - -### R3 — The production metadata COPY does not send `x-amz-metadata-directive: REPLACE` -**Where:** L17. - -**Evidence:** `getCopyObjMetadata` sets `x-amz-tagging-directive: REPLACE` (`:748`) but never the metadata directive, so `getCpObjMetadataFromHeader` takes the `defaultMeta` branch (`cmd/object-handlers.go:1143,1165-1170`). Therefore: -1. "its REPLACE metadata map also loses the previous timestamp before comparison" is **false on the production path** — `defaultMeta` preserves the stored revision. It is true only for a peer that does send REPLACE. -2. The empty tombstone is dropped for a *different* reason than the plan gives: `defaultMeta` carries the stored `X-Amz-Tagging` forward and the `objTags != ""` gate at `:1817` skips the overwrite. (Note `X-Amz-Tagging` is in `supportedHeaders`, `cmd/handler-utils.go:84`, so on the REPLACE path the empty value *does* arrive in the map — only the stamp is missing.) -3. The reproduction sends `x-amz-metadata-directive: REPLACE` (`baseline_test.go:134`), so it **does not pin the production request shape.** The conclusion still holds (with a stale stored stamp the delayed COPY wins either way), but the evidence chain as written is not the one production executes. - -**Smallest correction:** fix L17, and add a sender-shaped COPY case asserting against `getCopyObjMetadata` output rather than a hand-built header map. - -### R4 — Missing monotonic guard on the local revision at commit -**Where:** L31 explicitly asks the reviewer to decide. My answer: commit-time *generation* is not required; a commit-time monotonic *guard* is. - -**Evidence:** `er.PutObjectTags` writes `fi.Metadata[x-amz-tagging]` and copies `opts.UserDefined` with **no ordering check** (`cmd/erasure-object.go:2328-2330`), and the handler mints the stamp *before* the namespace lock. R5 newly makes DELETE mint a revision, so a DELETE→PUT pair can invert — via lock queueing (`globalOperationTimeout` waits) or clock skew between the two nodes serving the two requests. Result: source holds `tags=X @ t_old`, replica holds the tombstone `@ t_new`. Every retransmit is then rejected by `reconcileStoredObjectTags` (`stamp.Before(incoming)` false), the sender still records **Completed**, and — with R2's rule — re-sends forever. Permanent, silent divergence: precisely the failure class R5 exists to remove, newly broadened by change A. - -**Smallest correction:** in `er.PutObjectTags`, under the lock, if the incoming revision is not strictly after the stored one, advance it to stored + 1ns. Multi-pool is safe without a second site of change: `z.PutObjectTags` writes identical tags to all copies, so any per-pool stamp differences still merge to a consistent `(tags, newest stamp)` pair through `mergedPoolObjectInfo` (`cmd/erasure-server-pool-consistency.go:124-131`). - -### R5 — Under-specified duplicate-suppression comparison -**Where:** L51, "a valid newer source tag timestamp makes matching version/ETag insufficient". - -**Evidence:** read naively as "non-zero source stamp ⇒ bypass", this disables the duplicate guard for *every* tagged replica write; for multipart it re-uploads all parts, since 412 at initiation is currently the cheap exit (`:1786-1788`). `TestReviewR5NewerTagsMustBypassContentDuplicate` already encodes the correct comparison (source stamp vs. `oi`'s stored stamp), but the prose does not. - -**Smallest correction:** one sentence — "strictly newer than the destination's stored tag revision" — plus the cost note that even correctly scoped, this re-PUTs object data to deliver a tag-only change. - ---- - -## 3. Accepted / non-blocking (state them; do not necessarily fix) - -- **§A local generation semantics** (L29): accepted as sufficient, subject to R4. Use one `UTCNow()` for both stamps as proposed. -- **§B ACK removal** (L41): necessary and sufficient; preservation verified on both the single-pool and pooled write-back paths. -- **§C `encMetadata` reconciliation** (L47): accepted. Today's observable effect is fail-closed (update dropped) when `ReplicaLockReconcile` is on, and a mismatched `(new tags, old stamp)` pair when `VersionID == ""` — worth one sentence. -- **Equal timestamps:** adopting `reconcileStoredObjectTags` in the handler silently flips the non-versioned COPY path from "incoming wins on equal" (`cmd/object-handlers.go:1824`, `!ondiskTimestamp.After(srcTimestamp)`) to "stored wins on equal". This is the right direction and removes a real handler/storage inconsistency, but it is a compat-visible change and belongs in §D. -- **Missing/invalid timestamps:** the gate asymmetry is correct as the plan describes — invalid *stored* ⇒ incoming wins; invalid *incoming* with valid stored ⇒ stored wins. Note the metadata COPY sender swallows parse errors (`:1696-1699`, `if err == nil`); L35's fail-loud rule should cover that call site too. -- **Trust boundary: sound.** Stamp honored only under `trustedReplication` (`cmd/object-api-options.go:390-396`); headers stripped otherwise (`cmd/replication-trust.go:96-112,139-143`); client-supplied reserved headers rejected wholesale (`cmd/generic-handlers.go:75-85`). One asymmetry to resolve deliberately: the tag decision keys on `dstOpts.ReplicationRequest` (trusted marker) while Object Lock keys on `replicaTrusted` (marker + REPLICA). Production sets both. -- **SSE-KMS / R4 boundary — more precise than the plan:** the KMS early return (`cmd/object-api-options.go:431-461`) drops `ReplicationSourceTaggingTimestamp`, so R5's PUT/multipart persistence is a **silent no-op for KMS-header requests** until R4 lands. The metadata-COPY leg is **not** affected: the public SSE header is synthesized only in responses (`cmd/api-response.go:525-533`), so `getCopyObjMetadata` never forwards it and tag deletions still order correctly for SSE-KMS objects via COPY. Say this instead of only "KMS combined tests after R4". -- **Ordinary PUT/multipart with tags writes no revision,** so §A's invariant has a hole. Mostly masked by versioning; exposed on null/suspended versions, where `ReplicaLockReconcile` is off anyway (`:1847`, `:2432`). Either stamp there too (~3 lines) or document. -- **Tag-filtered replication rules never see a deletion:** `FilterTargetArns` runs on the *post*-deletion `UserTags` (`cmd/bucket-replication.go:1189-1193`; `scheduleReplication` is called with the post-write `oi` at `cmd/object-handlers.go:3879`), so a rule with a tag filter yields no target. Pre-existing, but it bounds any completeness claim. -- **Sender retries are otherwise clean:** `replicateAll` re-reads current state (`:1517`), so retries carry the current revision; staleness was confined to the ACK callback. No new HEAD protocol — agreed. -- **UUID / null / versioned / multi-pool:** `metadataPoolInfos`, `mergedPoolObjectInfo`, `retireReplicaCopies` and the `nullVersionID` normalization (`:136-138`, `:1162-1168`) do carry tag state correctly; I found no additional loss path there beyond R4's clock inversion. - ---- - -## 4. Can the proposed coverage establish the scope? - -**Can:** per-hop handler + storage behavior on real erasure disks (ErasureSD and 16-drive), multi-pool merge/retire through `consistencyPools` and the existing tag suite (`cmd/erasure-server-pool-tags_test.go`), sender option construction, and the ACK race with a deterministic interleave. - -**Cannot:** cross-site convergence, real clock skew, real lock contention. Both `ExecObjectLayerAPITest` instances run in one process against one clock. L73's "Not production multi-site acceptance" should be sharpened to say the suite establishes **per-hop** correctness only. - -**Must add:** (a) an untagged-object regression asserting **no** revision is synthesized (R1); (b) the sender-shaped metadata COPY (R3); (c) a DELETE→PUT inversion, by passing an older stamp directly at the storage call (R4); (d) equal-timestamp parity across the versioned and null-version COPY paths; (e) an SSE-KMS metadata COPY case marked blocked-on-R4. Also keep the three assertions inside `TestReviewR5DeleteThenDelayedTagUpdate` separable — once change A lands, the first passes and the later two silently depend on it. - ---- - -## 5. Summary - -- **Accepted:** §12.1/.2/.3/.5/.6/.7/.8 defect claims; §A generation model; §B ACK removal; §C `encMetadata` reconciliation, storage-recheck reuse, multipart persistence; §D R4 ownership and the no-migration position. -- **Non-blocking:** equal-timestamp behavior change, trust asymmetry, ordinary-PUT revision hole, tag-filtered rules, swallowed parse error in the COPY sender. -- **Required (blocking):** R1 ModTime fallback scope; R2 forced-replication scope; R3 metadata-directive correction + sender-shaped test; R4 monotonic guard at commit; R5 duplicate-suppression comparison wording. - -Answering L75 directly: §A's existing local timestamp semantics are **not** sufficient without R4. §B's forced metadata synchronization is necessary but **wrongly scoped** (R1, R2); the stale ACK removal is necessary and correctly scoped. §C's duplicate exception is defensible but **under-specified** (R5). And yes — a path can still lose or revive the deletion revision: the DELETE→PUT local inversion (R4), plus the documented SSE-KMS PUT leg and tag-filtered-rule gaps. - -I recommend a v2 addressing R1–R5, then re-review against the new hash. I made no edits. \ No newline at end of file diff --git a/docs/investigations/r5/opus-v2-metadata.json b/docs/investigations/r5/opus-v2-metadata.json deleted file mode 100644 index 4597e0717..000000000 --- a/docs/investigations/r5/opus-v2-metadata.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "model": "claude-opus-5", - "effort": "max", - "plan_version": "v2", - "plan_sha256": "5a782acf3f285b23d1ae43a73481c4eb772a9a6d917fc5a550ecfc7cbf7446ca", - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "actual_assistant_models": [ - "claude-opus-5" - ], - "session_id": "1723246d-35c6-48de-9693-69f061427fcb", - "is_error": false, - "modelUsage": { - "claude-haiku-4-5-20251001": { - "inputTokens": 1578, - "outputTokens": 19, - "cacheReadInputTokens": 0, - "cacheCreationInputTokens": 0, - "webSearchRequests": 0, - "costUSD": 0.001673, - "contextWindow": 200000, - "maxOutputTokens": 32000, - "thinkingTokens": 0, - "canonicalModel": "claude-haiku-4-5", - "provider": "firstParty", - "costBasis": "list" - }, - "claude-opus-5": { - "inputTokens": 28, - "outputTokens": 30031, - "cacheReadInputTokens": 709817, - "cacheCreationInputTokens": 79637, - "webSearchRequests": 0, - "costUSD": 1.9021934999999999, - "contextWindow": 1000000, - "maxOutputTokens": 64000, - "thinkingTokens": 23251, - "canonicalModel": "claude-opus-5", - "provider": "firstParty", - "costBasis": "list" - } - }, - "result": "APPROVE_WITH_NONBLOCKING_NOTES", - "blocking_items": 0, - "raw_output": "/Users/vonng/tmp/silo-r5-20260915-77ad/opus-v2.jsonl" -} \ No newline at end of file diff --git a/docs/investigations/r5/opus-v2-prompt.md b/docs/investigations/r5/opus-v2-prompt.md deleted file mode 100644 index 14ad77d01..000000000 --- a/docs/investigations/r5/opus-v2-prompt.md +++ /dev/null @@ -1,7 +0,0 @@ -Independent real Opus 5 at effort max follow-up review. Production sources remain exactly 9ebe81c1b3611f9cc73e676b5b741c2be62c467a; no fix implemented. Consensus target: docs/investigations/r5/plan-v2.md sha256 5a782acf3f285b23d1ae43a73481c4eb772a9a6d917fc5a550ecfc7cbf7446ca. Read that complete v2 plan, docs/investigations/r5/opus-v1-response.md, and your preserved docs/investigations/r5/opus-v1-review.md. Focus on the five prior blocking items and disposition; do not repeat a broad repository audit. Read precise source as needed to decide. - -R1 accepted (no empty/no-revision fallback). R3 confirmed against pinned SDK; actual SDK wire test now in /Users/vonng/tmp/silo-r5-20260915-77ad/matrix_test.go and /Users/vonng/tmp/silo-r5-20260915-77ad/discussion-baseline.log. R4 accepted plus a uniform multi-pool guard before per-set write, since normal GetObjectInfo/GetObjectNInfo can return primary pool and not merged latest revision. R5 explicit strictly-newer-than-stored. R2 challenged: same nonempty value at T1 and T3 can hide an intervening delete at T2; empty-only forcing would drop re-addition T3 and allow delayed delete. We accept one metadata COPY per explicitly scheduled retry/heal/resync for explicit revisions. Completed queueReplicationHeal returns early unless resync (cmd/bucket-replication.go ~3758); replicateObject requeues only Failed (~1305). No endless background loop is introduced. A new HEAD revision protocol seems more complexity than justified; evaluate the concrete correctness/cost tradeoff. Both equal-empty and equal-nonempty baseline action tests in discussion-baseline.log expose loss. - -Correct one nonblocking claim too: although COPY sender does not forward a public KMS header, destination bucket defaults/globalAutoEncryption apply BEFORE copyDstOpts (object-handlers.go 1425-1435), so COPY can still depend on R4. R4 has independently reproduced this. Plan documents tag-filter eligibility and unversioned content overwrite as pre-existing scope limits; per-hop guarantees only. - -Return APPROVE / APPROVE_WITH_NONBLOCKING_NOTES / REQUEST_CHANGES for this EXACT v2 and hash, with explicit blocker count and per-item resolution. If R2 is still blocking, provide the smallest concrete alternative that passes the X@T1/delete@T2/X@T3/reordered-delete case, not an empty-only suggestion. Do not claim consensus with remaining blockers. Do not edit or attempt Write; only return text. Model identity will be verified from raw assistant metadata. diff --git a/docs/investigations/r5/opus-v2-review.md b/docs/investigations/r5/opus-v2-review.md deleted file mode 100644 index b32433042..000000000 --- a/docs/investigations/r5/opus-v2-review.md +++ /dev/null @@ -1,43 +0,0 @@ -Write is disabled for this task by your instruction, so the review is returned in full as text. No files were created or edited. - -# Independent follow-up review — R5 repair plan v2 - -**Reviewer model: `claude-opus-5` (Opus 5), effort max.** Read: `docs/investigations/r5/plan-v2.md` (complete), `opus-v1-response.md`, my preserved `opus-v1-review.md`, plus `/Users/vonng/tmp/silo-r5-20260915-77ad/matrix_test.go` (discussion cases) and `discussion-baseline.log`. Source re-read at `9ebe81c1b` only where an item's disposition depended on it. - -**VERDICT: APPROVE_WITH_NONBLOCKING_NOTES** for `docs/investigations/r5/plan-v2.md`, consensus target sha256 `5a782acf3f285b23d1ae43a73481c4eb772a9a6d917fc5a550ecfc7cbf7446ca`. **Blocking items remaining: 0 (zero).** All five v1 blockers are resolved; R2 I withdraw as a blocker on the evidence below. - -Same binding caveat as v1, unchanged: this session has Glob/Grep/Read only, so I did not execute `sha256sum`. I reviewed the file at that path in this worktree at the stated base and cite its line numbers so findings bind to the hash. - -## Per-item resolution - -| Item | Status | Basis | -|---|---|---| -| **R1** empty/no-revision ModTime fallback | **Resolved** | Plan L35 is exactly my v1 correction: revision-when-recorded, existing nonempty ModTime fallback retained, empty + no revision sends nothing. §B and §D (L61) are now consistent. | -| **R2** forced metadata replication scope | **Resolved — my correction was wrong; I withdraw the blocker** | See below. | -| **R3** production COPY request shape | **Resolved, with wire evidence** | `TestTaggingProductionCopyWireShape` (matrix_test.go:436-459) drives the real `minio.Core.CopyObject` against an httptest peer: `metadata-directive="" tagging-directive="REPLACE"` plus the tagging timestamp header (log line 6). L17 now states both shapes and requires covering a peer that sends metadata REPLACE. | -| **R4** local revision inversion | **Resolved and correctly strengthened** | L31's uniform multi-pool requirement is necessary, and **my v1 note was wrong** — see correction 2. `TestLocalTaggingCommitCannotRegressRevision` encodes max-across-pools + 1ns in the response and every copy; it fails baseline (log line 9: stamp stays `01:00:00Z`). | -| **R5** duplicate-suppression wording | **Resolved** | L51 is explicit: valid source revision *strictly newer than the destination's stored tag revision*, `olderThan` semantics, client preconditions preserved, re-upload cost documented. | - -## R2 adjudication — why I withdraw it - -**My proposed restriction was incorrect.** `TestTaggingRepeatedValueNeedsRevisionDelivery` (matrix_test.go:422-435) runs both rows; baseline returns `replicateNone` for equal `""` **and** for equal `"key=same"` with a revision an hour newer (log lines 2-3). An empty-only condition fixes only the first row, so it does not repair X@T1 / delete@T2 / X@T3 with a reordered delete. The scenario is reachable: `er.PutObjectTags` never touches `fi.ModTime` (`cmd/erasure-object.go:2328-2332`), so the full-copy gate `oi1.ModTime.Unix() != oi2.LastModified.Unix()` (`:975-981`) never fires on tagging-only changes and the value comparison at `:1003` is decisive; concurrent workers for the same object are not serialized by revision, and at `:1600-1625` a `replicateNone` result is force-marked Completed, making the loss permanent and silent. - -**My cost rationale is refuted by source, not merely by assertion.** `queueReplicationHeal` returns at `cmd/bucket-replication.go:3768` for `Completed && VersionPurgeStatus.Empty() && !mustResync()`; `replicateObject` requeues only non-Completed at `:1306`. I also checked the feedback path I would have raised in its place: `mustReplicate` returns an empty decision for an incoming replication request (`:270-272`), so a forced COPY cannot schedule a new event at the destination — no active-active ping-pong. And the blast radius is **narrower than the plan claims**: `ObjectReplicationType` dispatches to `ri.replicateObject` (`:1233-1237`), which never calls `getReplicationAction`, so the extra COPY applies only to Metadata/Heal/ExistingObject types. - -**Concrete tradeoff against a HEAD revision protocol.** The sender already extends the HEAD (`sOpts.Set(xhttp.AmzTagDirective, "ACCESS")`, `:1595`), so the idea is not absurd — but the pinned SDK's `extractObjMetadata` (`minio-go@v7.3.1-0.20260910142817.../utils.go:232-277`) preserves only the whitelist plus `x-amz-meta-`/`X-Minio-Meta-`; any `x-minio-internal-*` response header is discarded. Exposing the revision therefore needs (a) a new target-side response header, in a client-visible namespace or behind an SDK whitelist change, (b) a sender-side read path, and (c) a fallback for peers that do not answer — and that fallback is the forced COPY anyway. Strictly more code, a new cross-version wire contract, and the same worst case. The plan's choice is right; L39 already states the residual cost honestly. - -## Corrections to my own v1 non-blocking claims - -1. **KMS / COPY (as you flagged).** My v1 line — "the metadata-COPY leg is **not** affected" — is wrong. The sender indeed forwards no public SSE header, but `CopyObjectHandler` applies the destination bucket's SSE config and `globalAutoEncryption` to `r.Header` at `cmd/object-handlers.go:1428-1433`, *before* `copyDstOpts` → `putOptsFromReq` → `putOpts` → `putOptsFromHeaders`, whose `crypto.S3KMS.IsRequested(hdr)` branch (`cmd/object-api-options.go:431-461`) returns an ObjectOptions carrying the legal-hold and retention timestamps but **not** `ReplicationSourceTaggingTimestamp`. So COPY does depend on R4 whenever the destination bucket has default KMS or auto-encryption is on. Plan L59 states this correctly; do not adopt my broader exclusion. -2. **Multi-pool merge.** My v1 R4 note claimed per-pool stamp differences merge safely via `mergedPoolObjectInfo`. They do not on ordinary reads: `z.GetObjectInfo` → `getLatestObjectInfoWithIdx` (`cmd/erasure-server-pool.go:1121`, `:1032-1072`) returns one pool's `ObjectInfo`, sorted by ModTime with a lowest-index tiebreak — and ModTime is identical across copies for tagging changes. `mergedPoolObjectInfo` is reached only from `replicaObjectInfo` and `updatePoolMetadata` (`cmd/erasure-server-pool-consistency.go:135-147`, `:169-174`). Since the replication sender reads through `GetObjectNInfo`, it can emit a stale primary-pool revision. L31's uniform value is required. - -## Non-blocking notes for v2 - -- **Tighten the cost statement** in L39 to Metadata/Heal/ExistingObject types only (`ObjectReplicationType` bypasses the predicate). It makes the accepted cost smaller and the test targets sharper. -- **Add a termination regression** asserting `mustReplicate` yields no decision for the incoming forced COPY (`:270-272`). That property, not the scanner gates alone, is what makes the rule terminating under bidirectional configurations. -- **Assert ModTime invariance across tagging** (`:2328-2332`). If a future change ever bumped ModTime on tagging, the equal-value skip disappears and the whole cost calculus shifts; a one-line assertion pins the premise. -- **Multi-pool guard mechanics.** `z.PutObjectTags` returns `copies[0]`'s result (`cmd/erasure-server-pool.go:3059-3067`) and is bypassed entirely for `SinglePool()` (`:3035-3036`). Keep the guard in `er.PutObjectTags` as the invariant holder, have it advance only when the passed value is not strictly newer than that set's stored value, and have the response report what was actually written. Transient per-set divergence from a direct-to-set write outside the pool lock re-converges on the next z-level write; say so rather than implying it cannot happen. -- **`replicateNone` branch side effect.** Revisioned objects now reach `applyAction` instead of the force-Completed path at `:1614-1624`, so a failed COPY becomes Failed + MRF rather than a phantom Completed, and `rinfo.ReplicationAction` flips `none`→`metadata`. Right direction; cover the metrics path once. -- **Sender parse asymmetry.** The receiver fails closed on a malformed tag timestamp (`cmd/object-api-options.go:421-424`) while the COPY sender swallows it (`cmd/bucket-replication.go:1695-1700`). L35/L37 covers this; just confirm the COPY call site is in scope, since it is a different function from `putReplicationOpts`. - -I made no edits, launched no agents, and executed nothing. No implementation is proposed or requested here, so I am not calling ExitPlanMode. \ No newline at end of file diff --git a/docs/investigations/r5/plan-v1.md b/docs/investigations/r5/plan-v1.md deleted file mode 100644 index e4549729c..000000000 --- a/docs/investigations/r5/plan-v1.md +++ /dev/null @@ -1,75 +0,0 @@ -# R5 repair plan v1 — tag deletion and ordered replication - -Status: proposed, no production implementation before real Opus consensus. -Base: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a` (local and GitHub main checked). - -## Problem and necessity - -A tag value and its `x-minio-internal-tagging-timestamp` form one state, including an empty value. Successful DeleteObjectTagging currently leaves the old timestamp. A delayed, authenticated metadata COPY newer than that old timestamp restores deleted tags. The parent and this worktree reproduce this through signed HTTP with real single-drive and 16-drive storage. An empty incoming COPY is also ignored. - -The consequence is persistent incorrect user metadata on the source or replicas, affecting tag-based access/lifecycle behavior. Repair is justified before claiming replication correctness. It is not a reason to change the supported PGSTY dependency graph, storage format, encryption protocol, or Object Lock behavior. - -## Current chain and additional gaps - -1. `PutObjectTaggingHandler` stamps only when replication is selected. `DeleteObjectTaggingHandler` never stamps. Both storage methods write the value under the existing object lock; multi-pool PutObjectTags updates the addressed version in each copy. -2. `putReplicationOpts` sets the timestamp only inside nonempty UserTags. Multipart uses this builder and clears SourceMTime at initiation, so using initiation time as a tag fallback would be wrong. -3. Metadata COPY carries an explicit empty tag via `getCopyObjMetadata`, but only defaults its timestamp to object ModTime for nonempty tags. -4. `CopyObjectHandler` branches on nonempty tags; its REPLACE metadata map also loses the previous timestamp before comparison. -5. `PutObjectHandler` and `NewMultipartUploadHandler` parse the source timestamp but never persist it in metadata. Multipart completion already rechecks the upload's persisted metadata against the addressed destination version under the object lock. -6. `getReplicationAction` compares tag values/counts, not their ordering time. An empty-to-empty deletion revision can be declared complete without transmitting the revision. -7. `checkPreconditionsPUT` skips matching version/ETag for non-SSE-C replicas even when their tag revision is newer. This affects PUT and multipart initiation. Explicit client If-Match/If-None-Match checks must still apply. -8. `replicateObject`'s completion metadata callback copies nonempty `ri.UserTags` from the old queue snapshot. A deletion committed before this worker's current-object read can be overwritten at acknowledgment. A status update must retain the tags read under its own metadata lock. - -The existing storage fix (`3ce831925`) already supplies `reconcileStoredObjectTags` in PUT, COPY, multipart completion and all-pool reconciliation. Its strict ordering keeps stored state on equal timestamps and keeps a valid stored revision against missing/invalid incoming timestamps. Reuse these gates, do not replace them. - -## Proposed minimal changes - -### A. Produce local revisions - -In both PUT tagging and DELETE tagging handlers, allocate opts.UserDefined if needed and assign a single UTCNow RFC3339Nano tag revision for each authorized mutation, independent of current replication selection. Use the same time for ReplicationTimestamp when replication is selected. This also covers empty PUT tagging and mutations while replication is disabled. Persist through existing PutObjectTags/DeleteObjectTags locks. Ordinary COPY must write an empty REPLACE tag and a fresh tag timestamp too. - -This preserves the existing wall-clock conflict model, not a new distributed causal clock. The timestamp is generated before the storage lock as in existing PUT tagging. Reviewer should explicitly assess whether a commit-time generation change is necessary for this scoped repair; if necessary, revise before implementing. Clock skew and simultaneous conflicting equal revisions cannot be completely ordered by this protocol. - -### B. Transport complete state - -Move tag timestamp selection outside the nonempty-value branch in putReplicationOpts. Use recorded RFC3339Nano time when present, otherwise object ModTime (also for empty legacy objects). Malformed recorded timestamps fail option construction rather than being silently treated as fresh. - -Use the same selection for metadata COPY; a small shared timestamp helper is acceptable to prevent inconsistent error/fallback rules. Preserve explicit empty tag REPLACE metadata. Multipart initiation retains this timestamp even though SourceMTime is cleared. - -When getReplicationAction sees a recorded tag revision after the existing identity/full-copy checks, select metadata replication even if visible values match: HEAD does not expose that revision. Do not change the existing null-version resync exclusion. This is an extra COPY only for already-scheduled work with an explicit revision, including retry/heal; it does not add scans or network calls on ordinary object reads. Avoid a new HEAD protocol merely to save that COPY. - -Remove the stale ri.UserTags assignment from replication completion metadata write-back. The callback changes replication status only; existing metadata write-back preserves the current tags and timestamp. - -### C. Accept, order, and persist - -COPY captures the stored UserTags and timestamp before metadata reconstruction. For a trusted replication request with a nonzero source timestamp, install the incoming tag value (including empty) with that timestamp, then reuse reconcileStoredObjectTags against the captured state. A missing source timestamp preserves stored state for metadata COPY. Storage rechecks under its lock, including all pools. Equal timestamps keep stored state. Ordinary COPY generates a fresh revision for its chosen tags, including empty. - -Ensure the final encMetadata merge cannot restore a stale tag timestamp over the accepted pair (SSE-C rotation snapshots reserved keys). Reconcile the timestamp entry in encMetadata with the tag decision before merging, using the existing lock-timestamp pattern. - -PUT and multipart initiation persist a nonzero parsed trusted source timestamp into the existing metadata map before entering storage. Their existing lock/reconcile flags retain the newest state. Completion takes tag state from the persisted upload, not client-supplied completion headers, and orders it again against current state. - -For trusted REPLICA PUT/multipart initiation, a valid newer source tag timestamp makes matching version/ETag insufficient to skip the request. Preserve explicit If-Match/If-None-Match and existing SSE-C behavior. Duplicate/equal/older non-SSE-C writes may keep their existing no-op/412 behavior; the sender treats these as already delivered. Completion does not set PreserveETag and does not need a new duplicate exception. - -### D. Boundaries and compatibility - -R4 owns object-api-options.go SSE-KMS common-field preservation. R5 will not implement it. R4 will provide a reviewed patch for isolated combined KMS verification. R5 owns the handlers, sender and tag-related duplicate exception. - -No migration: historical deletions with missing/wrong timestamps have irrecoverably lost ordering information. We do not invent historical deletion times or rewrite production state. A fresh authenticated tagging mutation after upgrade produces an ordered state. Upgrade both sender and receiver for complete guarantees; older peers may continue to drop empty revisions. No main merge, push, release or deployment is authorized here. - -## Verification matrix - -- Re-run parent overlay on exact HEAD; preserve raw failures. Extend temporary reproduction for PUT/multipart lost persistence, empty-to-empty sender decision, and matching-content newer revision skip. -- Signed HTTP tag PUT/delete, active replication and no selected replication, empty PUT, repeated DELETE; check response and persisted tag/time, worker scheduling when active. -- COPY old/new/equal/missing/invalid source time, empty/nonempty, metadata COPY/REPLACE, explicit UUID/null version; protect unrelated/latest versions and local empty COPY. -- Production putReplicationOpts/SDK headers and actual metadata sender requests: explicit empty tombstone, legacy ModTime fallback, nanoseconds, malformed timestamp. Equal empty values must still send ordered deletion; exercise retry after failed send. -- PUT and multipart through signed requests/SDK: first receipt, newer removal, older replay after removal, duplicate receipt, missing timestamp. For multipart, mutate tags between initiation and completion and check final disk state. -- Multi-pool real-storage fixture with duplicate UUID/null versions, newer tag state in secondary pool, all-pool persistence/retirement; reuse current tag storage suite and failure-closed coverage. Include a deterministic update after handler snapshot to show storage lock recheck. -- Old queued replication event followed by deletion: process event and confirm source completion callback cannot restore tags; check tag/time after reread. -- Run related replication trust, Object Lock/SSE-C retransmit, tag storage and API precondition tests; targeted race tests, gofmt, git diff --check. No whole-repository tests in parallel with other R tasks without need. -- KMS combined dependent tests only after R4 reviewed change is available. Report R5-only and combined results separately. - -## Work and acceptance - -Estimated 2–4 engineer days including replication boundary tests and review. The patch should remain localized; added regression code is expected to exceed production LOC. Completion requires a reviewable diff, actual Opus model/effort record, same-plan consensus, meaningful persisted-state tests, and explicit local versus merge/release state. Not production multi-site acceptance. - -Review questions: Are A's existing local timestamp generation semantics sufficient here? Are B's forced metadata synchronization, stale ACK removal and C's narrow duplicate exception necessary and correctly scoped? Is any incoming/forwarding/multi-pool path still able to lose or revive the deletion revision? diff --git a/docs/investigations/r5/plan-v2.md b/docs/investigations/r5/plan-v2.md deleted file mode 100644 index c403c6a90..000000000 --- a/docs/investigations/r5/plan-v2.md +++ /dev/null @@ -1,81 +0,0 @@ -# R5 repair plan v2 — tag deletion and ordered replication - -Status: proposed v2, responding to real Opus v1 REQUEST_CHANGES; no production implementation before consensus. -Base: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a` (local and GitHub main checked). - -## Problem and necessity - -A tag value and its `x-minio-internal-tagging-timestamp` form one state, including an empty value. Successful DeleteObjectTagging currently leaves the old timestamp. A delayed, authenticated metadata COPY newer than that old timestamp restores deleted tags. The parent and this worktree reproduce this through signed HTTP with real single-drive and 16-drive storage. An empty incoming COPY is also ignored. - -The consequence is persistent incorrect user metadata on the source or replicas, affecting tag-based access/lifecycle behavior. Repair is justified before claiming replication correctness. It is not a reason to change the supported PGSTY dependency graph, storage format, encryption protocol, or Object Lock behavior. - -## Current chain and additional gaps - -1. `PutObjectTaggingHandler` stamps only when replication is selected. `DeleteObjectTaggingHandler` never stamps. Both storage methods write the value under the existing object lock; multi-pool PutObjectTags updates the addressed version in each copy. -2. `putReplicationOpts` sets the timestamp only inside nonempty UserTags. Multipart uses this builder and clears SourceMTime at initiation, so using initiation time as a tag fallback would be wrong. -3. Metadata COPY carries an explicit empty tag via `getCopyObjMetadata`, but only defaults its timestamp to object ModTime for nonempty tags. -4. `CopyObjectHandler` branches on nonempty tags. Production getCopyObjMetadata + SDK Core.CopyObject sends tagging REPLACE but no metadata directive, so its default metadata map retains the old timestamp. A peer using metadata REPLACE additionally loses that timestamp before the handler comparison. Cover both shapes; the empty-value skip affects both. -5. `PutObjectHandler` and `NewMultipartUploadHandler` parse the source timestamp but never persist it in metadata. Multipart completion already rechecks the upload's persisted metadata against the addressed destination version under the object lock. -6. `getReplicationAction` compares tag values/counts, not their ordering time. An empty-to-empty deletion revision can be declared complete without transmitting the revision. -7. `checkPreconditionsPUT` skips matching version/ETag for non-SSE-C replicas even when their tag revision is newer. This affects PUT and multipart initiation. Explicit client If-Match/If-None-Match checks must still apply. -8. `replicateObject`'s completion metadata callback copies nonempty `ri.UserTags` from the old queue snapshot. A deletion committed before this worker's current-object read can be overwritten at acknowledgment. A status update must retain the tags read under its own metadata lock. - -The existing storage fix (`3ce831925`) already supplies `reconcileStoredObjectTags` in PUT, COPY, multipart completion and all-pool reconciliation. Its strict ordering keeps stored state on equal timestamps and keeps a valid stored revision against missing/invalid incoming timestamps. Reuse these gates, do not replace them. - -## Proposed minimal changes - -### A. Produce local revisions - -In both PUT tagging and DELETE tagging handlers, allocate opts.UserDefined if needed and assign a single UTCNow RFC3339Nano tag revision for each authorized mutation, independent of current replication selection. Use the same time for ReplicationTimestamp when replication is selected. This also covers empty PUT tagging and mutations while replication is disabled. Persist through existing PutObjectTags/DeleteObjectTags locks. Ordinary COPY must write an empty REPLACE tag and a fresh tag timestamp too. - -Under the existing storage write lock, guard local tagging revisions against regression: a valid supplied tag revision not strictly after the valid stored revision is advanced to stored + 1ns. Do this in er.PutObjectTags; for multi-pool writes, z.PutObjectTags first computes one revision strictly beyond every addressed copy and passes that identical value to all sets. This is necessary because ordinary replication source reads/returned primary ObjectInfo can observe one physical pool; merely allowing different pool revisions with equal values can send a revision older than an already-replicated tombstone. Only explicit valid local tag revisions are advanced; replicated PUT/COPY/multipart retain strict source ordering, and direct storage calls without a supplied revision retain current legacy semantics. Generate before locking as today; the guard runs under the lock. Preserve the requested map from unintended shared mutation. This is a per-object monotonic guard in the existing RFC3339Nano domain, not a new wire clock. Equal independent remote conflicting revisions still keep stored state; arbitrary distributed clock skew is not totally ordered by this protocol. - -### B. Transport complete state - -Move tag timestamp selection outside the nonempty-value branch in putReplicationOpts. Use recorded RFC3339Nano time when present even for an empty value. Without a recorded revision, retain the existing ModTime fallback only for nonempty tags; empty + no revision sends no timestamp. This avoids inventing a tombstone for never-tagged/historically unordered objects. Malformed recorded timestamps fail option construction rather than being silently treated as fresh. - -Use the same selection for metadata COPY; a small shared timestamp helper is acceptable to prevent inconsistent error/fallback rules. Preserve explicit empty tag REPLACE metadata. Multipart initiation retains this timestamp even though SourceMTime is cleared. - -When getReplicationAction sees a recorded tag revision after the existing identity/full-copy checks, select metadata replication even if visible values match: HEAD does not expose that revision. Keep this for empty AND nonempty states. Example: destination key=X@T1, source deleted at T2 and re-added key=X@T3; if the equal nonempty state skips T3, delayed delete T2 incorrectly removes X. An empty-only condition does not fix ordered deletion/re-addition. Preserve existing null-version resync exclusion. Cost: every explicitly scheduled retry/heal/resync for an object with a recorded revision may require a metadata COPY, even if already converged. It does not create a background retry loop: queueReplicationHeal returns early for Completed objects without requested resync (bucket-replication.go around 3758), and replicateObject requeues only failed results (around 1305). Successful copies remain Completed. Never-tagged objects retain the old skip optimization under the preceding rule. Accept the extra metadata I/O during explicit resync as the smallest correctness-complete option; avoid introducing an authenticated HEAD revision protocol solely as an optimization. - -Remove the stale ri.UserTags assignment from replication completion metadata write-back. The callback changes replication status only; existing metadata write-back preserves the current tags and timestamp. - -### C. Accept, order, and persist - -COPY captures the stored UserTags and timestamp before metadata reconstruction. For a trusted replication request with a nonzero source timestamp, install the incoming tag value (including empty) with that timestamp, then reuse reconcileStoredObjectTags against the captured state. A missing source timestamp preserves stored state for metadata COPY. Storage rechecks under its lock, including all pools. Equal timestamps keep stored state. Ordinary COPY generates a fresh revision for its chosen tags, including empty. - -Ensure the final encMetadata merge cannot restore a stale tag timestamp over the accepted pair (SSE-C rotation snapshots reserved keys). Reconcile the timestamp entry in encMetadata with the tag decision before merging, using the existing lock-timestamp pattern. - -PUT and multipart initiation persist a nonzero parsed trusted source timestamp into the existing metadata map before entering storage. Their existing lock/reconcile flags retain the newest state. Completion takes tag state from the persisted upload, not client-supplied completion headers, and orders it again against current state. - -For trusted REPLICA PUT/multipart initiation, a valid source tag timestamp strictly newer than the destination's stored tag revision makes matching version/ETag insufficient to skip the request. Use the existing olderThan predicate (zero never wins, valid source beats missing/invalid stored). Preserve explicit If-Match/If-None-Match and existing SSE-C behavior. Duplicate/equal/older non-SSE-C writes may keep their existing no-op/412 behavior; the sender treats these as already delivered. Completion does not set PreserveETag and does not need a new duplicate exception. This narrow exception can re-upload data to carry a tag-only revision; normal metadata work uses COPY, while full retransmission must not silently acknowledge a newer revision it did not persist. - -### D. Boundaries and compatibility - -R4 owns object-api-options.go SSE-KMS common-field preservation. R5 will not implement it. R4 will provide a reviewed patch for isolated combined KMS verification. R5 owns the handlers, sender and tag-related duplicate exception. - -Compatibility: equal-timestamp COPY consistently keeps stored state, including unqualified and explicit null requests; this aligns with existing storage tie behavior and changes the old unqualified handler incoming-wins tie. Preserve the existing tag trust predicate (trusted replication marker) and stronger ReplicaLockReconcile predicate (trusted marker + REPLICA and addressed version), without expanding trust. Production replication supplies both. - -Scope limitations: ordinary full object PUT/multipart creation retain their existing nonempty ModTime fallback; this change does not order independent unversioned content replacements against one another. Existing tag-filter target eligibility can exclude post-deletion empty tags; target-selection semantics are not changed here. This work guarantees correct tag ordering along selected per-hop replication requests, not every replication-rule configuration. Destination bucket-default/global-auto KMS is applied before copyDstOpts (object-handlers.go 1425-1435), so COPY can depend on R4 even when the sender does not forward an explicit encryption header. PUT/multipart KMS also require R4. - -No migration: historical deletions with missing/wrong timestamps have irrecoverably lost ordering information. We do not invent historical deletion times or rewrite production state. A fresh authenticated tagging mutation after upgrade produces an ordered state. Upgrade both sender and receiver for complete guarantees; older peers may continue to drop empty revisions. No main merge, push, release or deployment is authorized here. - -## Verification matrix - -- Re-run parent overlay on exact HEAD; preserve raw failures. Extend temporary reproduction for PUT/multipart lost persistence, empty-to-empty sender decision, and matching-content newer revision skip. -- Signed HTTP tag PUT/delete, active replication and no selected replication, empty PUT, repeated DELETE; check response and persisted tag/time, worker scheduling when active. -- COPY old/new/equal/missing/invalid source time, empty/nonempty, metadata COPY/REPLACE, explicit UUID/null version; protect unrelated/latest versions and local empty COPY. -- Production putReplicationOpts/SDK headers and actual metadata sender requests: explicit empty tombstone, nonempty legacy ModTime fallback, no fabricated empty legacy revision, nanoseconds, malformed timestamp. Equal empty values must still send ordered deletion; equal nonempty values must send re-addition revisions to defeat intervening delayed deletions. Pin actual SDK metadata COPY headers, and cover peer metadata REPLACE independently. Exercise retry after failed send. -- PUT and multipart through signed requests/SDK: first receipt, newer removal, older replay after removal, duplicate receipt, missing timestamp. For multipart, mutate tags between initiation and completion and check final disk state. -- Multi-pool real-storage fixture with duplicate UUID/null versions, newer tag state in secondary pool, all-pool persistence/retirement; reuse current tag storage suite and failure-closed coverage. Include a deterministic update after handler snapshot to show storage lock recheck. A local DELETE-to-PUT inversion with supplied older timestamp must produce one revision greater than the maximum across pools, in both response and every stored copy. -- Old queued replication event followed by deletion: process event and confirm source completion callback cannot restore tags; check tag/time after reread. -- Run related replication trust, Object Lock/SSE-C retransmit, tag storage and API precondition tests; targeted race tests, gofmt, git diff --check. No whole-repository tests in parallel with other R tasks without need. -- KMS combined dependent tests only after R4 reviewed change is available. Report R5-only and combined results separately. - -## Work and acceptance - -Estimated 2–4 engineer days including replication boundary tests and review. The patch should remain localized; added regression code is expected to exceed production LOC. Completion requires a reviewable diff, actual Opus model/effort record, same-plan consensus, meaningful persisted-state tests, and explicit local versus merge/release state. Establishes per-hop behavior with deterministic clock/commit interleaves; not production multi-site or real host-clock-skew acceptance. - -## v2 review focus - -See `opus-v1-response.md` for every blocking/nonblocking disposition and exact counterarguments. R1/R3/R4/R5 accepted with concrete changes. R2 is disputed as proposed: empty-only forced transfer is insufficient for same-value re-addition after deletion, and Completed scanner gates bound work. Please adjudicate on this v2 hash, not on general preference for avoiding metadata I/O. Do not treat unresolved disagreement as consensus. diff --git a/docs/investigations/r5/related-test-names.txt b/docs/investigations/r5/related-test-names.txt deleted file mode 100644 index c0a30ed69..000000000 --- a/docs/investigations/r5/related-test-names.txt +++ /dev/null @@ -1,135 +0,0 @@ -TestPeerBucketCorsReplicationOrdering -TestSiteReplicationMetaInfoPreservesCorsTombstone -TestSiteReplicationStatusDetectsCorsTimestampMismatch -TestSiteReplicationStatusCountsCorsPerSite -TestCORSReplicationStateOrdering -TestCORSReplicationStatusStateEquality -TestNewBucketCORSReplicationEvent -TestCorsReplicationDispatchStatusHealReload -TestMarshalUnmarshalReplicationMRFStats -TestEncodeDecodeReplicationMRFStats -TestMarshalUnmarshalBucketReplicationResyncStatus -TestEncodeDecodeBucketReplicationResyncStatus -TestMarshalUnmarshalReplicationState -TestEncodeDecodeReplicationState -TestMarshalUnmarshalTargetReplicationResyncStatus -TestEncodeDecodeTargetReplicationResyncStatus -TestCompositeReplicationStatus -TestReplicationResyncwrapper -TestReplicationValidationObjectUsesRulePrefix -TestGetReplicationActionEmptyObjectLockValues -TestReplicationActionForTargetRetentionRemoval -TestReplicationActionForTargetNullVersionResync -TestReplicationActionForTargetTimestampOnlyRemoval -TestMarshalUnmarshalBucketReplicationStat -TestEncodeDecodeBucketReplicationStat -TestMarshalUnmarshalBucketReplicationStats -TestEncodeDecodeBucketReplicationStats -TestMarshalUnmarshalReplicationLastHour -TestEncodeDecodeReplicationLastHour -TestMarshalUnmarshalReplicationLastMinute -TestEncodeDecodeReplicationLastMinute -TestMarshalUnmarshalReplicationLatency -TestEncodeDecodeReplicationLatency -TestMarshalUnmarshalReplicationQueueStats -TestEncodeDecodeReplicationQueueStats -TestAPISSECCompressionReplicaStaysReadable -TestSSECBatchReplicationCannotRead -TestAPIDeleteObjectVersionDenyAndReplicationCompatibility -TestAPISSECReplicaPartNumberReads -TestAPISSECReplicaMalformedPartIsRejected -TestPoolsDeleteVersionAPI -TestPoolsDeleteVersionUnreadablePool -TestPoolsDeleteVersionSingleCopy -TestPoolsDeleteVersionReplicationPurge -TestPoolsDeleteVersionCleanupFailure -TestPoolsDeleteVersionCallbacks -TestPoolsDeleteVersionSpecialCalls -TestPoolsDeleteUnversionedFanout -TestPoolsConditionalDeleteVersionSelection -TestPoolsConditionalDeleteDuplicateVersion -TestPoolsConditionalDeleteReportsOtherPoolFailure -TestPoolsConditionalDeleteSerializesPut -TestPoolsConditionalDeleteSerializesCompletion -TestPoolsReplicaSerializesMetadataAndHealing -TestPoolsConditionalDeletePreservesVersionHistory -TestPoolsReplicaIndependentLockWinners -TestPoolsReplicaSoleDrainingOwner -TestPoolsMetadataUpdateUsesMergedVersion -TestPoolsReplicaMetadataCopyReconcilesLockAndTags -TestPoolsReplicaCleanupFailureCanRetry -TestPoolsRetiringCopyPreservesSharedTierObject -TestPoolsDeleteVersionAfterInterruptedRebalance -TestPoolsDeleteDirectoryMarker -TestPoolsMultipartConditionalUsesLogicalLatest -TestPoolsMultipartConditionalHTTPMatrix -TestPoolsMultipartConditionalHTTPAbsentObject -TestPoolsMultipartConditionalHTTPNormalRouting -TestPoolsMultipartConditionalUnreadablePool -TestPoolsMultipartConditionalLatestVersionAndCallbackOnce -TestPoolsMultipartConditionalConcurrentCompletes -TestPoolsMultipartConditionMatrix -TestPoolsMultipartConditionBoundaries -TestPoolsMetadataUpdatePreservesTags -TestReplicaWritesPreserveTagOrdering -TestMergedPoolObjectInfoTagOrdering -TestPoolsMetadataCallbackReplacesTags -TestReconcileStoredObjectTagOrdering -TestPoolsMetadataUpdatePreservesAbsentTags -TestExtractReplicationMetadataHeaders -TestGetCopyObjectMetadataFromHeaderReplication -TestCloneRequestWithoutReplicationHeaders -TestIAMServiceAccountReplicationRejectsOtherCredentialKinds -TestIAMServiceAccountReplicationPreservesExpiration -TestPutOptsFromHeadersReplicationTimestamps -TestAPIGetObjectAttributesSSECReplicationAuthz -TestAPICopyObjectSSECKeyRotationReplicaKeepsFastPath -TestAPIFederatedCopyObjectRejectsRawSSECReplica -TestAPICopyObjectReplicaTaggingTimestampUnderKMS -TestCheckPreconditions -TestAPIPutObjectReplicationHeaderPoisoning -TestAPICopyObjectReplicationHeaderPoisoning -TestPostPolicyCannotForgeReplicationStatus -TestReplicationMRFDropsVisible -TestReplicationObjectDeleteWorkerAffinity -TestAPISSECReplicationTargetHead -TestAPISSECReplicaRetransmitOverExistingVersion -TestPutReplicationOptsRetentionRemoval -TestAPISSECReplicaWriteExemptionIsKeyedOnTheIncomingWrite -TestAPISSECReplicaRetransmitObjectLockOrdering -TestAPISSECReplicaRetransmitMultipartObjectLockOrdering -TestPutReplicationOptsRetentionRemovalTimestampOnly -TestAPIReplicaMarkerOnlyAppliesObjectLock -TestAPIReplicaMultipartNewerHoldSurvivesCompletion -TestAPITaggingReplicationOrdering -TestAPITaggingReplicationOrderingKMS -TestAPITaggingMultipartCommitRechecksRevision -TestAPILocalTaggingAlwaysAdvancesRevision -TestTaggingTimestampWire -TestAPIPoolsTaggingReplicaDeletion -TestAPITaggingSSECRotationPreservesDeletionRevision -TestTaggingRepeatedValueNeedsRevisionDelivery -TestTaggingProductionCopyWireShape -TestLocalTaggingCommitCannotRegressRevision -TestTaggingReplicaContentDuplicateGuard -TestAPITaggingUnqualifiedCopyOrdering -TestTaggingReplicationSenderRetryAndAcknowledgment -TestAPISSECReplicaSkipsDestinationTransforms -TestAPISSECMultipartReplicaRoundTripWithCompression -TestPutReplicationOptsRejectsCompressedSSEC -TestAPIReplicationTrustProtectsSSECReads -TestReplicationTrustControlsInternalOptionsAndEvents -TestAPIPutObjectReplicationTrust -TestAPISnowballReplicationTrustIsPerEntry -TestAPIDeleteObjectReplicationTrust -TestAPISSECMultipartReplicationTrust -TestAPIStreamingTrailerWithUntrustedReplicationHeaders -TestAPICopyObjectReplicaLegalHoldTimestamp -TestAPICopyObjectReplicaAbsentLockFieldsPreserveNewerState -TestAPICopyObjectReplicaRetentionRemovalKeepsOrderingTimestamp -TestAPICopyObjectReplicaObjectLockOrdering -TestAPICopyObjectReplicaRetentionRemovalUnderBucketKMS -TestAPICopyObjectReplicaLockTimestampSurvivesSSECKeyRotation -TestBucketPolicyReplicationKey -TestBucketPolicyReplicationStatusLegacyOrder -TestSiteReplicationStatusAccountsPerSiteAndSurvivesMalformedConfig diff --git a/docs/investigations/r5/reproduction.md b/docs/investigations/r5/reproduction.md deleted file mode 100644 index 2807406bb..000000000 --- a/docs/investigations/r5/reproduction.md +++ /dev/null @@ -1,38 +0,0 @@ -# Current-baseline reproduction - -Base: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`. Go 1.27.1, macOS arm64. Production sources unchanged. Temporary overlay injects regression tests; real erasure disks persist object metadata. Capacity adapter changes only reported capacity to avoid the developer machine's unrelated disk-usage threshold. - -## Commands and raw evidence - -Raw directory: `/Users/vonng/tmp/silo-r5-20260915-77ad/`. - -```sh -GOMAXPROCS=2 go test -p 1 -overlay /Users/vonng/tmp/silo-r5-20260915-77ad/overlay.json ./cmd -run '^TestReviewR5' -count=1 -v -GOMAXPROCS=2 go test -p 1 -overlay /Users/vonng/tmp/silo-r5-20260915-77ad/overlay.json ./cmd -run '^TestReviewR5Queued' -count=1 -v -``` - -Both exit 1, as expected before repair. Files: `baseline.log`, `baseline-extended.log`, `baseline-ack.log`; the full injected source is `baseline_test.go`. - -## Observations - -| Regression | Observed result | -|---|---| -| Empty source tags with explicit revision | putReplicationOpts returns zero TaggingTimestamp | -| Signed HTTP DELETE on a versioned object with replication selected | 204, empty tags, one queued event, unchanged old timestamp | -| Delayed signed trusted COPY after DELETE | 200 and deleted tags restored | -| Newer empty signed COPY | 200, old nonempty tags/time remain | -| First signed replica PUT carrying tag timestamp | 200, timestamp absent in stored object | -| First signed replica multipart initiation carrying timestamp | 200, timestamp absent in persisted upload metadata | -| Equal empty source/target values, source has newer deletion revision | getReplicationAction returns none | -| Same ETag/version with newer trusted source tag revision | checkPreconditionsPUT skips request | -| Process an old queued tagging event after a stored deletion | replication completes; source ACK restores `key=queued` with the deletion timestamp | - -All HTTP/storage cases above ran on both ErasureSD (one real disk) and Erasure (16 real disks). The last case uses a local HTTP protocol peer for replication responses and the real source object layer. It manually persists the deletion revision before processing the old queue snapshot to isolate the ACK defect from the separate DELETE-handler defect. The worker reads current deleted tags, yet its completion callback restores stale queue tags. - -These are component/in-process HTTP integration results, not multi-site production acceptance. - -## Provenance - -Current git history attributes introduction of ReplicationSourceTaggingTimestamp in COPY to upstream `c4373ef29` (2021-09-18, multi-site replication). COPY sender timestamps were added in `3781a0f9a` (2023-12-13), with default tag timestamps in `64a8f2e55` (2025-02-04). Queue-snapshot tag reassignment traces to `fa6d082bf` (2023-09-16). The storage tag reconciliation fix `3ce831925` is already present in this baseline and does not cover the HTTP/transport or queue-ACK omissions. - -No historical state can prove a missing deletion time. The planned repair records future revisions; a production backfill would need separate authoritative evidence and authorization. diff --git a/docs/investigations/r5/validation-results.json b/docs/investigations/r5/validation-results.json deleted file mode 100644 index 868e69194..000000000 --- a/docs/investigations/r5/validation-results.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "environment": { - "go": "go1.27.1 darwin/arm64", - "GOMAXPROCS": "2", - "go_test_p": "1" - }, - "new_r5_tests": { - "count": 13, - "exit_code": 0, - "log": "/Users/vonng/tmp/silo-r5-20260915-77ad/fixed-targeted-latest.log", - "runtime_seconds": 9.161 - }, - "related_selection": { - "selected_top_level_tests": 135, - "first_capacity_adapted_run": { - "passed": 134, - "failed": 1, - "failure": "POST fixture still used host capacity; XMinioStorageFull", - "exit_code": 1, - "log": "/Users/vonng/tmp/silo-r5-20260915-77ad/related-suite-capacity-final.log" - }, - "remaining_post_and_strengthened_pool_test": { - "passed": 2, - "failed": 0, - "exit_code": 0, - "log": "/Users/vonng/tmp/silo-r5-20260915-77ad/final-post-and-pools.log" - }, - "all_135_selected_tests_passed_across_batches": true, - "unfiltered_full_cmd_package_pass": false - }, - "race": { - "command": [ - "go", - "test", - "-race", - "-p", - "1", - "./cmd", - "-run", - "^(TestAPITagging.*|TestAPIPoolsTaggingReplicaDeletion|TestAPILocalTaggingAlwaysAdvancesRevision|TestTagging.*|TestLocalTaggingCommitCannotRegressRevision|TestReplicaWritesPreserveTagOrdering|TestMergedPoolObjectInfoTagOrdering|TestReconcileStoredObjectTagOrdering)$", - "-count=1", - "-v" - ], - "environment": { - "GOMAXPROCS": "2" - }, - "log": "/Users/vonng/tmp/silo-r5-20260915-77ad/targeted-race-final.log", - "exit_code": 0, - "duration_seconds": 121.658, - "passed_top_level_tests": 16, - "race_diagnostics": 0 - }, - "verifiers": { - "command": [ - "make", - "verifiers", - "GOLANGCI=/Users/vonng/tmp/silo-r5-20260915-77ad/golangci-lint-serial" - ], - "environment": { - "GOMAXPROCS": "2" - }, - "log": "/Users/vonng/tmp/silo-r5-20260915-77ad/make-verifiers-serial.log", - "exit_code": 0, - "duration_seconds": 203.553, - "wrapper": "Same repository-pinned lint binary with --allow-serial-runners to wait for the shared host lint lock." - }, - "build": { - "command": [ - "make", - "build" - ], - "environment": { - "GOMAXPROCS": "2" - }, - "log": "/Users/vonng/tmp/silo-r5-20260915-77ad/make-build.log", - "exit_code": 0, - "duration_seconds": 35.043, - "built_worktree_base": "dbcf8dec589deb5d91e17d295cb70997635f5b55", - "source_manifest": "docs/investigations/r5/final-implementation-manifest.json", - "version_exit_code": 0, - "version_output": "silo version DEVELOPMENT.2026-09-15T15-56-31Z (commit-id=dbcf8dec589deb5d91e17d295cb70997635f5b55)\nRuntime: go1.27.1 darwin/arm64\nLicense: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html\nCopyright: 2015-2025 MinIO, Inc.\nModifications: Copyright 2025-2026 PGSTY\nSource compatibility: based on MinIO technology" - }, - "limits": [ - "Existing TestReplicationResync order-dependent panic reproduced on the unpatched production baseline; passes in isolation on both versions.", - "Existing test capacity uses recorded test-only overlays, with real I/O and errors preserved.", - "Unfiltered full cmd package and production multi-site validation remain unperformed." - ] -} diff --git a/docs/investigations/r5/verification.md b/docs/investigations/r5/verification.md deleted file mode 100644 index b157b10f8..000000000 --- a/docs/investigations/r5/verification.md +++ /dev/null @@ -1,57 +0,0 @@ -# R5 local verification - -## Scope and source identity - -This is a local repair of ordered tag deletion along selected replication requests. It does not authorize or establish a main merge, push, release, deployment, historical-state migration, or production multi-site acceptance. - -- Research baseline: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`. -- Combined verification dependency: R4 `dbcf8dec589deb5d91e17d295cb70997635f5b55`; R5 does not modify `cmd/object-api-options.go`. -- Accepted plan: v2, SHA256 `5a782acf3f285b23d1ae43a73481c4eb772a9a6d917fc5a550ecfc7cbf7446ca`. -- Actual plan reviewer: `claude-opus-5`, explicit max effort; v1 requested changes, v2 approved with nonblocking notes and zero blockers. See `consensus.md`. -- Actual implementation reviewer: the same requested/observed model and effort, `GO_WITH_NONBLOCKING_NOTES`, zero blockers. Original review identity and hashes are in `opus-implementation-metadata.json`. -- `implementation-manifest.json` identifies the reviewed patch. `final-implementation-manifest.json` identifies the final source after a stronger multi-pool test assertion and gofumpt formatting. All seven production file hashes still match the review. - -## Executed regression checks - -All commands run from this worktree with `GOMAXPROCS=2` and `go test -p 1`, Go `go1.27.1 darwin/arm64`. Raw logs are in `/Users/vonng/tmp/silo-r5-20260915-77ad/`. - -| Check | Observed result | Evidence | -|---|---|---| -| New R5 tests before implementation | Reproduced real signed-HTTP deletion resurrection, empty COPY loss, full-write persistence/skip, equal-value sender skip, local revision inversion and stale source ACK | `baseline*.log`, `discussion-baseline.log`; `reproduction.md` | -| Latest complete new R5 selection | 13 top-level tests passed, 9.161s | `fixed-targeted-latest.log` | -| Related selection, host capacity adapted | 134 passed; one POST fixture still failed the host minimum-free threshold, 56.007s | `related-suite-capacity-final.log`; exact 135 names in `related-test-names.txt` | -| Remaining POST test plus strengthened multi-pool replay test | Both passed, 3.232s; completes the 135-name selection across the two batches | `final-post-and-pools.log` | -| Existing `TestReplicationResync` in isolation | Passed on baseline (2.191s) and R5 (1.776s) | `baseline-resync.log`, `fixed-resync-isolated.log` | -| Final R5 plus tag-storage race selection | 16 top-level tests passed, 25.584s runtime, no race diagnostics | `targeted-race-final.log`; exact command/exit in `final-check-results.json` | -| Repository verifiers | Passed: lint 0 issues, generated files unchanged, branding/compatibility and entrypoint checks passed | `make-verifiers-serial.log`, `verifiers-result.json` | -| Repository build and binary invocation | `make build` passed; the resulting `silo --version` exited 0 | `make-build.log`, `build-result.json`, `silo-version.log` | - -The selected regressions include existing replication trust/header poisoning, API preconditions, Object Lock, SSE-C retransmission, R4 KMS option/COPY tests, and pool metadata/cleanup/retry checks. The new R5 suite covers: - -- Local PUT tags, repeated DELETE, empty PUT and ordinary empty COPY; tag revisions advance even without selected replication, while local tagging preserves object ModTime. -- Empty/nonempty and newer/stale/equal/missing revisions through signed COPY with both metadata directives, PUT and multipart; UUID/null and unqualified COPY; unrelated newer versions survive. -- Multipart deletion committed between initiation and completion, with the upload's saved revision checked at initiation and ordered again at completion. -- Exact SDK sender headers, nanosecond precision, legacy fallback only for nonempty tags, and rejection of malformed recorded times. -- Equal-value metadata resend, failed COPY reporting/retry, no incoming-replica requeue, and a stale queued source ACK preserving the current deletion. -- Uniform local tag revisions beyond every physical pool, deterministic inverted request/commit timestamps, normal-routing readback after replay and inspection of every retained pool copy. -- Destination KMS encryption/readback and signed SSE-C key rotation with decrypted GET. These are local handler/storage fixtures, not an encrypted-source-to-encrypted-destination two-site deployment. - -## Baseline and environment failures retained - -The first broad selection panics at `TestReplicationResync` before any R5 test executes. Replacing all seven R5 production files with the R4 baseline, and hiding the two new tests in a Go overlay, reproduces the same panic after the same preceding tests (`baseline-related-suite.log`). The test passes alone on both versions. The remaining 135-name selection therefore runs separately; this is not reported as an unfiltered full-package pass. - -This host's used-space percentage makes existing allocation tests return `XMinioStorageFull`. The test-only overlays add the existing `tagTestCapacityDisk` via `r5Capacity` at the API/pool fixture boundaries and the final POST fixture. The adapter changes reported capacity only, delegates real I/O and propagates disk errors. The exact overlays, original/modified fixture hashes and diffs are retained as `capacity-fixture*` and `capacity-post*`. No fixture overlay or capacity-policy change enters production code. - -The first adapted link and first verifier run also failed actual `ENOSPC` when the volume had about 200–500 MiB available (`related-suite-capacity.log`, `make-verifiers.log`). Regenerable Go cache data untouched for three days was reclaimed with an exact manifest (`cache-reclaim.json`); subsequent successful checks are distinguished from those failures. A subsequent verifier caught gofumpt formatting in the new helper; that formatting was corrected before final validation. - -An earlier KMS multipart fixture used a single-PUT ETag with multipart data layout and failed decryption. Seeding a real multipart source fixed the fixture; the subsequent complete run passed plaintext readback. The failed log remains `fixed-targeted.log`, and this is not attributed to a production encryption change. - -## Local delivery and remaining integration gates - -Required scoped local checks are complete. `validation-results.json` records command results, and `evidence-manifest.json` identifies the raw files and binary by SHA256. The build compiled the working source identified by `final-implementation-manifest.json`; the Makefile stamped its pre-commit dependency ID `dbcf8dec5` into this local development binary. Final source identity is established by the file hashes, not by that pre-commit version label. - -An unfiltered full `cmd` run and real multi-site deployment remain future integration gates before any separately authorized merge/release. Known scope limits are retained in plan v2 and `implementation-review-response.md`: tag-filter target eligibility, historical missing revisions, malformed stored source times, legacy peers dropping empty revisions, and arbitrary distributed clock skew. - -The verifier uses the repository-pinned golangci-lint v2.13.1 through a local wrapper adding only `--allow-serial-runners`. This waits for the shared host lint lock instead of running another lint process concurrently. The first unscheduled attempt was rejected by that lock (`make-verifiers-success.log`; despite that filename, its recorded exit is 2). The final serialized run passed. The optional `typos` binary is unavailable and was skipped by the Makefile. - -R4 has since merged as `af2b1794d38d9e70e1d2c3ee692426e4b6cab4bd` (PR #193). `dependency-handoff.json` verifies that its production options file and both test bodies match the dependency used above. The other differences are test license headers and R4 review/validation documents. The R5 delivery base is this exact merged dependency, with the old unsigned `dbcf8dec5` ancestor removed. The original recorded plan/review baseline remains intact as historical evidence. The final local commit, clean-worktree check and post-rebase file-hash comparison are recorded outside the commit in `/Users/vonng/tmp/silo-r5-20260915-77ad/final-delivery.json`. diff --git a/docs/investigations/r6/README.md b/docs/investigations/r6/README.md deleted file mode 100644 index fa03e3aa8..000000000 --- a/docs/investigations/r6/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# R6:delete-marker purge 与 MRF 修复 - -## 当前交付状态 - -本地实现已完成,v3 已与真实 Opus 5.0 达成共识。原研究基线和同步到主干快照后的定向回归、race、完整构建、vet、lint 全部通过;此前受宿主机容量限制的六项 DELETE 测试,在空间恢复后也全部通过。 - -- 研究基线:`9ebe81c1b3611f9cc73e676b5b741c2be62c467a`。 -- 集成基线:`af2b1794d38d9e70e1d2c3ee692426e4b6cab4bd`;通过最终验证的源码提交:`cf381a7151ef25fc95ace5fedcd767fa19410de2`。后续提交仅整理本目录的验证文档。 -- 分支:`codex/r6-delete-marker-mrf`。 -- [PR #184](https://github.com/pgsty/silo/pull/184) 在最终核对时仍为 OPEN,head `6addf9eb916b5a4b837480cf534cd1efa5407d3c`。复用其按 purge 状态识别操作、接纳 marker 405 的方向,补齐实测遗漏;没有直接合并该 PR。 -- 从研究基线到集成基线,仅新增 R4 的 SSE-KMS PUT 选项与对应材料,R6 涉及的生产文件、测试文件和依赖未发生交叉修改。五个 R6 文件在同步前后的 SHA256 一致。 -- 本任务没有执行主干合并、远端推送、发布、部署或现网存量改写。 - -## 修复内容 - -| 操作 | 远端行为 | 结果与源端写回 | -|---|---|---| -| marker 创建 | 保留 HEAD 已存在/就绪检查及创建语义 | 更新创建状态;405 可以表示已创建 | -| 规范版本 purge | 发送指定 versionId 的永久删除 | 只更新 purge 状态,保留磁盘创建/replica 字段 | -| 旧 marker 形态 purge | 从任务级 purge 状态识别,沿用规范永久删除请求 | 不再被创建 COMPLETED 跳过;失败进入 MRF;已完成 purge 不重发 | - -- 离线、DELETE 拒绝、成功与 resync 出口使用操作自己的状态;失败 purge 不写成功 reset 标记。源端 purge 写回显式清空三个“创建更新”字段,避免多目标空状态被旧正则误解析后覆盖磁盘创建记录。 -- MRF 接受携带正确 marker、版本、对象、桶和非零时间的 405;其它错误或无效信息不调度删除。 -- 删除任务携带重试计数;锁失败、复制失败、工作队列满三个入 MRF 出口都递增。耗尽现有预算后继续保留 scanner 恢复路径。 -- purge 的内部 COMPLETE 保持不变;操作审计使用规范 COMPLETED,失败为 FAILED。按目标状态变化更新统计,成功 heal purge 的统计为次数增加、字节数为零。 -- 不改 wire 格式、MRF 磁盘格式、共享正则、复制状态合并框架或依赖版本。 - -## 真实 Opus 共识 - -使用本机 Claude Code 2.1.270,每轮显式指定 `claude-opus-5 --effort max`。全部记录到的 assistant 模型均为 `claude-opus-5`;实际调用成功,未用模拟评审或限流失败代替同意。 - -| 方案 | 结论 | 处置 | -|---|---|---| -| v1 | REVISE,1 个阻断 | 接受意见:不能用本次目标子集重写完整创建状态 | -| v2 | GO_WITH_NONBLOCKING_NOTES | 共识后实现;随后用真实存储发现多目标空状态正则反例 | -| v3 | GO_WITH_NONBLOCKING_NOTES,0 阻断 | reviewer 撤回过强的旧证明,确认三字段清空方案;共识后应用并验证 | - -最终不可变方案:[plan-v3.md](plan-v3.md),SHA256 `dc9a67fc91b3113fa35218a2f903455807430cc4daf9c78a88d0be6b8fc27058`。 -详见 [完整共识及逐项处置](consensus.md)、[研究与 #184 审查](research.md)、[v3 原始评审正文](opus-v3-review.md)。模型只读权限不允许计算哈希;它核对了具体文件内容,本任务在评审前后计算并确认哈希不变。评审不替代执行验证。 - -## 验证范围 - -原基线证据:[机器记录](verification/baseline-verification.json)。最终集成证据:[五项检查记录](verification/rebased-verification.json)、[六项 DELETE 复验](verification/rebased-delete-verification.json)、[源码与方案哈希清单](final-source-manifest.json)。对应日志在同目录,均与原始日志逐字节一致。运行环境:Go 1.27.1,darwin/arm64,GOMAXPROCS=4。 - -| 检查 | 最终结果 | -|---|---| -| `go test -p 2 ./cmd ./internal/bucket/replication -run 'TestReplication\|TestReplicate\|TestMRF\|TestResync\|TestSiteResync' -count=1 -v` | 通过,28 个顶层测试 / 198 个通过条目 | -| `go test -race -p 2 ./cmd -run 'TestReplicateDelete\|TestReplicationMRF\|TestReplicationDeleteQueueFull' -count=1 -v` | 通过,无数据竞争报告 | -| `go build -p 2 ./...` | 通过 | -| `go vet -p 2 ./cmd ./internal/bucket/replication` | 通过 | -| golangci-lint 2.13.1,仓库配置,`--build-tags kqueue` | 通过,0 issues | -| 原容量失败的六项 DELETE 测试,按完整测试名精确复跑 | 六项全部通过 | - -- 28 个顶层定向测试通过,包含 198 个通过条目(含子测试)。 -- 单盘、16 盘真实 erasure 存储;真实源端签名 DELETE、minio-go HTTP、源/目标 marker 元数据。 -- 失败 → MRF 文件持久化 → 新 ReplicationPool 读取 → 真实 marker+405 lookup → 工作队列 → 生产 replicateDelete → 恢复。 -- 覆盖创建/旧新 purge、部分目标重试、两目标一成一败/离线、远端已删除但响应丢失、重试预算耗尽和 scanner 接管;恢复后核对源和目标 marker 最终状态。 -- 两个目标空状态的实际存储反例已转绿;所有 purge 写回均通过断言确认创建字段为空,完整创建/replica 元数据和时间戳保留。 -- 验证实际审计 webhook 的 FAILED/COMPLETED,以及失败/成功统计变化;race 未报告数据竞争。 - -**边界:** 目标为受控 HTTP 适配器,调用真实 ObjectLayer;测试显式消费队列并执行生产复制函数,直接驱动 MRF 保存,没有启动后台定时器和完整 worker 循环。这是三端点 fan-out 与磁盘恢复验证,不是三台独立 SILO 进程的站点复制集群、接收端认证或进程崩溃验收。 - -首次扩大测试中,六项无关 DELETE 测试在种子数据写入时触发宿主机容量阈值,该次测试未通过。空间恢复后,保持代码不变精确复跑六项,全部通过;这不等于运行了整个 cmd 测试集。R6 存储夹具使用仓库现有容量适配器,数据仍真实落盘。一次测试链接遇到磁盘空间耗尽,清理可确认属于本任务的旧 Go 缓存后复验。详情和中间失败记录:[verification-notes.md](verification-notes.md)。 - -## 仍然独立的事项 - -当前 DELETE/scanner/heal/resync 已产生规范 purge;旧任务形态不会序列化跨重启,不能宣称所有失败 purge 永久卡住。本修复主要恢复活跃的 marker MRF 路径,并完善旧形态兼容。 - -未覆盖或未修改:缺失客户端导致的目标状态遗漏、目标级 resync 的既有 purge 子集替换、复制跟踪已丢失、replica relay、purge 后延迟创建且无 tombstone、源端元数据写失败依赖 scanner、共享解析器的通用健壮性、额外 backoff/指标设计。完整多进程站点验收应另行安排。 - -原始大日志与临时复现:`/Users/vonng/tmp/silo-r6-20260915-aa3f/`。每轮 metadata 记录模型、命令、基线、方案及原始输出哈希。 diff --git a/docs/investigations/r6/consensus.md b/docs/investigations/r6/consensus.md deleted file mode 100644 index cb7193ff1..000000000 --- a/docs/investigations/r6/consensus.md +++ /dev/null @@ -1,53 +0,0 @@ -# R6 plan consensus — final v3 - -2026-09-15. Source baseline: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`. - -Earlier v2 agreed immutable plan: [plan-v2.md](plan-v2.md), SHA256 `dae51753a3ab4b2ea98b85e720144338fdf52541ca6daf968c7dac5ce564d8c3`. - -Codex accepts this plan. Real Claude Code 2.1.270, explicitly `--model claude-opus-5 --effort max`, read the same file and returned **GO_WITH_NONBLOCKING_NOTES, no blockers**, with explicit consensus. Every recorded assistant model in both rounds is `claude-opus-5`. CLI auxiliary usage is listed separately in the metadata files. The reviewer had read-only tools, so verified content but did not independently compute the hash; Codex computed the hash before and after review and confirmed it unchanged. The plan is 59 lines as described by the reviewer. A `rate_limit_event` telemetry record is not a failure verdict: the actual final result is `subtype=success, is_error=false`, and includes the explicit review and consensus. - -| Round | Verdict | Outcome | -|---|---|---| -| v1 | REVISE, one blocker | Accepted B1: preserve the disk creation block using the existing empty-update signal. All seven notes resolved/scoped in decisions-v2.md. No production code changed. | -| v2 | GO_WITH_NONBLOCKING_NOTES | Same immutable plan accepted by both parties; implementation and tests now proceed. | - -Raw logs: `/Users/vonng/tmp/silo-r6-20260915-aa3f/opus-v1.jsonl`, `opus-v2.jsonl`, and matching stderr logs. Extracted reviews and SHA/model/command metadata are alongside this document. No simulated reviewer or fallback model was substituted. - -## v2 nonblocking dispositions - -1. Keep canonical success audit normalization COMPLETE → COMPLETED, using replication.CompletedLegacy for conversion; assert the actual audit outcome and document the visible string correction. -2. Use operation status for per-target change comparisons. Assert count-only successful heal purge deltas, zero bytes, and no pending operation outcome for failed purges. No new metrics policy. -3. Initialize ResetStatusesMap before an existing assignment when nil, unconditionally safe; no general merge change. -4. Already-COMPLETE purge also short-circuits under ExistingObjectReplicationType, matching canonical behavior; add a matrix row. -5. Offline errors populate Err for both creation and purge. This is explicit error reporting with the same failed outcome. -6. Preserve getReplicationState's existing unused third parameter and shape-agnostic behavior. -7. Validate real marker/nonempty version/nonzero ModTime plus bucket and decoded object identity. Use decodeDirObject for the name comparison so the stricter gate does not reject the internal directory-object encoding. -8. Use int for delete RetryCount, matching the persisted MRF field and QueueReplicationHeal input. No on-disk format changes. -9. Purge-status subset replacement is pre-existing and remains outside R6; creation-block preservation under partial fan-out is newly tested. -10. Tests drive saveMRFEntries directly, verify the real stored record, and create a fresh pool for each load/queue replay. Timer waiting and process-crash durability are not claimed. - -These are implementation refinements within the accepted plan; Opus explicitly stated they require no new review round/hash. Consensus is not test acceptance, merge, release or deployment. Production implementation begins only after this record was written. - - -## Final v3 consensus, 2026-09-16 CST - -Final immutable plan: [plan-v3.md](plan-v3.md), SHA256 `dc9a67fc91b3113fa35218a2f903455807430cc4daf9c78a88d0be6b8fc27058` (70 lines; locally recomputed unchanged after review). - -Codex accepts v3. Real `claude-opus-5 --effort max` returned GO_WITH_NONBLOCKING_NOTES, no blockers, and explicit consensus on the exact read content. As before, read-only reviewer tools could not compute the digest; the model verified the named content and Codex verified its hash. Original review/metadata are opus-v3-review.md and opus-v3.metadata.json. The review candidly withdraws the earlier multi-target regex proof. This is actual additional review, not a simulated amendment to the earlier output. - -The v2 implementation was completed only after v2 agreement. Codex then found and reproduced the two-empty-status parser counterexample on real storage; the incremental three-field v3 source change remained unapplied until this new consensus was recorded. - -| v3 note | Disposition | -|---|---| -| N1 payload invariant | Accepted as mandatory: wrap the real ObjectLayer update in purge tests and assert all three creation-update fields and their composite are empty. | -| N2 masking scope | Ordinary purges are protected through FileInfo.Deleted=false; ordinary object versions have another such guard. The unrecorded marker case corrupts creation metadata only on its first failed write. | -| N3 ReplicaStatus | Clearing it is defensive, not a repair of an observed producer population. | -| N4 shared parser | Remains unchanged; generic parser robustness is separate. No future arbitrary caller guarantee is claimed. | -| N5 timestamp assignment | Retained; empty creation-update payload makes it irrelevant to purge disk creation metadata. | -| N6 prior proof | v2's regex proof is correct only for a single target. The immutable raw reviews and executable counterexample are both retained. | - -Only v3's three field assignments and the required invariant remain to be applied after this record. All earlier compatible refinements and acceptance limits still stand. - -## Implementation completion, 2026-09-16 CST - -The paragraph above records the state at approval time. The agreed v3 assignments and mandatory payload invariant have since been implemented and verified. After rebasing onto `af2b1794d38d9e70e1d2c3ee692426e4b6cab4bd`, all five R6 source/test hashes remained identical. Source commit `cf381a7151ef25fc95ace5fedcd767fa19410de2` passed the scoped regression, race, build, vet and lint checks, plus the six formerly capacity-blocked DELETE tests. See [README.md](README.md) and its linked machine verification records. Subsequent changes only document this evidence; no new production-plan deviation was introduced. diff --git a/docs/investigations/r6/decisions-v2.md b/docs/investigations/r6/decisions-v2.md deleted file mode 100644 index acccc6760..000000000 --- a/docs/investigations/r6/decisions-v2.md +++ /dev/null @@ -1,18 +0,0 @@ -# R6 v1 review disposition - -Actual reviewer: Claude Code 2.1.270, assistant model `claude-opus-5`, explicit effort max. Original verdict REVISE, one blocker; see opus-v1-review.md and opus-v1.metadata.json. No consensus or production implementation at this stage. - -| Item | Disposition | v2 change / evidence | -|---|---|---| -| B1 creation-status pin rewrites a partial target set | Accepted. The disk no-update semantics are preferable and smaller. | Purge results leave ReplicationStatus empty, only VersionPurgeStatus changes; assert full persisted creation state and timestamp across partial fan-out/repeated failure. No generic state merge rewrite. | -| N1 wire version fallback | Accepted. | Existing DeleteMarkerVersionID fallback retained; tests assert query version and false marker flag for every purge shape. | -| N2 queue-full retry budget | Accepted. | All three queueMRFSave sites for deletes increment RetryCount, including queueReplicaDeleteTask. | -| N3 completion statistics change | Accepted. | Check concrete Heal/ExistingObject counter deltas for COMPLETE-to-COMPLETED conversion. | -| N4 null/empty versions | Accepted as current boundary. | 405 recovery requires a real nonempty identity; empty/null special cases remain outside acceptance. | -| N5 live producer/old-shape scope | Accepted. | Old tasks are not serialized; robustness path distinguished from currently active MRF defect. | -| N6 detached MRF execution | Accepted. | Real disk persistence each round, new pool, synchronized queue receives with bounded timeout, no sleeping for presumed completion. | -| N7 fixture threshold | Resolved with actual execution. | Existing capacity adapter; baseline canonical and old scanner recovery pass. PR MRF canonical recovery completes on single/16-drive fixtures; old-shape failure still queues zero entries. | - -Correction to our research inference: a canonical purge's empty returned creation result causes replicatedInfos.ReplicationStatus() to report PENDING, but that does NOT show loss of the disk creation block. xlMetaV2 skips that block update when the composite creation status is empty. The temporary probe's in-memory assertion was too strong; do not promote it into a disk-state defect. The PR old-shape missing-MRF observation and all target-branch observations remain valid. - -The local-source metadata-write error path and missing-client/purge-subset merge behavior are explicitly documented acceptance limits. They are not new claims of convergence. v2 does not broaden the repair into those independent mechanisms. diff --git a/docs/investigations/r6/final-source-manifest.json b/docs/investigations/r6/final-source-manifest.json deleted file mode 100644 index 7528e2f5c..000000000 --- a/docs/investigations/r6/final-source-manifest.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "recorded_at_utc": "2026-09-15T16:31:10.707976+00:00", - "research_base": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "integration_base": "af2b1794d38d9e70e1d2c3ee692426e4b6cab4bd", - "verified_source_commit": "cf381a7151ef25fc95ace5fedcd767fa19410de2", - "branch": "codex/r6-delete-marker-mrf", - "source_sha256": { - "cmd/bucket-replication.go": "999c2818a8980cbeb55cfcbc244840069fb44e042b4b4d6ef7668c2660ce31e0", - "cmd/bucket-replication-utils.go": "365641c760901641e8320cc5123697ab92a46f1add612048b991ed2d1cfad43b", - "cmd/replication-delete-marker_test.go": "d967787804d558ac6266b113228fdf4a4f9fcb7cab39138a4fb07558814ccca4", - "cmd/replication-delete-operation_test.go": "2888a04a543324776041316de2821f388d28c3c1a6f5d0031e5ac9d34f58d504", - "cmd/replication-delete-mrf_test.go": "5e160f7e19cbbb8cd5fa4e7ffd9cff9e09361b3fc4c5ee5ae61458f99c777781" - }, - "source_patch_sha256": "f374335371c9c6bed00fe453a6e10270bedcc1f1f391bfc09338664b3f50d886", - "source_patch_raw_path": "/Users/vonng/tmp/silo-r6-20260915-aa3f/final-integrated-source.diff", - "dependency_and_lint_config_sha256": { - "go.mod": "8351bb86377a8deed95fd0bd67c1e363e11d33f8da7631cea8f68cea11259976", - "go.sum": "2287ce975cab91f92f59a3b6e164d49325f141f35d480b8c3d23f796df3772b2", - ".golangci.yml": "b18a20eb81da3e8714ba3d9cdf404913b7c8e011fe6958ff7f10e6410a4573ba" - }, - "final_plan": { - "path": "plan-v3.md", - "sha256": "dc9a67fc91b3113fa35218a2f903455807430cc4daf9c78a88d0be6b8fc27058" - }, - "opus_metadata_sha256": { - "opus-v1.metadata.json": "22b8d6b27a1112441b070bae096641eb5f0d2f954ddfe3fd88e17e0377b577f4", - "opus-v2.metadata.json": "9b6d7a44578fa6be9cc341a045db0c73fc7439ef7e0ee5a8aa99e66250493cc8", - "opus-v3.metadata.json": "678ed800aae399d627a19e9802aeb7109bf51ee539163263c2fec06aba4efbc3" - }, - "verification_record_sha256": { - "verification/baseline-verification.json": "700dddb0a9efab01a0524a51736fecd457ed81e689926c842fa2de6838f1e1d6", - "verification/rebased-delete-verification.json": "f370ba2c9c0b9d6695cf2701f97dbd774a336f833d474992f2c390e67d9cbf58", - "verification/rebased-verification.json": "4d9ad5688d45cba15fe9632590854a48e6fca048f5f3556d5edf9a4b1997d9ca" - }, - "go_version": "go version go1.27.1 darwin/arm64", - "GOMAXPROCS": "4", - "verification_source_unchanged": true, - "delivery_note": "The follow-up commit records verification documents only; all production and test files match verified_source_commit." -} diff --git a/docs/investigations/r6/opus-prompt-v1.md b/docs/investigations/r6/opus-prompt-v1.md deleted file mode 100644 index dfb54450d..000000000 --- a/docs/investigations/r6/opus-prompt-v1.md +++ /dev/null @@ -1,5 +0,0 @@ -You are the independent Opus reviewer for SILO R6. Read AGENTS.md first. Review only; do not edit files. We need real independent technical scrutiny, not a ceremonial approval. - -Source commit: 9ebe81c1b3611f9cc73e676b5b741c2be62c467a. Plan file: docs/investigations/r6/plan-v1.md. Plan SHA256: bcd023e2b00ad3dc709baa738bb02551d47aeebb49879b0f76f2789f4c4dff8b. Read that exact file in full and inspect the referenced current source and tests. PR184 raw patch and fresh baseline probes are in /Users/vonng/tmp/silo-r6-20260915-aa3f/ (read pr184.diff, review_probe_test.go, baseline.log if available). Its code direction is useful but its claims are not accepted evidence. Focus on every exit of replicateDeleteToTarget and replicateDelete, persisted queueMRFHeal with MethodNotAllowed ObjectInfo, preserving statuses, resync accounting and actual wire behavior on retries. - -Critically verify whether the minimal proposed code could restore an already removed marker, whether retry budgets and multi-target status remain safe within the explicit bounded scope. Explain any disagreement with precise code evidence and a concrete minimal correction. Return a verdict GO, GO_WITH_NONBLOCKING_NOTES, or REVISE, explicitly for plan v1 hash bcd023e2b00ad3dc709baa738bb02551d47aeebb49879b0f76f2789f4c4dff8b. List blocking findings separately from nonblocking suggestions. If there are no blockers, explicitly state that consensus on this plan is technically acceptable, while implementation still requires tests. Do not claim to have run anything. diff --git a/docs/investigations/r6/opus-prompt-v2.md b/docs/investigations/r6/opus-prompt-v2.md deleted file mode 100644 index 2d95f2c7c..000000000 --- a/docs/investigations/r6/opus-prompt-v2.md +++ /dev/null @@ -1,3 +0,0 @@ -Review the revised R6 plan, read-only. The exact source remains 9ebe81c1b3611f9cc73e676b5b741c2be62c467a. Read AGENTS.md, docs/investigations/r6/opus-v1-review.md, docs/investigations/r6/decisions-v2.md, and docs/investigations/r6/plan-v2.md (SHA256 dae51753a3ab4b2ea98b85e720144338fdf52541ca6daf968c7dac5ce564d8c3). Focus on resolving your B1 and coupled exits, and verify the amended retry queue-full site. We accepted your disk-preservation correction; the previous inference from an empty per-target return was too strong. No production implementation has started. Source and temporary probes remain available. Real storage fixtures now execute with the repository capacity adapter (only DiskInfo capacity changes); baseline-mrf.log and pr184-mrf.log have the evidence. - -Perform a targeted delta review of v2, inspect source as needed; do not re-review unrelated systems or attempt to write a file. Say GO, GO_WITH_NONBLOCKING_NOTES, or REVISE explicitly for plan-v2.md hash dae51753a3ab4b2ea98b85e720144338fdf52541ca6daf968c7dac5ce564d8c3, list any blockers, and if acceptable explicitly state consensus on the exact plan. Tests are future obligations, not completed acceptance. diff --git a/docs/investigations/r6/opus-prompt-v3.md b/docs/investigations/r6/opus-prompt-v3.md deleted file mode 100644 index ddc20b023..000000000 --- a/docs/investigations/r6/opus-prompt-v3.md +++ /dev/null @@ -1,5 +0,0 @@ -Targeted R6 incremental review, read only, no file writes. Real v2 consensus was GO; v2 implementation was then authorized and implemented. We found an executable counterexample to part of both our empty-composite proofs. Read docs/investigations/r6/plan-v3.md (SHA256 dc9a67fc91b3113fa35218a2f903455807430cc4daf9c78a88d0be6b8fc27058) especially the final incremental section, and /Users/vonng/tmp/silo-r6-20260915-aa3f/v2-unrecorded-purge.log. Inspect the current implementation and the new TestReplicationMRFMarkerRecovery/unrecorded-purge test. Source identity: baseline 9ebe81c1 plus implementation-v2.diff in that evidence directory; v3's three-field correction is NOT applied yet. - -The regex requires a character but permits a semicolon: two empty entries parse into a nonempty fake status. Usual purge metadata sets FileInfo.Deleted=false and masks it, but a task ahead of source purge metadata reproduces real creation-block overwrite with two targets. Proposed minimal delta is ONLY in outer replicateDelete after getReplicationState: if isPurge then drs.ReplicationStatusInternal="", drs.Targets=nil, drs.ReplicaStatus="". This makes CompositeReplicationStatus truly empty, preserves disk creation/replica blocks, retains purge/reset metadata, and avoids changing shared parsing or merging. - -Please independently verify the counterexample, field selection, whether the proposal introduces any loss or leak, and whether it suffices. Focus this review on the delta and its interactions rather than restarting unrelated exploration. Return explicit GO/GO_WITH_NONBLOCKING_NOTES/REVISE for plan-v3.md hash dc9a67fc91b3113fa35218a2f903455807430cc4daf9c78a88d0be6b8fc27058; list blockers and state exact-plan consensus if acceptable. Do not claim tests ran or that the normal producer necessarily emits the special setup. The reviewers' earlier multi-target regex proof was too strong; revise the conclusion candidly. Other scope limits remain explicit. diff --git a/docs/investigations/r6/opus-v1-review.md b/docs/investigations/r6/opus-v1-review.md deleted file mode 100644 index d7db521b4..000000000 --- a/docs/investigations/r6/opus-v1-review.md +++ /dev/null @@ -1,78 +0,0 @@ -I'll start by reading AGENTS.md and the plan file, then dig into the actual source. - -Let me locate the core source files and the evidence directory. - -I've verified the plan against the actual source. Writing up the review now. - -The `Write` tool is disabled in this session, so I can't create the plan file — the review is below in full. - ---- - -# Independent review — SILO R6 `plan-v1.md` - -**Plan hash reviewed:** `bcd023e2b00ad3dc709baa738bb02551d47aeebb49879b0f76f2789f4c4dff8b` -**Source:** `9ebe81c1b3611f9cc73e676b5b741c2be62c467a` -**I did not run anything.** All findings come from reading the source, the existing tests, PR #184's diff/prose, and the supplied `baseline.log` (read, not produced by me). - -## Verdict: **REVISE** - -One blocking finding. The classification rule, the canonical-wire choice, the MRF 405 work, and the retry-budget work are technically correct and better grounded than PR #184. One explicitly prescribed detail in §A causes a silent regression on the *canonical* purge shape — the shape every live producer emits — and §C would not catch it. The correction is small and stays inside the plan's bounded scope. - ---- - -## Blocking - -### B1 — Pinning `rinfo.ReplicationStatus = rinfo.PrevReplicationStatus` for purges makes the persisted marker creation-status block get rewritten from the fan-out subset - -§A prescribes this "on every exit" while also forbidding changes to `getReplicationState` merging. Together that is a regression, not preservation. - -1. `bucket-replication-utils.go:392-399` — `targetState()` never sets `ReplicationStatus`, so a canonical purge returns `""` today. -2. `bucket-replication-utils.go:92-101` — `ReplicationStatusInternal()` rebuilds `"arn=STATUS;"` **only from `rinfos.Targets`**, i.e. only fanned-out targets. -3. `bucket-replication.go:508-543` — fan-out skips `!Replicate`, non-matching `dobj.TargetArn`, and nil clients. `TargetArn` is really set by `queueReplicateDeletesWrapper` (`:2400-2409`). -4. `bucket-replication-utils.go:410-412` → `drs`, passed as `DeleteReplication` at `bucket-replication.go:572-582`. -5. `erasure-object.go:2163-2177` → `xl-storage-format-v2.go:1438-1446` — for a `DeleteType` version, **if `fi.DeleteMarkerReplicationStatus()` is non-empty**, `MetaSys[ReplicationStatus]` and `MetaSys[ReplicationTimestamp]` are overwritten. - -Today that guard never fires for a canonical purge: the string is `"arn1=;"`, `replicationStatusesMap` doesn't match it (`:428-439`), composite over the empty map is `""` (`:455-459`) — so the on-disk creation block is left **untouched**, which preserves it perfectly including non-attempted targets. Under the plan it becomes non-empty and is rewritten: - -- **Status loss:** `"arn1=COMPLETED;arn2=COMPLETED;"` with a fan-out covering only `arn2` persists `"arn2=COMPLETED;"` — `arn1` silently dropped. That is the exact failure the plan exists to fix, newly introduced on the main shape. -- **Zero timestamp:** `rs.ReplicationTimeStamp = rinfos.ReplicationTimeStamp` (`:413`) is never assigned in `replicateDelete`; it's only rescued by `bucket-replication.go:568-570` when the composite *changes*. A repeated FAILED→FAILED purge writes `0001-01-01T00:00:00Z`. -- **Empty ReplicaStatus:** under a legacy `RoleArn` config the composite can be `REPLICA` (`bucket-replication-utils.go:497-503`), taking `xl-storage-format-v2.go:1398-1400`, which writes `ReplicaStatus` — never populated by `ObjectInfo.ReplicationState()` (`:569-587`). - -Only delete-**marker** versions are affected; `ObjectType` versions only touch `VersionPurgeStatusKey` (`xl-storage-format-v2.go:1465-1476`). - -**Minimal correction (strictly smaller than the plan):** keep the classification and every exit fix, but for purges **leave `rinfo.ReplicationStatus` at its zero value** rather than pinning it. That is what the canonical path already does, and it preserves the on-disk block byte-for-byte including non-attempted targets, with no change to `getReplicationState`. Consequences that then become mandatory, not optional: - -- purge exits write only `VersionPurgeStatus` — never `Failed`, `Completed`, or `PrevReplicationStatus`; -- the resync defer (`:618-622`) must be gated on the operation's own success. Under the plan as written, pinning `Completed` would stamp the reset marker for a **failed** purge on any marker whose creation was COMPLETED — the two changes are coupled and cannot land separately; -- the stats gate at `:556` must be replaced by the per-target operation-status comparison §A already calls for, otherwise purges stop being reported at all. - -Add a partial-fan-out test: two ARNs persisted, one excluded from fan-out, failed purge → marker metadata unchanged. §C item 5 fans out to *both* targets and cannot detect this. - ---- - -## Nonblocking - -1. **The wire version ID is load-bearing.** For the legacy shape `dobj.VersionID` is empty, so the implementation must keep the existing `versionID` fallback (`bucket-replication.go:609-612`). A `RemoveObject` with empty `VersionID` against a versioned target **creates a new marker** (`erasure-object.go:2126-2149`) — the exact resurrection being fixed. Make §C1 assert the outgoing `versionId` and `x-minio-source-deletemarker` explicitly. -2. **Retry increment must cover all three delete-path MRF sites:** `:487` (lock), `:565` (aggregate failure), `:2427` (queue full). §B names only the first two; missing `:2427` leaves an unbounded loop once marker MRF is live. Mirror `ri.RetryCount++` at `:1310`. -3. **Stats will move.** `COMPLETE`→`COMPLETED` makes `ReplicationStats.Update` reach its `Completed` case for Heal/ExistingObject deletes (`bucket-replication-stats.go:184-201`, `replication.go:139-144`). Today `"COMPLETE" != "COMPLETED"` so nothing is recorded. Assert expected counter deltas rather than discovering them. -4. **§B's identity gate excludes null-version markers by construction** — `GetObjectInfo` returns `ObjectNotFound`, not 405, when `VersionID == ""` (`erasure-object.go:996-999`), and `ToObjectInfo` leaves `VersionID` empty when `versioned` is false (`erasure-metadata.go:120-123`). No regression, but document it instead of implying MRF healing is complete. -5. **Legacy-shape scope.** I found no producer of that shape on this baseline: `object-handlers.go:3225-3228`, `bucket-handlers.go:565-570`, `bucket-replication.go:3323-3327`/`:3775-3779` are mutually exclusive, and `erasure-object.go:1752-1766` only sets `DeleteMarkerVersionID` when `VersionID == ""`. `DeletedObjectReplicationInfo` isn't serialized, so it can't survive a restart. §A's legacy handling is upgrade/robustness work; the live value of R6 is mostly §B. Say so in the PR text so the fork doesn't inherit #184's overclaiming. -6. **§C item 3 is testable but fiddly.** `globalLocalDrivesMap` is populated by `newErasureServerPools` (`erasure-server-pool.go:174-181`), so save/load works — but `loadMRF` **deletes the file after reading** (`:4013-4015`) and `queueMRFHeal` dispatches a detached goroutine with a 1s per-entry context (`:4067-4081`). Re-persist between rounds and synchronize, don't sleep. -7. **Confirm the fixtures run.** The supplied `baseline.log` shows both `TestReplicateDeleteMarkerPurge` subtests aborting at `replication-delete-marker_test.go:121` with "Storage reached its minimum free drive threshold" — an environment failure. §C items 2, 3, 5 all depend on those fixtures. - ---- - -## Independently confirmed as correct in the plan - -- **Observation 1 holds at every exit:** `:624` (early-return without sending), `:645-649`/`:702-706`/`:712-716` (field selected on `dobj.VersionID`), `:684-688` (HEAD-not-ready overwrites creation status unconditionally), `:628` (completed-purge early-out only for non-empty `VersionID`), `:618-622` (resync stamp keyed on `ReplicationStatus`). The supplied probe log agrees. -- **Observation 2 is a real regression in PR #184.** `replicateDelete` selects on `dobj.VersionID != ""` (`:549-552`), so with only the target-side fix a failed legacy purge aggregates to `COMPLETED`, emits `ObjectReplicationComplete`, and **skips `queueMRFSave`** (`:563-566`) — worse than baseline. -- **Task-level classification is required; #184's per-target `isDMPurge` is wrong.** `VersionPurgeStatus()` needs `completed == len(ri.Targets)` (`bucket-replication-utils.go:122-139`); a target classified as a creation never sets the purge field, so the composite can never reach COMPLETE. The task-level rule is also safe here — no current producer emits a creation with a non-empty composite purge status. -- **The canonical-wire choice really does fix resurrection; #184 does not.** With `ReplicationDeleteMarker=true` and an absent version the receiver re-creates the marker (`xl-storage.go:1346-1349`; `xl-storage-format-v2.go:1517-1520`). With `false`, the receiver returns VersionNotFound (`erasure-object.go:2013-2027`) and the handler answers 204 (`object-handlers.go:3171-3192`). Scope it honestly: this only changes *legacy-shaped* purges (canonical already sends `false`), and does nothing for a delayed creation after a purge — correctly filed as separate. -- **§B's 405 premise is exact:** `erasure-object.go:996-1002` returns a populated `ObjectInfo` with `toObjectErr(errMethodNotAllowed,…)` → `MethodNotAllowed{}` (`object-api-errors.go:96-102`), preserved multi-pool too (`erasure-server-pool.go:1063-1077`). -- **§B's retry gap is real:** `ToMRFEntry()` (`:1927-1937`) never sets `RetryCount`, so the `> mrfRetryLimit` drop (`:3883-3887`) can never fire for deletes. No msgp code exists for the type, so the field is genuinely schema-free. -- **Resync accounting already handles both shapes** — `resyncTargetSucceeded` (`:3154-3162`) keys on `roi.VersionPurgeStatus`. The plan correctly leaves it alone. -- **The pushback on #184's permanence/topology prose is correct.** Canonical purge retries are not short-circuited on this baseline (`:624` requires `VersionID == ""`; `:628` exits only on `VersionPurgeComplete`). #184's Bugs 1 and 2 as narrated are legacy-shape-only. - ---- - -Because B1 is a blocker, I am not stating consensus on this exact hash. Resolve B1 (and the coupled resync-defer/stats-gate items it forces) and the plan becomes technically acceptable in my judgement — implementation would still require the tests in §C plus the partial-fan-out regression above. diff --git a/docs/investigations/r6/opus-v1.metadata.json b/docs/investigations/r6/opus-v1.metadata.json deleted file mode 100644 index f08cd80f1..000000000 --- a/docs/investigations/r6/opus-v1.metadata.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "source_commit": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "requested_model": "claude-opus-5", - "requested_effort": "max", - "cli_version": "2.1.270", - "plan": "plan-v1.md", - "plan_sha256": "bcd023e2b00ad3dc709baa738bb02551d47aeebb49879b0f76f2789f4c4dff8b", - "assistant_models": [ - "claude-opus-5" - ], - "raw_path": "/Users/vonng/tmp/silo-r6-20260915-aa3f/opus-v1.jsonl", - "raw_sha256": "16f1954b213b5813a72a8db6d960d7933e1065c63e9ae2d6f139d0cff881011f", - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --add-dir /Users/vonng/tmp/silo-r6-20260915-aa3f --output-format stream-json --verbose", - "auxiliary_usage_models": [ - "claude-haiku-4-5-20251001", - "claude-opus-5" - ], - "subtype": "success", - "is_error": false, - "session_id": "927a163e-c14f-4366-b1bc-dbb363fbe793", - "duration_ms": 357292, - "num_turns": 61 -} diff --git a/docs/investigations/r6/opus-v2-review.md b/docs/investigations/r6/opus-v2-review.md deleted file mode 100644 index 0b9453cf1..000000000 --- a/docs/investigations/r6/opus-v2-review.md +++ /dev/null @@ -1,86 +0,0 @@ -I'll start by reading the required documents and verifying the plan hash. - -Now let me verify the plan hash and inspect the source claims. - -I read AGENTS.md, opus-v1-review.md, decisions-v2.md, and plan-v2.md, then re-checked every claim v2 changed against the source at `9ebe81c1b`. I ran nothing; findings are source reading plus the supplied logs. - -One honesty note up front: **no shell tool is available in this session, so I could not compute the SHA256 myself.** My verdict is on the exact content I read at `docs/investigations/r6/plan-v2.md` — 59 lines, opening `# R6 plan v2 — marker purge operation and MRF recovery`, dated 2026-09-15, closing with the "v2 changes from Opus v1" section that lists B1/N1–N7 dispositions. If that is `dae51753a3ab4b2ea98b85e720144338fdf52541ca6daf968c7dac5ce564d8c3`, the verdict below attaches to that hash. - -# Verdict: GO_WITH_NONBLOCKING_NOTES - -**Blockers: none.** B1 is resolved, and resolved by the correct mechanism rather than by wording. - ---- - -## B1 and the coupled exits — resolved, mechanism verified end-to-end - -§A now says purges leave `rinfo.ReplicationStatus` empty on every exit. I traced the preservation chain to make sure that is actually load-bearing and not just an absence: - -1. `replicatedInfos.ReplicationStatusInternal()` (`cmd/bucket-replication-utils.go:92-101`) emits `"arn1=;"` — non-empty, so it does reach `ReplicationState`. -2. `replicationStatusesMap` (`:428-439`) uses `replStatusRegex` (`:168`), whose second group `([^,].*?)` requires ≥1 char before `;`. `"arn1=;"` does not match → **empty `Targets` map**. -3. `CompositeReplicationStatus()` (`:356-372`) therefore takes the `default:` branch → `getCompositeReplicationStatus(empty)` → `""`; the `ReplicaTimeStamp` fall-through at `:366-371` also returns `""` because `replStatus == Completed` is false. So the composite is `""` under every sub-case. -4. `xl-storage-format-v2.go:1438` gates the `DeleteType` rewrite on `!fi.DeleteMarkerReplicationStatus().Empty()` → **guard never fires** → `MetaSys[ReplicationStatus]`, `MetaSys[ReplicationTimestamp]` and the Replica pair at `:1441-1442` are left byte-for-byte intact, including ARNs excluded from fan-out. `updateVersion` is still true via `:1390`, so `MetaSys[VersionPurgeStatusKey]` at `:1448-1449` is written as intended. - -All three consequences I said were mandatory and non-separable are present: - -| Coupled item | v2 | -|---|---| -| Purge exits write only `VersionPurgeStatus` | §A, explicit, "on every exit"; `PrevReplicationStatus` retained for inspection only — harmless, `targetState()` sets it and nothing persists it | -| Resync defer gated on the operation's own success | §A "Do not stamp the current resync reset for a failed purge"; §C1 asserts the reset marker | -| `:556` stats gate replaced by per-target operation-status comparison | §A "Feed per-target old/new operation status into stats rather than selecting changes from the unrelated creation status" | -| Partial-fan-out regression test | §C5, with two creation ARNs, a non-zero timestamp, and fan-out restricted to one ARN | - -The zero-timestamp and `ReplicaStatus` sub-findings from v1 are dissolved rather than patched: with the composite empty, `:1444-1445` is never reached, so `drs.ReplicationTimeStamp` (set at `bucket-replication.go:568-570`) cannot land on disk for a purge at all. - -I also checked the downstream consumer the change could have silently flipped: `resyncTargetSucceeded` (`bucket-replication.go:3154-3162`) keys the purge branch on `roi.VersionPurgeStatus` and reads `t.VersionPurgeStatus`, never `t.ReplicationStatus`. Leaving `ReplicationStatus` empty makes resync accounting strictly more correct for the old shape (today a resynced old-shape purge sets `ReplicationStatus = Completed` at `:713`). §A's "leave it alone" is right. - -## Amended retry queue-full site — correct - -`cmd/bucket-replication.go:2411-2448`, `queueReplicaDeleteTask`, `default:` branch of the select at `:2426-2427` → `p.queueMRFSave(doi.ToMRFEntry())`. §B names it exactly ("queueReplicaDeleteTask queue-full fallback"). I grepped every `queueMRFSave(` call: the delete-path sites are precisely `:487` (lock), `:565` (aggregate failure), `:2427` (queue-full) — three, no more. `:1206`, `:1311`, `:2339`, `:2370` are object-path. - -The supporting claims hold too: `MRFReplicateEntry.RetryCount` already exists with msgp tag `rc` (`bucket-replication-utils.go:792`), so no format change; `DeletedObjectReplicationInfo.ToMRFEntry()` (`:1927-1937`) sets only `Bucket`/`Object`/`versionID`; `versionID` is unexported but survives as the map key (`persistMRF` `:3870`, read back at `:4068`); the `> mrfRetryLimit` drop at `:3883-3887` is therefore currently unreachable for deletes; and `DeletedObjectReplicationInfo` has no generated msgp code, so the new field is genuinely schema-free. The gap's exact location is `queueReplicationHeal:3762` setting `roi.RetryCount` while `dv` at `:3781-3793` drops it — which is what §B closes. - -## N7 evidence — checks out - -`baseline-mrf.log` shows `TestReplicateDeleteMarkerPurge` both subtests **PASS**; the "Storage reached its minimum free drive threshold" abort from v1 is gone, so §C items 2/3/5/6 have a working fixture. The logs also independently corroborate two plan premises on real storage: `err=Method not allowed` on the source marker lookup (§B's 405 premise), and `legacy=true … MRF=0` on both baseline and PR #184 (observation 2, and #184's residual gap). Your retraction is right, and I'd add that the mechanism is visible: `replicatedInfos.ReplicationStatus()` (`bucket-replication-utils.go:103-119`) counts *every* target including empty-status ones, so `creation=PENDING` for a canonical purge is an artifact of that aggregate, not disk state. The retained logs still carry the old "failure stored in incorrect status field" assertion text on the canonical rows — worth a pointer to decisions-v2's retraction beside them so a later reader doesn't re-derive the wrong conclusion. - ---- - -## Non-blocking notes - -1. **Audit `Status` string changes on the live canonical path.** §A folds audit into the COMPLETE→COMPLETED mapping. The audit defer at `bucket-replication.go:434-444` logs `Status: string(replicationStatus)`, so every successful canonical versioned-delete replication goes from `COMPLETE` to `COMPLETED`. That is a user-visible change on the *live* path, not the legacy one. Keep it — `CompletedLegacy` is documented as an error at `internal/bucket/replication/datatypes.go:35-36` — but assert the audit string in §C, reuse `replication.CompletedLegacy` rather than a `"COMPLETE"` literal, and mention it in the PR text. -2. **The stats delta is wider than §C5's Heal/ExistingObject framing.** The `Completed` case gate is right (`bucket-replication-stats.go:189-192` requires `IsDataReplication()`, which excludes the unset OpType on handler-originated deletes — `replication.go:139-145`). But replacing the `:556` gate also changes *which* purges reach `Update`: today `""` vs `COMPLETED` makes that gate fire for nearly every purge of a previously-replicated version, and old-shape purges currently record a spurious `Pending` (aggregate `Pending`, prev `COMPLETED`). Assert that spurious `Pending` disappears too. Note `ri.Size` is never set in `replicateDeleteToTarget`, so `Completed` deltas are count-only, zero bytes. -3. **Make the `ResetStatusesMap` nil-guard unconditional** instead of contingent on tests exposing it (plan line 51). `getReplicationState:419-422` writes into `prevState.ResetStatusesMap` unguarded; `ObjectToDelete.ReplicationState()` (`:590-600`) leaves it nil, unlike `ObjectInfo.ReplicationState()` (`:576`). Today it is unreachable only because the resync defer requires `ReplicationStatus == Completed`, which purges never reach — and §A re-gates exactly that defer. I traced the live producers: the sole `ExistingObjectReplicationType` delete is `:3329-3342`, fed by `getHealReplicateObjectInfo` → `oi.ReplicationState()` → non-nil, so this is robustness and test-fixture safety, not a live panic. Two lines; just do it. -4. **Pin the already-COMPLETE purge under ExistingObject resync in §C1.** The purge early-out at `:628` has no `OpType != ExistingObjectReplicationType` exclusion, unlike the creation early-out at `:624`. Routing old-shape purges through it means a resync of an already-COMPLETE old-shape purge now short-circuits where today it re-sends. That matches canonical behaviour and is probably intended, but §C1's "resync success/failure" row currently leaves the implementer free to pick either. -5. **Scope "Preserve actual target failure in Err, including offline error where appropriate."** The offline exit (`:631-651`) sets no `Err` today, and `Err` flows into `replStat.set(...)` → `srUpdate` → site-replication stats. Say whether creations also start carrying an offline `Err`, or restrict it to purges; otherwise §C6's offline row has no fixed expectation. -6. **Leave `getReplicationState`'s third parameter alone.** `vID` (`bucket-replication-utils.go:402`) is entirely unused in the body. Since §A re-plumbs shape classification, someone will be tempted to wire it up; the empty-composite preservation depends on that function staying shape-agnostic. -7. **§B identity gate: key on `versionID` + `DeleteMarker` + non-zero `ModTime`.** `queueMRFHeal` calls `GetObjectInfo` with `ObjectOptions{VersionID: vID}` and no `Versioned`, so `ToObjectInfo` (`erasure-metadata.go:118-123`) returns `fi.VersionID` verbatim — the gate works. Bucket/object equality is trivially satisfied (they are the request arguments); the one component that can be perturbed is a strict `oi.Name == e.Object` against `decodeDirObject` (`cmd/utils.go:899-904`) for directory objects. `erasure-server-pool.go:1063-1077` does return the populated `oi` with the error upward, so the gate has real data to inspect. -8. **RetryCount type.** `ReplicateObjectInfo.RetryCount` is `uint32` (`:3762`), `MRFReplicateEntry.RetryCount` is `int` (`:792`), `QueueReplicationHeal` takes `int`. Pick one for the new delete-task field and keep conversions in one place. -9. **State the purge-subset loss as pre-existing, not merely excluded.** The asymmetry §C5 carves out is structural: creation status survives partial fan-out *because* the composite is empty (`:1438`), while `VersionPurgeStatusInternal()` is non-empty and unconditionally rewrites `MetaSys[VersionPurgeStatusKey]` from the fan-out subset (`:1448-1449`). Canonical purges already lose excluded-ARN purge status today, so R6 neither causes nor fixes it. Say that, or a reader will assume both were repaired. -10. **§C3 flush mechanics.** `persistMRF` (`:3866-3874`) only writes to disk on the 5-minute timer, ctx cancel, or 1M entries; the re-queued entry lands in an in-memory map first. N6's "persist each failed entry again" covers it, but make explicit that the test drives `saveMRFEntries` directly rather than waiting on the timer — and that `loadMRF` deletes the file after reading (`:4013-4015`), so each round needs a fresh write. - ---- - -## Consensus - -I state consensus on **`docs/investigations/r6/plan-v2.md` exactly as reviewed here** (hash `dae51753a3ab4b2ea98b85e720144338fdf52541ca6daf968c7dac5ce564d8c3` as supplied; content verified, digest not independently computed for lack of a shell tool). The classification rule, the all-exits treatment, the canonical-wire choice, the empty-creation-status preservation mechanism, the valid-405 gate, the three-site retry budget, and the §C matrix are technically correct against `9ebe81c1b`, and the scope limits in the "Existing state, compatibility, and delivery" section are accurate rather than overclaimed. The ten notes above are refinements and assertions to add, not conditions on this agreement — none of them requires another review round or a new hash. - -Tests in §C remain future obligations. Nothing here asserts they pass; the only executed evidence I relied on is the supplied baseline/PR logs, which establish fixture viability and the two defect premises, not acceptance. - -The review above is complete and stands as delivered. Summarizing the required outputs: - -**Verdict for `docs/investigations/r6/plan-v2.md`, hash `dae51753a3ab4b2ea98b85e720144338fdf52541ca6daf968c7dac5ce564d8c3`: GO_WITH_NONBLOCKING_NOTES** - -**Blockers: none.** - -**Consensus:** I state consensus on the exact plan as reviewed. The one caveat is procedural, not substantive: no shell tool is available in this session, so I verified the plan's *content* (59 lines, opening `# R6 plan v2 — marker purge operation and MRF recovery`, closing with the B1/N1–N7 disposition list) but could not independently compute its digest. Consensus attaches to that content. - -What I resolved this round: -- **B1** — accepted resolution, and I confirmed the mechanism is real rather than merely absent: `"arn1=;"` fails `replStatusRegex`, so `Targets` is empty, so `CompositeReplicationStatus()` returns `""` under every sub-case including the `ReplicaTimeStamp` fall-through, so the `xl-storage-format-v2.go:1438` guard never fires and the on-disk creation block survives partial fan-out intact — while `updateVersion` still writes `VersionPurgeStatusKey`. -- **Coupled exits** — all three present (purge-only status writes; resync defer gated on the operation's own success; `:556` gate replaced by per-target operation status), plus the §C5 partial-fan-out test. I also checked the one consumer that could have flipped silently, `resyncTargetSucceeded:3154-3162`, and it keys on `VersionPurgeStatus` — so the change makes resync accounting strictly more correct. -- **Queue-full retry site** — `queueReplicaDeleteTask`, `default:` branch, `bucket-replication.go:2427`; the delete-path `queueMRFSave` sites are exactly `:487`, `:565`, `:2427`, and the `RetryCount` disk field already exists with msgp tag `rc`. -- **N7** — the fixture-threshold failure is gone; `TestReplicateDeleteMarkerPurge` passes on baseline, and the logs corroborate the 405 premise and the `legacy=true … MRF=0` gap on both baseline and #184. - -The ten non-blocking notes are in my response; the three worth acting on before you branch are the audit-string change on the live canonical path (note 1), the wider-than-stated stats delta (note 2), and making the `ResetStatusesMap` nil-guard unconditional (note 3). None requires another review round or a new hash. - -Tests in §C remain future obligations — nothing here asserts they pass. I made no edits and wrote no files. diff --git a/docs/investigations/r6/opus-v2.metadata.json b/docs/investigations/r6/opus-v2.metadata.json deleted file mode 100644 index ee21bdaaf..000000000 --- a/docs/investigations/r6/opus-v2.metadata.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "source_commit": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "requested_model": "claude-opus-5", - "requested_effort": "max", - "cli_version": "2.1.270", - "plan": "plan-v2.md", - "plan_sha256": "dae51753a3ab4b2ea98b85e720144338fdf52541ca6daf968c7dac5ce564d8c3", - "assistant_models": [ - "claude-opus-5" - ], - "raw_path": "/Users/vonng/tmp/silo-r6-20260915-aa3f/opus-v2.jsonl", - "raw_sha256": "aa4d04931875e82ae850a8fa2b875c094f36625784c8c090781f9a0273d20871", - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --add-dir /Users/vonng/tmp/silo-r6-20260915-aa3f --output-format stream-json --verbose", - "auxiliary_usage_models": [ - "claude-haiku-4-5-20251001", - "claude-opus-5" - ], - "subtype": "success", - "is_error": false, - "session_id": "c6dfdf82-d542-47a1-8e69-a48753d61b12", - "duration_ms": 219946, - "num_turns": 41 -} diff --git a/docs/investigations/r6/opus-v3-review.md b/docs/investigations/r6/opus-v3-review.md deleted file mode 100644 index bfdc66364..000000000 --- a/docs/investigations/r6/opus-v3-review.md +++ /dev/null @@ -1,77 +0,0 @@ -I'll start by reading the key evidence documents and the current implementation state. - -I have everything I need. Let me verify one last interaction — whether the delta is pinned by any existing test — and then write the review. - -Write is disabled in this session (consistent with your read-only constraint), so the review is delivered here in full. - -# Verdict: **GO_WITH_NONBLOCKING_NOTES** — `plan-v3.md` - -**Blockers: none.** - -Digest caveat: no shell tool in this session, so I could not compute SHA256 and cannot attest `dc9a67fc91b3113fa35218a2f903455807430cc4daf9c78a88d0be6b8fc27058`. Consensus attaches to the content I read — 70 lines, opening `# R6 plan v3 — marker purge operation and MRF recovery`, closing `...including the proof now corrected by the executable counterexample.` — the same procedural caveat the v2 reviewer recorded. I ran no tests; `v2-unrecorded-purge.log` is treated as reported evidence, not as my own execution. - -Source reviewed: worktree = `9ebe81c1` + `implementation-v2.diff`, v3 delta **not** applied. - ---- - -## 1. Counterexample verified — and I am withdrawing the general form of the earlier proof - -The v2 consensus rested on: empty per-target status ⇒ `"arn=;"` ⇒ no regex match ⇒ empty `Targets` ⇒ empty composite ⇒ guard at `cmd/xl-storage-format-v2.go:1438` never fires. **That holds only for |targets| ≤ 1. It is false for ≥ 2.** The reviewers' multi-target regex proof was too strong; the executable counterexample is correct. - -Trace against `replStatusRegex = ([^=].*?)=([^,].*?);` (`cmd/bucket-replication-utils.go:168`), input `arn1=;arn2=;`: -- group 1 lazily reaches the first `=` → `arn1` -- group 2's `[^,]` **accepts `;`** (the class excludes comma, not semicolon), then `.*?` runs to the next `;` → `;arn2=` -- one match spans the whole string → `{arn1: ";arn2="}` - -Then `CompositeReplicationStatus` (`:356-379`): non-empty internal, not a legacy literal → `default` → `getCompositeReplicationStatus` sees one bogus entry → **`Pending`**. The `ReplicaTimeStamp` fall-through at `:366-371` cannot rescue it (it only returns `ReplicaStatus` when `replStatus == Completed`). Generalizes: N=1 → `""`; every N ≥ 2 → `Pending`, always via a `;`-prefixed value, so it can never accidentally land on a real status. - -**Why the ordinary multi-target tests passed** — plan §v3 is right, with one mechanism refinement: the masking is not in the guard, it is in `FileInfo.DeleteMarkerReplicationStatus()` (`cmd/erasure-metadata.go:670-675`), which returns `""` whenever `!fi.Deleted`, regardless of the composite. `erasureObjects.DeleteObject` sets `deleteMarker=false` when `versionFound && !goi.VersionPurgeStatus.Empty()` (`cmd/erasure-object.go:2100-2101`) — the normal state after the handler's PENDING write. A **second independent masking condition** sits at `:2102` (`else if !goi.DeleteMarker`), so purges of ordinary object versions are never exposed: the hole is confined to delete-marker versions whose on-disk purge status is absent. - -Unrecorded path: disk marker has creation metadata only → `deleteMarker` stays `opts.Versioned=true` → `fi.Deleted=true` → `DeleteMarkerReplicationStatus()` = bogus `Pending` → guard at `:1438` fires → `default` branch rewrites `ReplicationStatus` = `arn1=;arn2=;` and `ReplicationTimestamp` = `UTCNow()` (supplied by `:576`, since FAILED ≠ PENDING). Exactly the recorded log: statuses emptied, stamp jumping `15:06:15Z` → `16:06:15.612037Z`. - -The fixture is sound: the recreate at `replication-delete-mrf_test.go:258` genuinely removes the marker (`updateVersion=false` → removal branch at `:1457`), the reseed at `:261-264` writes a creation-only block, and `deletion` — captured from the real signed handler DELETE at `:249` — still carries PENDING purge state. `before` is sampled at `:304`, after the reseed. - -**Scoping, unweakened:** the normal producer does not demonstrably race. `DeleteObjectHandler` enqueues only after a successful write and derives the task from the returned `objInfo` (`cmd/object-handlers.go:3164, 3222-3242`); `queueReplicationHeal` derives it from current disk state (`cmd/bucket-replication.go:3808-3821`). Reaching the unrecorded shape needs disk/task divergence — e.g. a partial-quorum PENDING write later healed back from a stale shard while the in-memory task survives. Narrow, not impossible, unproven. Two facts bound the blast radius: the damage is **one-shot** (the same failed write records the purge status, masking every later round), and it is erased if the purge ever completes. It remains real metadata corruption while a marker is stuck — garbage per-target creation statuses plus a lost creation timestamp. - -## 2. Field selection — correct, with one candid downgrade - -| Field | Assessment | -|---|---| -| `ReplicationStatusInternal = ""` | **Load-bearing.** Direct cause; forces the composite to case 3. | -| `Targets = nil` | **Consistency hygiene.** The composite switches on the string, so it changes nothing today; it prevents an internally inconsistent state (empty string, populated map) misleading a future reader. Keep. | -| `ReplicaStatus = ""` | **Defensive, not load-bearing today.** The fallback at `:374-375` is real, but no production producer populates it: `ObjectInfo.ReplicationState()` (`:572-590`) and `ObjectToDelete.ReplicationState()` (`:593-602`) both omit the replica fields, and every delete-task construction site (`bucket-replication.go:2678, 3361, 3813`, `object-handlers.go:3237`, `erasure-object.go:1715, 1758, 1764`) routes through one of them. Keep it — free, and it closes the documented fallback — but it does not repair anything observed. | - -`isPurge` is the right gate: one value computed pre-fan-out at `:428`, identical to the one `replicateDeleteToTarget` uses at `:616`. Creations untouched. - -## 3. Loss / leak — none found - -- **Creation + replica blocks preserved.** Both write sites (`:1396` ventry, `:1438` in-place) sit behind the same now-dead guard, and `:1430-1453` mutates the existing `ver.DeleteMarker.MetaSys` by key, never clearing it. A no-update payload, exactly as the plan says. -- **Purge block / reset map still written** (`:1448-1453`); `updateVersion` unchanged (`:1380` diverts on the non-empty purge status, `:1390` sets it for any non-COMPLETE purge). -- **Successful purge:** composite COMPLETE → `updateVersion=false` → version removed at `:1457`; `:1458`/`:1460` both stay false. Unchanged. -- **ventry path (`:1395-1411`, added at `:1506`) unreachable for purges:** reaching `:1506` requires an `ObjectType` version, and for those `fi.Deleted` is always false via `:2100-2103`. -- **Zero fan-out (nil clients):** two of three assignments are already no-ops; I traced the `ReplicaStatus` case through `:2081/:2084` and `:1380` for replica and non-replica sources — identical outcome with and without the delta. Pre-existing behavior, already an explicit exclusion. -- **No re-derivation risk:** `SetDeleteReplicationState` runs only under `opts.EvalMetadataFn != nil` (`erasure-object.go:2030-2038`, `erasure-server-pool-consistency.go:342-351`); `replicateDelete` never sets it, so emptied fields are not refilled with a PENDING decision. -- **`ReplicationState.Equal`** is used only by `FileInfo.ReplicationInfoEquals` comparing two on-disk infos. No interaction. -- **Only observable change:** `dobjInfo` handed to `sendEvent` at `:604-610` carries empty `ReplicationStatusInternal`/`ReplicationStatus` instead of bogus `PENDING` for multi-target purges (`erasure-metadata.go:160-162`). Strict improvement; that payload never reflected disk state. Worth one line in the PR description. -- **Stats / audit / MRF** are computed from `rinfos` before `drs` (`:548-573`). Untouched. - -## 4. Sufficiency — yes, for the class - -- `getReplicationState` has exactly **one** production caller (`bucket-replication.go:574`). No second write path to patch. -- With the composite forced empty, the guard is dead for all purges under every (`fi.Deleted`, `updateVersion`) combination — not just the fixture's. -- `versionPurgeStatusesMap` is not exposed to the same misparse: `VersionPurgeStatusInternal()` skips empty statuses (`:147-149`), so `arn=;` never appears there. -- After the delta no known producer emits an empty entry at all: purges write `""`; every `!isPurge` exit of `replicateDeleteToTarget` assigns a status (`:640, 664, 681, 695, 715, 725`); the object path pre-initializes `ReplicationStatus: replication.Failed` (`:1338`). Shared parser untouched, as required. -- No existing test asserts a purge writes a non-empty creation status, so no expected regression. `partial-creation-block` restricts fan-out to one ARN → single entry → composite already `""` → unchanged. - -## 5. Non-blocking notes - -- **N1 (strongest — promote from optional to required).** The plan's "small invariant over the source update payload **if useful**" is the only thing that can pin `Targets` and `ReplicaStatus`; the disk assertion at `replication-delete-mrf_test.go:353` pins `ReplicationStatusInternal` alone. Make it mandatory: wrap the `ObjectLayer` passed to `replicateDelete` and assert, for every purge subtest, that `opts.DeleteReplication` has all three fields empty and `CompositeReplicationStatus() == ""`. A `replicaSource + unrecordedPurge, targets: 2` case is optional — it may pass either way because the replica timestamp round-trips byte-identically, which is exactly why the payload invariant is the better instrument. -- **N2.** Tighten §v3's mechanism sentence: `fi.Deleted=false` protects via `DeleteMarkerReplicationStatus()` returning `""` (`erasure-metadata.go:670-675`), not via the guard; record the second masking condition (`erasure-object.go:2102`) confining exposure to delete-marker versions, and the one-shot nature of the corruption. -- **N3.** State `ReplicaStatus = ""` as defensive (§2), not as a fix for an observed failure. -- **N4.** Residual, explicitly out of R6: `replStatusRegex` still misparses any future two-empty-entry string. No producer remains after this delta; record as a follow-up issue, do not fix here. -- **N5.** `drs.ReplicationTimeStamp = UTCNow()` at `:576` is now provably never written for purges. Harmless — leave it rather than widen a three-line delta that has been reviewed field by field. -- **N6.** For the record: the v2 "empty composite" argument was sound only for a single target; the earlier reviews in this lineage, including the one I am extending, overstated it. - -## 6. Consensus - -I state consensus on the exact plan content as read, including §A's three-field instruction (line 23) and the §v3 restatement (line 67), subject to the digest caveat. All v2 dispositions and scope limits carry forward unchanged: no merge/publish/deploy, no live storage rewrite, no parser or `getReplicationState` merge change, nil-client and already-erased-tracking states remain excluded, and the 3-endpoint fixture is not a production three-daemon SR mesh. Proceed with the three-field delta plus the N1 payload invariant. diff --git a/docs/investigations/r6/opus-v3.metadata.json b/docs/investigations/r6/opus-v3.metadata.json deleted file mode 100644 index 6dce8683d..000000000 --- a/docs/investigations/r6/opus-v3.metadata.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "source_commit": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "requested_model": "claude-opus-5", - "requested_effort": "max", - "cli_version": "2.1.270", - "plan": "plan-v3.md", - "plan_sha256": "dc9a67fc91b3113fa35218a2f903455807430cc4daf9c78a88d0be6b8fc27058", - "assistant_models": [ - "claude-opus-5" - ], - "raw_path": "/Users/vonng/tmp/silo-r6-20260915-aa3f/opus-v3.jsonl", - "raw_sha256": "7e63d6d522919206d5833b7d908d028123e3a403e9c34a26a07934af79e870c1", - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --add-dir /Users/vonng/tmp/silo-r6-20260915-aa3f --output-format stream-json --verbose", - "auxiliary_usage_models": [ - "claude-haiku-4-5-20251001", - "claude-opus-5" - ], - "subtype": "success", - "is_error": false, - "session_id": "07e73b10-dab0-4992-bcad-a71ddac270a3", - "duration_ms": 282053, - "num_turns": 40, - "source_diff": "/Users/vonng/tmp/silo-r6-20260915-aa3f/implementation-v2.diff", - "source_diff_sha256": "30f154da13626b5b38b48fbd48755b21a2377ff7b1a5534dc64feda2cc208488" -} diff --git a/docs/investigations/r6/plan-v1.md b/docs/investigations/r6/plan-v1.md deleted file mode 100644 index 75739bb74..000000000 --- a/docs/investigations/r6/plan-v1.md +++ /dev/null @@ -1,54 +0,0 @@ -# R6 plan v1 — marker purge operation and MRF recovery - -Date: 2026-09-15. Implementation has NOT started; this is the review candidate. -Baseline: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a` (clean detached worktree and freshly fetched origin/main agree). -PR #184: OPEN; head `6addf9eb916b5a4b837480cf534cd1efa5407d3c`, code commit `96b21557a85cd4a615ba8a797bfc4c556e413db4`. -Evidence directory: `/Users/vonng/tmp/silo-r6-20260915-aa3f/` (PR JSON/diff, baseline overlays/probes/logs). -Current PGSTY support policy is read from local AGENTS.md, copied verbatim from the main checkout's ignored AGENTS.md. No dependency or supported-stack change. - -## Observed mechanisms and scope - -1. `replicateDeleteToTarget`: an old-shaped task has VersionID empty, DeleteMarkerVersionID set, creation target COMPLETED, purge target PENDING/FAILED. The creation early-return suppresses its DELETE. Offline/RemoveObject error/success select the wrong field for this shape. HEAD-not-ready always overwrites creation status, including purges. The resync defer uses creation COMPLETED even when purge fails; purge COMPLETE early-out only recognizes nonempty VersionID. -2. `replicateDelete`: aggregate audit/event/MRF status and the stats change check still select on VersionID/creation status. Fixing only the target routine leaves a failed old-shaped purge reporting Completed and not entering MRF. -3. `queueMRFHeal`: disk entries are consumed, GetObjectInfo(marker version) returns real metadata plus MethodNotAllowed, and every error is discarded. Direct queueReplicationHeal is NOT an MRF test. -4. Current DELETE producer, scanner/heal, and resync already construct the canonical VersionID purge shape. Existing `TestReplicateDeleteMarkerPurge/recover_legacy_true` proves scanner/heal can recover old state. This is NOT evidence that every failed purge is permanently stuck, nor a defect exclusive to >2 sites. -5. PR #184 has the right state-based classification and 405 recovery direction, but misses the outer status, HEAD failure, resync defer, already-complete guard, bounded delete retry propagation, and real MRF coverage. Its existing-marker test confirms one attempt through a helper HTTP endpoint, not a three-site deployment. Its prose makes stronger permanence/topology claims than the current baseline proves. - -## Proposed minimal implementation - -### A. One operation classification, all exits - -Add a small `DeletedObjectReplicationInfo.isVersionPurge()` helper: true when VersionID is nonempty OR DeleteMarkerVersionID is nonempty and the task's composite VersionPurgeStatus is nonempty. Use the task-level decision in both outer and target functions; do not classify a multi-target task differently merely because one target lacks a map entry. - -For purges preserve `rinfo.ReplicationStatus = rinfo.PrevReplicationStatus` on every exit. Only creation changes this field; only purge changes VersionPurgeStatus. Use this classification for completed early-outs, offline/error/success and resync success. Do not stamp the current resync reset for a failed purge. Existing creation semantics (HEAD 405 means already created, readiness gate, quorum fall-through) remain. - -Route ALL purges through the canonical permanent-delete request already produced by today's handlers: explicit version ID, `ReplicationDeleteMarker=false`. Perform marker HEAD/readiness probes only for creations. Purge authorization/failure is determined by the DELETE itself. This removes the obsolete old-shape HEAD error path, prevents a lost-response retry from re-creating an absent marker, and needs no new wire header or receiver change. Keep current RemoveObject 404/idempotency semantics. Preserve actual target failure in Err, including offline error where appropriate. - -In the outer routine use purge outcomes for audit/event/MRF and for per-target change detection. Map internal purge COMPLETE to operation COMPLETED only when passing an operation status to the existing statistics/event/audit logic; stored purge metadata remains COMPLETE. Feed per-target old/new operation status into stats rather than selecting changes from the unrelated creation status. Keep existing stats policy, no new metrics framework. - -For pre-fan-out exits: config/decision failures remain not-tracked; lock failure retains MRF scheduling; missing configured clients remain logged/skipped and are a documented separate state-preservation limitation. Do not rewrite general `getReplicationState` target merging in this issue. Do not claim global convergence for nil clients or resync narrowed to one of many targets. - -### B. Real MRF healing and bounded retries - -Accept GetObjectInfo MethodNotAllowed only when returned ObjectInfo is a delete marker with nonempty matching bucket/object/version identity. QueueReplicationHeal still rejects zero ModTime. Other errors and invalid/empty ObjectInfo are not queued. Reuse the existing disk persistence/load/queue path; no timer/backoff redesign or format change. - -Carry a RetryCount in the in-memory delete task, pass it from queueReplicationHeal, increment it when a failure/lock error is submitted to MRF, and include it in ToMRFEntry (whose disk format already has RetryCount). Respect the existing mrfRetryLimit and drop accounting; after budget exhaustion the scanner can still start a fresh heal. This closes the retry-count omission exposed by re-enabling marker MRF. No additional persistence schema. - -### C. Regression and acceptance matrix - -1. Target operation table: create (new/HEAD405/completed/readiness failure/quorum), canonical object purge, canonical marker purge, old marker purge. Pending/failed/completed purge, creation pending/completed/replica; success, DELETE403/405/503, offline, absent version, response lost after real removal, resync success/failure. Verify HTTP method/version/header, exact status fields and reset marker. No HEAD for purges. -2. Full outer call: old/new purge shapes, source/target real erasure metadata, first failure yields purge FAILED while creation state survives; MRF queue entry exists; correct operation status accounting. -3. Persist that MRF entry with saveMRFEntries, create a fresh ReplicationPool with no in-memory entry, then queueMRFHeal -> loadMRF -> real GetObjectInfo(405) -> QueueReplicationHeal -> delete queue -> replicateDelete. Initially still failing, entry reappears with increased retry count. Recover target, reload/replay and prove source AND target marker versions removed; repeated successful purge remains absent. Repeat in single-drive and 16-drive fixtures. No direct queueReplicationHeal substitute for MRF proof. -4. Creation MRF: failed marker creation metadata also travels the real disk MRF path and reaches the target. Negative MRF lookups (missing/corrupt/nonmarker 405/empty identity) do not schedule mutations. Verify retry budget still drops with counters, scanner fallback works. -5. Two independent HTTP targets plus source in local erasure fixtures: one target succeeds, the other fails/offline; source remains with per-target COMPLETE/FAILED purge states and unchanged creation states. Restore failed target via persisted MRF; successful target is not resent, both targets and source are absent at completion. This is 3-endpoint fan-out/metadata evidence, not a production three-daemon SR mesh, process-crash/power-loss or cross-region test. Report that distinction. -6. Run focused replication/delete/MRF/resync tests and race tests, then build/vet appropriate to changed code. Do not run other tasks' whole-repository suites concurrently. Tests must fail on baseline for the repaired paths; existing canonical purge and scanner recovery remain covered. - -## Existing state, compatibility, and delivery - -Pending/failed old marker metadata is consumed using the normal heal/MRF machinery; no bulk migration or live object rewrite. Disk entries whose marker lookup was previously dropped can be rediscovered by the scanner. Already missing/erased replication tracking or nil target clients cannot be recovered by this patch alone. A durable receiver tombstone for a delayed *creation* after a purge, replica relay behavior, full arbitrary-mesh convergence, and new backoff/observability are separate issues. - -Reuse PR #184's operation-state separation and valid-405 intent, and adapt its useful target/legacy convergence tests; do not copy its unproven permanence claims or adopt the entire PR blindly. Expected work: 1 production file plus focused tests and investigation records (helper placement may touch bucket-replication-utils.go if clearer). Branch only after exact-version Opus consensus. Normal implementation/tests authorized; merge, publish, deploy, and live storage rewrites excluded. - -## Review request - -Verify the classification and every exit against the exact source; challenge the canonical-wire choice, status conversion, MRF validity/retry budget, test sufficiency and scope. Respond with explicit GO/GO_WITH_NONBLOCKING_NOTES/REVISE for plan-v1.md and list blockers separately. Agreement must be on this exact plan hash. No implementation before resolving blockers. diff --git a/docs/investigations/r6/plan-v2.md b/docs/investigations/r6/plan-v2.md deleted file mode 100644 index cbb73bc5c..000000000 --- a/docs/investigations/r6/plan-v2.md +++ /dev/null @@ -1,59 +0,0 @@ -# R6 plan v2 — marker purge operation and MRF recovery - -Date: 2026-09-15. Implementation has NOT started; this is the review candidate. -Baseline: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a` (clean detached worktree and freshly fetched origin/main agree). -PR #184: OPEN; head `6addf9eb916b5a4b837480cf534cd1efa5407d3c`, code commit `96b21557a85cd4a615ba8a797bfc4c556e413db4`. -Evidence directory: `/Users/vonng/tmp/silo-r6-20260915-aa3f/` (PR JSON/diff, baseline overlays/probes/logs). -Current PGSTY support policy is read from local AGENTS.md, copied verbatim from the main checkout's ignored AGENTS.md. No dependency or supported-stack change. - -## Observed mechanisms and scope - -1. `replicateDeleteToTarget`: an old-shaped task has VersionID empty, DeleteMarkerVersionID set, creation target COMPLETED, purge target PENDING/FAILED. The creation early-return suppresses its DELETE. Offline/RemoveObject error/success select the wrong field for this shape. HEAD-not-ready always overwrites creation status, including purges. The resync defer uses creation COMPLETED even when purge fails; purge COMPLETE early-out only recognizes nonempty VersionID. -2. `replicateDelete`: aggregate audit/event/MRF status and the stats change check still select on VersionID/creation status. Fixing only the target routine leaves a failed old-shaped purge reporting Completed and not entering MRF. -3. `queueMRFHeal`: disk entries are consumed, GetObjectInfo(marker version) returns real metadata plus MethodNotAllowed, and every error is discarded. Direct queueReplicationHeal is NOT an MRF test. -4. Current DELETE producer, scanner/heal, and resync already construct the canonical VersionID purge shape. Existing `TestReplicateDeleteMarkerPurge/recover_legacy_true` proves scanner/heal can recover old state. This is NOT evidence that every failed purge is permanently stuck, nor a defect exclusive to >2 sites. -5. PR #184 has the right state-based classification and 405 recovery direction, but misses the outer status, HEAD failure, resync defer, already-complete guard, bounded delete retry propagation, and real MRF coverage. Its existing-marker test confirms one attempt through a helper HTTP endpoint, not a three-site deployment. Its prose makes stronger permanence/topology claims than the current baseline proves. - -## Proposed minimal implementation - -### A. One operation classification, all exits - -Add a small `DeletedObjectReplicationInfo.isVersionPurge()` helper: true when VersionID is nonempty OR DeleteMarkerVersionID is nonempty and the task's composite VersionPurgeStatus is nonempty. Use the task-level decision in both outer and target functions; do not classify a multi-target task differently merely because one target lacks a map entry. - -For purges leave `rinfo.ReplicationStatus` EMPTY on every exit; `PrevReplicationStatus` still carries the previous value for inspection. This is the existing canonical-purge no-update signal: the disk marker metadata layer does not overwrite creation status or its timestamp when the composite creation status is empty. Pinning previous status here would instead rebuild creation state from the fan-out subset and is explicitly rejected. Only creation sets ReplicationStatus; only purge sets VersionPurgeStatus. Preserve the full on-disk creation/replica status and timestamp block without rewriting it, including excluded targets. Use this classification for completed early-outs, offline/error/success and resync success. Do not stamp the current resync reset for a failed purge. Existing creation semantics (HEAD 405 means already created, readiness gate, quorum fall-through) remain. - -Route ALL purges through the canonical permanent-delete request already produced by today's handlers: explicit version ID, `ReplicationDeleteMarker=false`. Perform marker HEAD/readiness probes only for creations. Purge authorization/failure is determined by the DELETE itself. This removes the obsolete old-shape HEAD error path, prevents a lost-response retry from re-creating an absent marker, and needs no new wire header or receiver change. Keep current RemoveObject 404/idempotency semantics. Preserve actual target failure in Err, including offline error where appropriate. - -In the outer routine use purge outcomes for audit/event/MRF and for per-target change detection. Map internal purge COMPLETE to operation COMPLETED only when passing an operation status to the existing statistics/event/audit logic; stored purge metadata remains COMPLETE. Feed per-target old/new operation status into stats rather than selecting changes from the unrelated creation status. Keep existing stats policy, no new metrics framework. - -For pre-fan-out exits: config/decision failures remain not-tracked; lock failure retains MRF scheduling; missing configured clients remain logged/skipped and are a documented separate state-preservation limitation. Do not rewrite general `getReplicationState` target merging in this issue. Do not claim global convergence for nil clients or resync narrowed to one of many targets. - -### B. Real MRF healing and bounded retries - -Accept GetObjectInfo MethodNotAllowed only when returned ObjectInfo is a delete marker with nonempty matching bucket/object/version identity. QueueReplicationHeal still rejects zero ModTime. Other errors and invalid/empty ObjectInfo are not queued. Reuse the existing disk persistence/load/queue path; no timer/backoff redesign or format change. - -Carry a RetryCount in the in-memory delete task, pass it from queueReplicationHeal, increment it at ALL THREE submission sites: aggregate failure, lock failure, and queueReplicaDeleteTask queue-full fallback, and include it in ToMRFEntry (whose disk format already has RetryCount). Respect the existing mrfRetryLimit and drop accounting; after budget exhaustion the scanner can still start a fresh heal. This closes the retry-count omission exposed by re-enabling marker MRF. No additional persistence schema. - -### C. Regression and acceptance matrix - -1. Target operation table: create (new/HEAD405/completed/readiness failure/quorum), canonical object purge, canonical marker purge, old marker purge. Pending/failed/completed purge, creation pending/completed/replica; success, DELETE403/405/503, offline, absent version, response lost after real removal, resync success/failure. Verify HTTP method/version/header, exact status fields and reset marker. No HEAD for purges. -2. Full outer call: old/new purge shapes, source/target real erasure metadata, first failure yields purge FAILED while the complete on-disk creation/replica status and timestamp survive (empty creation field in the per-target result is intentional); MRF queue entry exists; correct operation status accounting. -3. Persist that MRF entry with saveMRFEntries, create a fresh ReplicationPool with no in-memory entry, then queueMRFHeal -> loadMRF -> real GetObjectInfo(405) -> QueueReplicationHeal -> delete queue -> replicateDelete. Initially still failing, entry reappears with increased retry count. Recover target, reload/replay and prove source AND target marker versions removed; repeated successful purge remains absent. Repeat in single-drive and 16-drive fixtures. No direct queueReplicationHeal substitute for MRF proof. -4. Creation MRF: failed marker creation metadata also travels the real disk MRF path and reaches the target. Negative MRF lookups (missing/corrupt/nonmarker 405/empty identity) do not schedule mutations. Verify retry budget still drops with counters, including worker-queue saturation, and scanner fallback works. -5. Also persist two creation ARNs and a nonzero creation timestamp, restrict fan-out to one ARN, fail/repeat the purge and verify the full creation block/timestamp is unchanged. This checks preservation only; the separate purge-target subset-merging limitation remains excluded. Verify COMPLETE-to-COMPLETED statistics deltas for Heal/ExistingObject operations. -6. Two independent HTTP targets plus source in local erasure fixtures: one target succeeds, the other fails/offline; source remains with per-target COMPLETE/FAILED purge states and unchanged creation states. Restore failed target via persisted MRF; successful target is not resent, both targets and source are absent at completion. This is 3-endpoint fan-out/metadata evidence, not a production three-daemon SR mesh, process-crash/power-loss or cross-region test. Report that distinction. -7. Run focused replication/delete/MRF/resync tests and race tests, then build/vet appropriate to changed code. Do not run other tasks' whole-repository suites concurrently. Tests must fail on baseline for the repaired paths; existing canonical purge and scanner recovery remain covered. - -## Existing state, compatibility, and delivery - -No current producer emits the old in-memory shape, and DeletedObjectReplicationInfo is not serialized across restart. Old-shape handling is robustness/upgrade compatibility; the directly active repair is marker MRF. Pending/failed marker metadata is consumed using the normal heal/MRF machinery; no bulk migration or live object rewrite. Disk entries whose marker lookup was previously dropped can be rediscovered by the scanner. The valid-405 gate covers nonempty version identities; null/empty-version markers that return ObjectNotFound remain outside it. Local source DeleteObject metadata-write errors retain the existing scanner recovery behavior; this issue does not change that independent failure path. These are explicit acceptance limits, not claims of full failure-mode coverage. Already missing/erased replication tracking or nil target clients cannot be recovered by this patch alone. A durable receiver tombstone for a delayed *creation* after a purge, replica relay behavior, full arbitrary-mesh convergence, and new backoff/observability are separate issues. - -Reuse PR #184's operation-state separation and valid-405 intent, and adapt its useful target/legacy convergence tests; do not copy its unproven permanence claims or adopt the entire PR blindly. Expected work: 1 production file plus focused tests and investigation records (helper placement may touch bucket-replication-utils.go if clearer). No getReplicationState merge change is proposed: the empty creation-update signal is the minimal preservation mechanism. If tests expose a nil ResetStatusesMap on a newly successful purge resync, initialize that map before its existing assignment without changing its merge semantics. Branch only after exact-version Opus consensus. Normal implementation/tests authorized; merge, publish, deploy, and live storage rewrites excluded. - -## Review request - -Verify the classification and every exit against the exact source; challenge the canonical-wire choice, status conversion, MRF validity/retry budget, test sufficiency and scope. Respond with explicit GO/GO_WITH_NONBLOCKING_NOTES/REVISE for plan-v2.md and list blockers separately. Agreement must be on this exact plan hash. No implementation before resolving blockers. - -## v2 changes from Opus v1 - -B1 accepted: remove the proposed creation-status pin, require disk-block preservation and partial-fan-out regression. N1 accepted: outgoing versionId/header assertions remain mandatory. N2 accepted: include queue-full RetryCount increment. N3 accepted: assert status-conversion counter deltas. N4/N5 accepted: scope empty/null identities and unsaved old task shape accurately. N6 accepted: persist each failed entry again and synchronize actual queue delivery between rounds. N7 resolved: capacity-adapted real-storage baseline/PR probes completed; see research.md and baseline-mrf.log/pr184-mrf.log. A prior probe incorrectly treated the empty returned creation result as evidence of a disk-state loss; the reviewer corrected that inference. Raw logs are retained. diff --git a/docs/investigations/r6/plan-v3.md b/docs/investigations/r6/plan-v3.md deleted file mode 100644 index 3afcd2216..000000000 --- a/docs/investigations/r6/plan-v3.md +++ /dev/null @@ -1,69 +0,0 @@ -# R6 plan v3 — marker purge operation and MRF recovery - -Date: 2026-09-15. The v2 implementation exists locally after v2 consensus; the v3 incremental correction below has NOT been implemented pending review. -Baseline: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a` (clean detached worktree and freshly fetched origin/main agree). -PR #184: OPEN; head `6addf9eb916b5a4b837480cf534cd1efa5407d3c`, code commit `96b21557a85cd4a615ba8a797bfc4c556e413db4`. -Evidence directory: `/Users/vonng/tmp/silo-r6-20260915-aa3f/` (PR JSON/diff, baseline overlays/probes/logs). -Current PGSTY support policy is read from local AGENTS.md, copied verbatim from the main checkout's ignored AGENTS.md. No dependency or supported-stack change. - -## Observed mechanisms and scope - -1. `replicateDeleteToTarget`: an old-shaped task has VersionID empty, DeleteMarkerVersionID set, creation target COMPLETED, purge target PENDING/FAILED. The creation early-return suppresses its DELETE. Offline/RemoveObject error/success select the wrong field for this shape. HEAD-not-ready always overwrites creation status, including purges. The resync defer uses creation COMPLETED even when purge fails; purge COMPLETE early-out only recognizes nonempty VersionID. -2. `replicateDelete`: aggregate audit/event/MRF status and the stats change check still select on VersionID/creation status. Fixing only the target routine leaves a failed old-shaped purge reporting Completed and not entering MRF. -3. `queueMRFHeal`: disk entries are consumed, GetObjectInfo(marker version) returns real metadata plus MethodNotAllowed, and every error is discarded. Direct queueReplicationHeal is NOT an MRF test. -4. Current DELETE producer, scanner/heal, and resync already construct the canonical VersionID purge shape. Existing `TestReplicateDeleteMarkerPurge/recover_legacy_true` proves scanner/heal can recover old state. This is NOT evidence that every failed purge is permanently stuck, nor a defect exclusive to >2 sites. -5. PR #184 has the right state-based classification and 405 recovery direction, but misses the outer status, HEAD failure, resync defer, already-complete guard, bounded delete retry propagation, and real MRF coverage. Its existing-marker test confirms one attempt through a helper HTTP endpoint, not a three-site deployment. Its prose makes stronger permanence/topology claims than the current baseline proves. - -## Proposed minimal implementation - -### A. One operation classification, all exits - -Add a small `DeletedObjectReplicationInfo.isVersionPurge()` helper: true when VersionID is nonempty OR DeleteMarkerVersionID is nonempty and the task's composite VersionPurgeStatus is nonempty. Use the task-level decision in both outer and target functions; do not classify a multi-target task differently merely because one target lacks a map entry. - -For purges leave `rinfo.ReplicationStatus` EMPTY on every exit; `PrevReplicationStatus` still carries the previous value for inspection. This is the per-target no-update intent. Before the outer source DeleteObject write, explicitly enforce the no-update signal for purge: after getReplicationState, set drs.ReplicationStatusInternal="", drs.Targets=nil, drs.ReplicaStatus="". Leave purge fields, reset map, replica timestamp and other fields unchanged. This is necessary because two serialized empty statuses (arn1=;arn2=;) are misparsed by the existing regexp into a nonempty composite. Do not change that shared parser/serializer or getReplicationState merging in R6. Pinning previous status here would instead rebuild creation state from the fan-out subset and is explicitly rejected. Only creation sets ReplicationStatus; only purge sets VersionPurgeStatus. Preserve the full on-disk creation/replica status and timestamp block without rewriting it, including excluded targets. Use this classification for completed early-outs, offline/error/success and resync success. Do not stamp the current resync reset for a failed purge. Existing creation semantics (HEAD 405 means already created, readiness gate, quorum fall-through) remain. - -Route ALL purges through the canonical permanent-delete request already produced by today's handlers: explicit version ID, `ReplicationDeleteMarker=false`. Perform marker HEAD/readiness probes only for creations. Purge authorization/failure is determined by the DELETE itself. This removes the obsolete old-shape HEAD error path, prevents a lost-response retry from re-creating an absent marker, and needs no new wire header or receiver change. Keep current RemoveObject 404/idempotency semantics. Preserve actual target failure in Err, including offline error where appropriate. - -In the outer routine use purge outcomes for audit/event/MRF and for per-target change detection. Map internal purge COMPLETE to operation COMPLETED only when passing an operation status to the existing statistics/event/audit logic; stored purge metadata remains COMPLETE. Feed per-target old/new operation status into stats rather than selecting changes from the unrelated creation status. Keep existing stats policy, no new metrics framework. - -For pre-fan-out exits: config/decision failures remain not-tracked; lock failure retains MRF scheduling; missing configured clients remain logged/skipped and are a documented separate state-preservation limitation. Do not rewrite general `getReplicationState` target merging in this issue. Do not claim global convergence for nil clients or resync narrowed to one of many targets. - -### B. Real MRF healing and bounded retries - -Accept GetObjectInfo MethodNotAllowed only when returned ObjectInfo is a delete marker with nonempty matching bucket/object/version identity. QueueReplicationHeal still rejects zero ModTime. Other errors and invalid/empty ObjectInfo are not queued. Reuse the existing disk persistence/load/queue path; no timer/backoff redesign or format change. - -Carry a RetryCount in the in-memory delete task, pass it from queueReplicationHeal, increment it at ALL THREE submission sites: aggregate failure, lock failure, and queueReplicaDeleteTask queue-full fallback, and include it in ToMRFEntry (whose disk format already has RetryCount). Respect the existing mrfRetryLimit and drop accounting; after budget exhaustion the scanner can still start a fresh heal. This closes the retry-count omission exposed by re-enabling marker MRF. No additional persistence schema. - -### C. Regression and acceptance matrix - -1. Target operation table: create (new/HEAD405/completed/readiness failure/quorum), canonical object purge, canonical marker purge, old marker purge. Pending/failed/completed purge, creation pending/completed/replica; success, DELETE403/405/503, offline, absent version, response lost after real removal, resync success/failure. Verify HTTP method/version/header, exact status fields and reset marker. No HEAD for purges. -2. Full outer call: old/new purge shapes, source/target real erasure metadata, first failure yields purge FAILED while the complete on-disk creation/replica status and timestamp survive (empty creation field in the per-target result is intentional); MRF queue entry exists; correct operation status accounting. -3. Persist that MRF entry with saveMRFEntries, create a fresh ReplicationPool with no in-memory entry, then queueMRFHeal -> loadMRF -> real GetObjectInfo(405) -> QueueReplicationHeal -> delete queue -> replicateDelete. Initially still failing, entry reappears with increased retry count. Recover target, reload/replay and prove source AND target marker versions removed; repeated successful purge remains absent. Repeat in single-drive and 16-drive fixtures. No direct queueReplicationHeal substitute for MRF proof. -4. Creation MRF: failed marker creation metadata also travels the real disk MRF path and reaches the target. Negative MRF lookups (missing/corrupt/nonmarker 405/empty identity) do not schedule mutations. Verify retry budget still drops with counters, including worker-queue saturation, and scanner fallback works. -5. Also persist two creation ARNs and a nonzero creation timestamp, restrict fan-out to one ARN, fail/repeat the purge and verify the full creation block/timestamp is unchanged. This checks preservation only; the separate purge-target subset-merging limitation remains excluded. Verify COMPLETE-to-COMPLETED statistics deltas for Heal/ExistingObject operations. -6. Two independent HTTP targets plus source in local erasure fixtures: one target succeeds, the other fails/offline; source remains with per-target COMPLETE/FAILED purge states and unchanged creation states. Restore failed target via persisted MRF; successful target is not resent, both targets and source are absent at completion. This is 3-endpoint fan-out/metadata evidence, not a production three-daemon SR mesh, process-crash/power-loss or cross-region test. Report that distinction. -7. Run focused replication/delete/MRF/resync tests and race tests, then build/vet appropriate to changed code. Do not run other tasks' whole-repository suites concurrently. Tests must fail on baseline for the repaired paths; existing canonical purge and scanner recovery remain covered. - -## Existing state, compatibility, and delivery - -No current producer emits the old in-memory shape, and DeletedObjectReplicationInfo is not serialized across restart. Old-shape handling is robustness/upgrade compatibility; the directly active repair is marker MRF. Pending/failed marker metadata is consumed using the normal heal/MRF machinery; no bulk migration or live object rewrite. Disk entries whose marker lookup was previously dropped can be rediscovered by the scanner. The valid-405 gate covers nonempty version identities; null/empty-version markers that return ObjectNotFound remain outside it. Local source DeleteObject metadata-write errors retain the existing scanner recovery behavior; this issue does not change that independent failure path. These are explicit acceptance limits, not claims of full failure-mode coverage. Already missing/erased replication tracking or nil target clients cannot be recovered by this patch alone. A durable receiver tombstone for a delayed *creation* after a purge, replica relay behavior, full arbitrary-mesh convergence, and new backoff/observability are separate issues. - -Reuse PR #184's operation-state separation and valid-405 intent, and adapt its useful target/legacy convergence tests; do not copy its unproven permanence claims or adopt the entire PR blindly. Expected work: 1 production file plus focused tests and investigation records (helper placement may touch bucket-replication-utils.go if clearer). No getReplicationState merge change is proposed: explicitly empty the creation/replica update fields only at the outer purge write; this enforces the intended no-update signal even for multiple empty target statuses. If tests expose a nil ResetStatusesMap on a newly successful purge resync, initialize that map before its existing assignment without changing its merge semantics. Branch only after exact-version Opus consensus. Normal implementation/tests authorized; merge, publish, deploy, and live storage rewrites excluded. - -## Review request - -Verify the classification and every exit against the exact source; challenge the canonical-wire choice, status conversion, MRF validity/retry budget, test sufficiency and scope. Respond with explicit GO/GO_WITH_NONBLOCKING_NOTES/REVISE for plan-v3.md and list blockers separately. Agreement must be on this exact plan hash. No implementation before resolving blockers. - -## v2 changes from Opus v1 - -B1 accepted: remove the proposed creation-status pin, require disk-block preservation and partial-fan-out regression. N1 accepted: outgoing versionId/header assertions remain mandatory. N2 accepted: include queue-full RetryCount increment. N3 accepted: assert status-conversion counter deltas. N4/N5 accepted: scope empty/null identities and unsaved old task shape accurately. N6 accepted: persist each failed entry again and synchronize actual queue delivery between rounds. N7 resolved: capacity-adapted real-storage baseline/PR probes completed; see research.md and baseline-mrf.log/pr184-mrf.log. A prior probe incorrectly treated the empty returned creation result as evidence of a disk-state loss; the reviewer corrected that inference. Raw logs are retained. - -## v3 incremental finding and correction - -The v2 proof that empty rinfo.ReplicationStatus necessarily leads to an empty composite was incomplete for multiple targets: regexp `([^=].*?)=([^,].*?);` matches `arn1=;arn2=;` with status `;arn2=`. The usual source purge already has VersionPurgeStatus on disk, which makes erasureObjects.DeleteObject set FileInfo.Deleted=false; that independently protects the disk creation block and explains why the ordinary v2 multi-target tests passed. A task carrying purge state while the disk marker still has only creation metadata can instead set FileInfo.Deleted=true; a failed two-target purge then rewrites the full creation block with empty entries and a new timestamp. - -Real erasure proof: `TestReplicationMRFMarkerRecovery/unrecorded-purge` in cmd/replication-delete-mrf_test.go; raw `v2-unrecorded-purge.log` in the evidence directory. Test fixture creates a normal signed purge task, then locally restores only its test source marker's creation metadata while retaining that task's purge state. With one target succeeding and another failing, v2 rewrites creation fields (test fails). This is a boundary/robustness scenario, not a claim that today's normal producer necessarily races its persisted purge state. It also reveals an inherited weakness; no unrelated parser fix is proposed. - -Minimal correction: the outer `replicateDelete`, immediately after `drs := getReplicationState(...)`, executes the three field assignments listed in §A inside `if isPurge`. `ReplicaStatus` must be empty too because CompositeReplicationStatus otherwise falls back to it. This is a no-update payload, not a request to delete disk creation/replica fields; the xlMetaV2 guard does not run for empty composite. Purge maps and reset timestamps retain their current behavior. Keep per-target status empty as agreed in v2. Add the real unrecorded-purge scenario, plus a small invariant over the source update payload if useful, and rerun ordinary multi-target/partial/replica preservation, MRF, race and lint. - -Production source under review is baseline 9ebe81c1 plus `/Users/vonng/tmp/silo-r6-20260915-aa3f/implementation-v2.diff` (v2 has been implemented after its recorded consensus). Only this additional correction awaits v3 agreement. All other v2 dispositions and scope limits remain. The earlier consensus/raw reviews remain immutable evidence, including the proof now corrected by the executable counterexample. diff --git a/docs/investigations/r6/research.md b/docs/investigations/r6/research.md deleted file mode 100644 index af6e17797..000000000 --- a/docs/investigations/r6/research.md +++ /dev/null @@ -1,52 +0,0 @@ -# R6 research and PR #184 review - -## Source identity - -- Worktree baseline / freshly fetched origin/main: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`. -- PR URL: https://github.com/pgsty/silo/pull/184 -- PR head at this review: `6addf9eb916b5a4b837480cf534cd1efa5407d3c`; OPEN, no reviews returned by GitHub API. Its recorded PR base OID `89637554d60c27cfc51d2281d0a4fe15e415f06d` is older than the fetched branch; current worktree source wins. -- Production diff is exactly 24 insertions/11 removals in bucket-replication.go relative to this baseline. The PR additionally changes tests and adds a convergence write-up. -- No production edits were made during research. All test overlays and raw logs: `/Users/vonng/tmp/silo-r6-20260915-aa3f/`. - -## Reproduced target-path findings - -`review_probe_test.go` is a temporary in-package probe using a real minio-go HTTP client and counted HEAD/DELETE requests. `base-overlay.json` adds the probe; `pr-overlay.json` replaces only bucket-replication.go with the exact PR version for comparison. - -| Probe | Baseline | PR #184 | Required behavior | -|---|---|---|---| -| old shape, creation COMPLETED, purge PENDING, successful target | DELETE 0; creation COMPLETED; purge PENDING | DELETE 1; creation COMPLETED; purge COMPLETE | send purge and finish purge state | -| old shape, creation PENDING, HEAD 403 | creation FAILED; purge PENDING | same | purge must not overwrite creation | -| old shape, ExistingObjectReplicationType, DELETE 403 | purge still PENDING | purge FAILED, but current reset timestamp recorded | failed operation must not mark successful resync | -| old shape, purge already COMPLETE, creation PENDING | two HTTP calls, creation FAILED | two HTTP calls, purge becomes FAILED | skip completed purge, preserve states | - -Raw logs: `baseline.log`, `pr184-probes.log`. The first baseline run also attempted the existing storage tests, which failed at seed PutObject due to the host's free-space percentage threshold. This is a fixture/environment failure, not replication evidence. Follow-up overlays use the existing `tagTestCapacityDisk` adapter: only DiskInfo total/used capacity is adapted; actual storage writes remain on test disks. Host df showed about 10 GiB available but 100% used by filesystem percentage rounding. - -## Additional code-review findings - -- Outer `replicateDelete` chooses operation outcome using VersionID, so PR's preserved creation COMPLETED can conceal old-shape purge FAILED from ObjectReplicationFailed and queueMRFSave. Per-target stats comparisons also use creation fields. -- `VersionPurgeComplete` is `COMPLETE`, while ordinary replication completion is `COMPLETED`. A raw string cast does not take the completion branch of ReplicationStats.Update. -- The old marker-shape request sends ReplicationDeleteMarker=true for purges. Today's canonical purge tasks use false and explicit VersionID. Reuse that existing wire meaning to avoid creating a marker at the absent-version fallback after an ambiguous successful DELETE. -- MRF persists entries on actual local drives, removes the loaded file, then asynchronously reads each object. MethodNotAllowed accompanies real marker ObjectInfo in erasureObjects.getObjectInfo. Tests must cover save/load/fresh pool and subsequent queueing, not call queueReplicationHeal directly. -- DeletedObjectReplicationInfo currently lacks RetryCount; its ToMRFEntry always serializes zero. Restoring marker MRF makes the existing budget omission reachable on the fast retry path. - -## Claims excluded from acceptance - -The current DELETE, scanner/heal, and resync producers already build canonical purges. The inherited old-shape test intentionally repairs a legacy PENDING marker with scanner/heal. It therefore contradicts an unqualified claim that all failed purges persist forever or only manual resync repairs them. R6 does not establish the frequency of failures for any site count, nor prove the cited production 405 traffic is entirely caused by these paths. - -PR documentation describes missing-client status loss, replica relaying, tombstones and arbitrary-mesh recovery. They are separate mechanisms. This patch's acceptance concerns tracked source fan-out with configured reachable/recoverable targets; it does not establish global multi-site convergence under lost state, nil clients, delayed creation after purge, or process/storage failure at every possible point. - -## Real erasure and disk-MRF probes - -`review_integration_test.go` reuses the existing signed DELETE/storage fixture, injects a target 403, preserves the intended creation state explicitly, and persists the actual failed entry. A fresh ReplicationPool then loads the disk record and attempts queueMRFHeal. It validates source and target absence after recovery. - -- Baseline canonical failure: creation result PENDING (lost prior COMPLETED), purge FAILED, one MRF entry. Real source lookup yields matching marker ObjectInfo and MethodNotAllowed. Disk-MRF replay schedules no task (probe fails as expected). -- Baseline old-shaped failure: creation COMPLETED, purge still PENDING, zero MRF entries (probe fails as expected). -- PR canonical failure: creation result is still empty (disk preservation clarified below). Disk-MRF replay now runs and source/target removal succeeds on both single-drive and 16-drive fixtures. -- PR old-shaped failure: creation COMPLETED and purge FAILED, but zero MRF entries. This directly confirms the outer-function omission. -- The existing baseline old-shape recovery test PASSES using its explicit scanner/heal fallback, on both storage fixtures. Therefore scanner recovery is retained as observed evidence, not only a code inference. - -Raw logs: `baseline-mrf.log`, `pr184-mrf.log`. Temporary probe development first hit an unused import and then an uninitialized statistics fixture; those were corrected without changing production code. They do not count as defect evidence. Final logs above contain actual assertion outcomes. - -## Correction after independent Opus review - -Opus v1 B1 disproved the interpretation that a canonical purge's empty creation result means the disk creation state was lost. It is an intentional no-update signal in xlMetaV2.DeleteVersion: the disk block is retained if the composite creation state is empty. `baseline-mrf.log` and `pr184-mrf.log` include a temporary assertion that was too strong; their empty per-target result is not a disk-loss finding. v2 keeps that existing no-update signal, tests the actual persisted block/timestamp, and avoids introducing the partial-fan-out overwrite that a creation-status pin would cause. See decisions-v2.md for all dispositions. diff --git a/docs/investigations/r6/review_integration_test.go.txt b/docs/investigations/r6/review_integration_test.go.txt deleted file mode 100644 index 53ef5d8c3..000000000 --- a/docs/investigations/r6/review_integration_test.go.txt +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) 2026 PGSTY -// SPDX-License-Identifier: AGPL-3.0-only - -package cmd - -import ( - "bytes" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "sync/atomic" - "testing" - "time" - - "github.com/minio/madmin-go/v3" - "github.com/minio/minio-go/v7" - "github.com/minio/minio/internal/auth" - "github.com/minio/minio/internal/bucket/replication" - xhttp "github.com/minio/minio/internal/http" - "github.com/minio/minio/internal/once" -) - -// Exercise the actual single-object DELETE handler and erasure metadata. A -// marker purge must delete the target version and finish the source purge; -// merely seeing a 405 on HEAD is not evidence of a completed permanent delete. -func TestReviewR6FailureMRF(t *testing.T) { - defer DetectTestLeak(t)() - for _, legacy := range []bool{false, true} { - t.Run(fmt.Sprintf("recover_legacy_%v", legacy), func(t *testing.T) { - ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{ - t: t, endpoints: []string{"DeleteObject"}, - objAPITest: func(obj ObjectLayer, instanceType, bucket string, router http.Handler, creds auth.Credentials, t *testing.T) { - testReviewR6FailureMRF(obj, instanceType, bucket, router, creds, t, legacy) - }, - }) - }) - } -} - -func testReviewR6FailureMRF(obj ObjectLayer, instanceType, bucket string, router http.Handler, creds auth.Credentials, t *testing.T, legacy bool) { - ctx := t.Context() - oldStats := globalReplicationStats.Swap(NewReplicationStats(ctx, nil)) - defer globalReplicationStats.Store(oldStats) - if pools, ok := obj.(*erasureServerPools); ok { - for _, pool := range pools.serverPools { for _, set := range pool.sets { - disks := set.getDisks() - wrapped := make([]StorageAPI, len(disks)) - for i,d := range disks { if d!=nil {wrapped[i]=tagTestCapacityDisk{StorageAPI:d}} } - set.getDisks = func() []StorageAPI {return wrapped} - }} - } - - const arn = "arn:minio:replication::af470089-d354-4473-934c-9e1f52f6da89:bucket" - const name = "marker" - version := mustGetUUID() - remoteBucket := getRandomBucketName() - if err := obj.MakeBucket(ctx, remoteBucket, MakeBucketOptions{}); err != nil { - t.Fatal(err) - } - if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketVersioningConfig, enabledBucketVersioningConfig); err != nil { - t.Fatal(err) - } - for _, b := range []string{bucket, remoteBucket} { - if _, err := obj.PutObject(ctx, b, name, mustGetPutObjReader(t, bytes.NewReader([]byte("data")), 4, "", ""), ObjectOptions{Versioned: true}); err != nil { - t.Fatal(err) - } - opts := ObjectOptions{VersionID: version, Versioned: true, DeleteMarker: true, ReplicationRequest: true, MTime: UTCNow()} - opts.SetReplicaStatus(replication.Replica) - if _, err := obj.DeleteObject(ctx, b, name, opts); err != nil { - t.Fatalf("%s: seed marker in %s: %v", instanceType, b, err) - } - } - var reject atomic.Bool - reject.Store(true) - remote := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - opts := ObjectOptions{VersionID: r.URL.Query().Get("versionId"), Versioned: true} - switch r.Method { - case http.MethodHead: - oi, err := obj.GetObjectInfo(r.Context(), remoteBucket, name, opts) - if oi.DeleteMarker { - w.Header().Set(xhttp.AmzDeleteMarker, "true") - w.Header().Set(xhttp.AmzVersionID, oi.VersionID) - } - if err != nil { - writeErrorResponseHeadersOnly(w, toAPIError(r.Context(), err)) - return - } - w.WriteHeader(http.StatusOK) - case http.MethodDelete: - if reject.Load() {w.WriteHeader(403); fmt.Fprint(w, `AccessDenied`);return} - opts.DeleteMarker = r.Header.Get(xhttp.MinIOSourceDeleteMarker) == "true" - opts.SetReplicaStatus(replication.Replica) - _, err := obj.DeleteObject(r.Context(), remoteBucket, name, opts) - if err != nil && !isErrVersionNotFound(err) && !isErrObjectNotFound(err) { - writeErrorResponse(r.Context(), w, toAPIError(r.Context(), err), r.URL) - return - } - w.WriteHeader(http.StatusNoContent) - default: - t.Errorf("unexpected remote method %s", r.Method) - w.WriteHeader(http.StatusBadRequest) - } - })) - defer remote.Close() - client, err := minio.New(strings.TrimPrefix(remote.URL, "http://"), &minio.Options{Region: "us-east-1"}) - if err != nil { - t.Fatal(err) - } - target := &TargetClient{Client: client, ARN: arn, Bucket: remoteBucket} - globalBucketTargetSys.Lock() - globalBucketTargetSys.arnRemotesMap[arn] = arnTarget{Client: target, lastRefresh: UTCNow()} - globalBucketTargetSys.targetsMap[bucket] = []madmin.BucketTarget{{Arn: arn, TargetBucket: remoteBucket}} - globalBucketTargetSys.Unlock() - globalBucketTargetSys.hMutex.Lock() - globalBucketTargetSys.hc[client.EndpointURL().Host] = epHealth{Online: true} - globalBucketTargetSys.hMutex.Unlock() - meta, err := globalBucketMetadataSys.Get(bucket) - if err != nil { - t.Fatal(err) - } - cfg := configs[0] - cfg.RoleArn = arn - meta.replicationConfig = &cfg - globalBucketMetadataSys.Set(bucket, meta) - worker := make(chan ReplicationWorkerOperation, 1) - p := &ReplicationPool{ - ctx: ctx, - objLayer: obj, - workers: []chan ReplicationWorkerOperation{worker}, - stats: globalReplicationStats.Load(), - mrfSaveCh: make(chan MRFReplicateEntry, 1), - } - oldPool := globalReplicationPool - globalReplicationPool = once.NewSingleton[ReplicationPool]() - globalReplicationPool.Set(p) - defer func() { globalReplicationPool = oldPool }() - - req, err := newTestSignedRequestV4(http.MethodDelete, "/"+bucket+"/"+name+"?versionId="+version, 0, nil, creds.AccessKey, creds.SecretKey, nil) - if err != nil { - t.Fatal(err) - } - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - if w.Code != http.StatusNoContent { - t.Fatalf("DELETE status %d: %s", w.Code, w.Body.String()) - } - var deletion DeletedObjectReplicationInfo - select { - case op := <-worker: - deletion = op.(DeletedObjectReplicationInfo) - case <-time.After(time.Second): - t.Fatal("DELETE did not schedule replication") - } - if deletion.VersionID != version || deletion.DeleteMarkerVersionID != "" { - t.Errorf("purge scheduled as marker creation: version=%q marker=%q", deletion.VersionID, deletion.DeleteMarkerVersionID) - } - deletion.ReplicationState.Targets = map[string]replication.StatusType{arn: replication.Completed} - deletion.ReplicationState.ReplicationStatusInternal = arn+"=COMPLETED;" - if legacy {deletion.VersionID,deletion.DeleteMarkerVersionID="",version} - result:=replicateDelete(ctx,deletion,obj) - t.Logf("legacy=%v result creation=%s purge=%s MRF=%d",legacy,result.ReplicationStatus(),result.VersionPurgeStatus(),len(p.mrfSaveCh)) - if result.VersionPurgeStatus()!=replication.VersionPurgeFailed || result.ReplicationStatus()!=replication.Completed {t.Error("failure stored in incorrect status field")} - var entry MRFReplicateEntry - select {case entry= <-p.mrfSaveCh:default:t.Fatal("failed purge did not enter MRF")} - oi,lookupErr:=obj.GetObjectInfo(ctx,bucket,name,ObjectOptions{VersionID:version}) - t.Logf("real source lookup: name=%s version=%s marker=%v purge=%s err=%v",oi.Name,oi.VersionID,oi.DeleteMarker,oi.VersionPurgeStatus,lookupErr) - if !isErrMethodNotAllowed(lookupErr)||!oi.DeleteMarker {t.Fatal("expected real marker plus 405")} - p.saveMRFEntries(ctx,map[string]MRFReplicateEntry{entry.versionID:entry}) - fresh:= &ReplicationPool{ctx:ctx,objLayer:obj,workers:[]chan ReplicationWorkerOperation{worker},stats:globalReplicationStats.Load(),mrfSaveCh:make(chan MRFReplicateEntry,1)} - globalReplicationPool=once.NewSingleton[ReplicationPool]();globalReplicationPool.Set(fresh) - reject.Store(false) - if err:=fresh.queueMRFHeal();err!=nil{t.Fatal(err)} - select {case op:= <-worker:deletion=op.(DeletedObjectReplicationInfo);case <-time.After(time.Second):t.Fatal("persisted MRF marker lookup was skipped")} - result=replicateDelete(ctx,deletion,obj) - if result.VersionPurgeStatus()!=replication.VersionPurgeComplete {t.Fatalf("MRF retry result: %+v",result)} - for _,b:=range []string{bucket,remoteBucket}{ - oi,err:=obj.GetObjectInfo(ctx,b,name,ObjectOptions{VersionID:version}) - if !isErrVersionNotFound(err)&&!isErrObjectNotFound(err){t.Errorf("marker remains in %s: %+v %v",b,oi,err)} - } -} diff --git a/docs/investigations/r6/review_probe_test.go.txt b/docs/investigations/r6/review_probe_test.go.txt deleted file mode 100644 index 4d678e100..000000000 --- a/docs/investigations/r6/review_probe_test.go.txt +++ /dev/null @@ -1,68 +0,0 @@ -package cmd -import ( - "fmt" - "net/http" - "net/http/httptest" - "strings" - "sync/atomic" - "testing" - "github.com/minio/minio-go/v7" - "github.com/minio/minio/internal/bucket/replication" - xhttp "github.com/minio/minio/internal/http" -) -func TestReviewR6LegacyPurgeRetries(t *testing.T) { - var deletes atomic.Int32 - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodHead { - w.Header().Set(xhttp.AmzDeleteMarker, "true") - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - deletes.Add(1) - w.WriteHeader(http.StatusNoContent) - })) - defer server.Close() - client, err := minio.New(strings.TrimPrefix(server.URL, "http://"), &minio.Options{Region: "us-east-1", MaxRetries: 1}) - if err != nil { - t.Fatal(err) - } - old := globalBucketTargetSys - globalBucketTargetSys = &BucketTargetSys{hc: map[string]epHealth{client.EndpointURL().Host: {Online: true}}} - defer func() { globalBucketTargetSys = old }() - d := DeletedObjectReplicationInfo{Bucket: "source", DeletedObject: DeletedObject{ObjectName: "marker", DeleteMarker: true, DeleteMarkerVersionID: mustGetUUID()}} - d.ReplicationState.Targets = map[string]replication.StatusType{"arn1": replication.Completed} - d.ReplicationState.PurgeTargets = map[string]VersionPurgeStatusType{"arn1": replication.VersionPurgePending} - result := replicateDeleteToTarget(t.Context(), d, &TargetClient{Client: client, ARN: "arn1", Bucket: "target"}) - t.Logf("remote DELETE calls=%d, creation=%s, purge=%s", deletes.Load(), result.ReplicationStatus, result.VersionPurgeStatus) - if deletes.Load() != 1 || result.VersionPurgeStatus != replication.VersionPurgeComplete { - t.Error("pending legacy purge was not delivered") - } -} - - -func TestReviewR6PurgeExits(t *testing.T) { - for _, tc := range []struct{name string; creation replication.StatusType; purge VersionPurgeStatusType; head int; existing bool; want VersionPurgeStatusType; wantCalls int; wantReset bool}{ - {"head_forbidden", replication.Pending, replication.VersionPurgePending, 403, false, replication.VersionPurgeFailed, 1, false}, - {"failed_resync", replication.Completed, replication.VersionPurgePending, 405, true, replication.VersionPurgeFailed, 2, false}, - {"already_purged", replication.Pending, replication.VersionPurgeComplete, 405, false, replication.VersionPurgeComplete, 0, false}, - } { - t.Run(tc.name, func(t *testing.T){ - var calls atomic.Int32 - server:=httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter,r *http.Request){ - calls.Add(1) - if r.Method==http.MethodHead {w.Header().Set(xhttp.AmzDeleteMarker,"true"); w.WriteHeader(tc.head);return} - w.WriteHeader(403);fmt.Fprint(w,`AccessDenied`) - })) - defer server.Close() - client,err:=minio.New(strings.TrimPrefix(server.URL,"http://"),&minio.Options{Region:"us-east-1",MaxRetries:1});if err!=nil{t.Fatal(err)} - old:=globalBucketTargetSys;globalBucketTargetSys=&BucketTargetSys{hc:map[string]epHealth{client.EndpointURL().Host:{Online:true}}};defer func(){globalBucketTargetSys=old}() - d:=DeletedObjectReplicationInfo{Bucket:"source",DeletedObject:DeletedObject{ObjectName:"marker",DeleteMarker:true,DeleteMarkerVersionID:mustGetUUID()}} - d.ReplicationState.Targets=map[string]replication.StatusType{"arn1":tc.creation} - d.ReplicationState.PurgeTargets=map[string]VersionPurgeStatusType{"arn1":tc.purge} - if tc.existing {d.OpType=replication.ExistingObjectReplicationType} - got:=replicateDeleteToTarget(t.Context(),d,&TargetClient{Client:client,ARN:"arn1",Bucket:"target",ResetID:"new-reset"}) - t.Logf("calls=%d, creation=%s, purge=%s, reset=%q",calls.Load(),got.ReplicationStatus,got.VersionPurgeStatus,got.ResyncTimestamp) - if got.ReplicationStatus!=tc.creation || got.VersionPurgeStatus!=tc.want || int(calls.Load())!=tc.wantCalls || (got.ResyncTimestamp!="")!=tc.wantReset {t.Errorf("operation classified inconsistently: %+v",got)} - }) - } -} diff --git a/docs/investigations/r6/verification-notes.md b/docs/investigations/r6/verification-notes.md deleted file mode 100644 index 530b01d0f..000000000 --- a/docs/investigations/r6/verification-notes.md +++ /dev/null @@ -1,39 +0,0 @@ -# R6 verification notes and evidence boundaries - -## Reproduction evidence before acceptance - -Raw evidence directory: `/Users/vonng/tmp/silo-r6-20260915-aa3f/`. - -| Log | What it establishes | -|---|---| -| baseline.log | Old task shape skips DELETE; wrong HEAD/outcome fields and already-complete behavior. Initial storage fixture attempts hit the host capacity threshold. | -| baseline-mrf.log | After adapting fixture capacity, existing scanner/heal recovery passes. Actual disk-MRF replay drops a real marker+405. Old-shaped task queues no MRF. The canonical per-target creation-result assertion was later retracted as a disk-loss inference; see decisions-v2.md. | -| pr184-probes.log | Exact PR #184 fixes old successful delivery but leaves HEAD failure, failed resync stamp and already-complete retry errors. | -| pr184-mrf.log | PR canonical disk-MRF recovery works; failed old-shaped purge still queues no MRF. | -| v2-unrecorded-purge.log | V2 can rewrite creation metadata with two empty target statuses when disk purge state is absent. Real source/target storage, no simulated metadata layer. This led to v3 review and the explicit empty update payload. | - -The temporary baseline probes were developed with two fixture corrections (unused import, then missing ReplicationStats initialization). Those development failures are not treated as product evidence. Likewise the initial in-memory `creation=PENDING` observation does not establish a disk-state loss; Opus v1 corrected that inference. Opus v3 then corrected the earlier multi-target regex proof after the real storage counterexample. - -## What the local storage tests exercise - -The suite uses the existing single-drive and 16-drive erasure fixtures, the real signed source DELETE handler, real source/target marker metadata and a real minio-go client over HTTP. Target HTTP adapters call the real ObjectLayer, and can reject a request, be marked offline, or close the connection **after** removing the marker. They are controlled replication target adapters, not three independently booted SILO site-replication daemons. The target adapters do not exercise receiver authentication or the complete target HTTP router; those were not changed by this patch. - -`saveMRFEntries` writes real MRF files to registered fixture drives. Tests read and check the encoded record, re-persist it because loadMRF consumes the file, construct a new empty ReplicationPool, and call queueMRFHeal. The real GetObjectInfo(VersionID) returns marker metadata with 405 and the queued task goes through the actual replication worker channel. The test explicitly receives that task and invokes production replicateDelete to control each failure/recovery round; it does not start the long-running background worker loop. Tests drive persistence directly, rather than waiting for the five-minute timer. They prove pool replacement/disk reload, not process crash or power-loss durability. - -Negative MRF lookups wait for the actual lookup and assert no task arrives within a bounded observation window. Missing, read-error, nonmarker, wrong bucket/object/version, empty info and zero timestamp responses are covered. Null/empty marker version identities that return ObjectNotFound are explicitly outside the new valid-405 gate. - -The multi-target fixture checks one target complete while the other fails/offline, persisted per-target purge states, no extra delivery to the successful target, then recovery and marker removal on source and both targets. The separate partial-fan-out test preserves the complete creation block and its timestamp. It does not claim to fix the pre-existing purge-status subset replacement. - -## Environment failures and fixes to test inputs - -- During the exploratory broad run, the host reported a high used-space percentage. Six unrelated DELETE tests aborted at seed PutObject with `Storage reached its minimum free drive threshold`: TestDeleteObjectConditional, TestDeleteObjectConditionalWithReadQuorumFailure, TestDeleteObjectConditionalVersioned, TestDeleteObjectsVersioned, TestDeleteObject, TestDeleteObjectVersionMarker. See replication-suite.log. That exploratory broad suite was not a pass. After host free space recovered, all six exact tests passed on the final integrated source, without changing those tests or production capacity policy; see verification/rebased-delete-verification.json and rebased-delete-recheck.log. This recheck is not a full cmd-suite run. -- R6 storage fixtures reuse the repository's tagTestCapacityDisk adapter: Total/Used are adapted to the actual free space; object/MRF metadata and data still go to real disks. This isolates host occupancy from replication semantics. -- A test link later failed with `no space left on device` (head-exits.log). Thirteen old Go cache artifacts containing this exact worktree path, totaling 1495 MiB, were removed after identification. No repository data or other tasks' cache entries were selected. The manifest is owned-cache-cleanup.json in the raw evidence directory. Available space also changed due to unrelated host activity; we do not attribute the whole increase to this cleanup. -- The HEAD quorum test initially used HTTP 503 and expected a quorum-code fall-through. Existing ErrorRespToObjectError classifies 503 as backend-down before its S3 code conversion. Final tests separately cover 503 not-ready failure and a non-503 SlowDownRead code reaching the existing quorum branch. No production change to that classification was made. -- Initial lint reported five test-style issues, subsequently corrected. v2-scope-regression.log is an intermediate failed run, including the still-unfixed v3 counterexample and the initial quorum test expectation. It is not final acceptance. - -## Limits retained after R6 - -No three-daemon/full-mesh SR deployment, process restart/crash, cross-region test, production repair, merge or release is claimed. The following pre-existing mechanisms remain separate: absent configured clients and omitted purge target state, target-scoped resync replacing the purge subset, loss of replication tracking, replica relay behavior, delayed marker **creation** after a purge without tombstones, local source metadata-write failures relying on scanner recovery, generic status parser robustness, and new MRF timer/backoff/observability design. - -Current normal producers already emit canonical version purges. The old in-memory task shape is not serialized through restart. Its support is robustness compatibility; marker MRF is the directly active retry-path repair. Do not reuse PR #184's unqualified permanence, request-rate or site-count claims as acceptance conclusions. diff --git a/docs/investigations/r6/verification/baseline-build.log b/docs/investigations/r6/verification/baseline-build.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r6/verification/baseline-lint.log b/docs/investigations/r6/verification/baseline-lint.log deleted file mode 100644 index 6a3ebaa7e..000000000 --- a/docs/investigations/r6/verification/baseline-lint.log +++ /dev/null @@ -1 +0,0 @@ -0 issues. diff --git a/docs/investigations/r6/verification/baseline-race.log b/docs/investigations/r6/verification/baseline-race.log deleted file mode 100644 index 320c759d0..000000000 --- a/docs/investigations/r6/verification/baseline-race.log +++ /dev/null @@ -1,288 +0,0 @@ -=== RUN TestReplicateDeleteMarkerPurge -=== RUN TestReplicateDeleteMarkerPurge/recover_legacy_false -=== RUN TestReplicateDeleteMarkerPurge/recover_legacy_true ---- PASS: TestReplicateDeleteMarkerPurge (0.84s) - --- PASS: TestReplicateDeleteMarkerPurge/recover_legacy_false (0.39s) - --- PASS: TestReplicateDeleteMarkerPurge/recover_legacy_true (0.44s) -=== RUN TestReplicateDeleteMarkerTargetSemantics -=== RUN TestReplicateDeleteMarkerTargetSemantics/existing_marker_is_idempotent -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_removes_an_existing_marker -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_forbidden -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_method_rejected -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_unavailable ---- PASS: TestReplicateDeleteMarkerTargetSemantics (0.01s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/existing_marker_is_idempotent (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_removes_an_existing_marker (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_forbidden (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_method_rejected (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_unavailable (0.01s) -=== RUN TestReplicationMRFMarkerRecovery -=== RUN TestReplicationMRFMarkerRecovery/canonical - replication-delete-mrf_test.go:485: ErasureSD: canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -goroutine 5773 [running]: -runtime/debug.Stack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:26 +0x6c -runtime/debug.PrintStack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:18 +0x24 -github.com/minio/minio/internal/ioutil.SafeClose[...](...) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/ioutil/ioutil.go:470 -github.com/minio/minio/internal/logger/target/http.(*Target).Cancel(0xc002fd4680) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/target/http/http.go:630 +0x98 -github.com/minio/minio/cmd.replicationTestAudit.func2() - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:593 +0xa4 -github.com/minio/minio/cmd.testReplicationMRFMarkerRecovery(0xc000f31d48, {0x1099ca8b0, _}, {_, _}, {_, _}, {_, _}, {{0x105dfce44, ...}, ...}, ...) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:486 +0x5cb8 -github.com/minio/minio/cmd.TestReplicationMRFMarkerRecovery.func1.1({0x1099ca8b0, 0xc002432410}, {0x105df9251, 0x9}, {0xc0024cc180, 0x3c}, {0x109974420, _}, {{0x105dfce44, 0xa}, ...}, ...) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:53 +0x124 -github.com/minio/minio/cmd.ExecObjectLayerAPITest({0xc000f31d48, 0xc002f98030, {0xc002e26720, 0x1, 0x1}, 0x0, {0x0, 0x0, 0x0, {0x0, ...}, ...}}) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/test-utils_test.go:1789 +0x32c -github.com/minio/minio/cmd.TestReplicationMRFMarkerRecovery.func1(0xc000f31d48) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:52 +0x15c -testing.tRunner(0xc000f31d48, 0xc002777e00) - /opt/homebrew/Cellar/go/1.27.1/libexec/src/testing/testing.go:2193 +0x168 -created by testing.(*T).Run in goroutine 5772 - /opt/homebrew/Cellar/go/1.27.1/libexec/src/testing/testing.go:2258 +0x7c0 - replication-delete-mrf_test.go:485: Erasure: canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -goroutine 5773 [running]: -runtime/debug.Stack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:26 +0x6c -runtime/debug.PrintStack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:18 +0x24 -github.com/minio/minio/internal/ioutil.SafeClose[...](...) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/ioutil/ioutil.go:470 -github.com/minio/minio/internal/logger/target/http.(*Target).Cancel(0xc000fed6c0) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/target/http/http.go:630 +0x98 -github.com/minio/minio/cmd.replicationTestAudit.func2() - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:593 +0xa4 -github.com/minio/minio/cmd.testReplicationMRFMarkerRecovery(0xc000f31d48, {0x1099ca8b0, _}, {_, _}, {_, _}, {_, _}, {{0x105dfce44, ...}, ...}, ...) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:486 +0x5cb8 -github.com/minio/minio/cmd.TestReplicationMRFMarkerRecovery.func1.1({0x1099ca8b0, 0xc00251aea0}, {0x105df2e8e, 0x7}, {0xc0024cc700, 0x3c}, {0x109974420, _}, {{0x105dfce44, 0xa}, ...}, ...) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:53 +0x124 -github.com/minio/minio/cmd.ExecObjectLayerAPITest({0xc000f31d48, 0xc002f98030, {0xc002e26720, 0x1, 0x1}, 0x0, {0x0, 0x0, 0x0, {0x0, ...}, ...}}) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/test-utils_test.go:1817 +0x5f0 -github.com/minio/minio/cmd.TestReplicationMRFMarkerRecovery.func1(0xc000f31d48) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:52 +0x15c -testing.tRunner(0xc000f31d48, 0xc002777e00) - /opt/homebrew/Cellar/go/1.27.1/libexec/src/testing/testing.go:2193 +0x168 -created by testing.(*T).Run in goroutine 5772 - /opt/homebrew/Cellar/go/1.27.1/libexec/src/testing/testing.go:2258 +0x7c0 -=== RUN TestReplicationMRFMarkerRecovery/lock-failure - replication-delete-mrf_test.go:485: ErasureSD: lock-failure recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lock-failure recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error#01 -=== RUN TestReplicationMRFMarkerRecovery/legacy - replication-delete-mrf_test.go:485: ErasureSD: legacy recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: legacy recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/unrecorded-purge - replication-delete-mrf_test.go:485: ErasureSD: unrecorded-purge recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: unrecorded-purge recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/two-targets - replication-delete-mrf_test.go:485: ErasureSD: two-targets recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: two-targets recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/two-targets-offline - replication-delete-mrf_test.go:485: ErasureSD: two-targets-offline recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: two-targets-offline recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/replica-source - replication-delete-mrf_test.go:485: ErasureSD: replica-source recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: replica-source recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/lost-reply-canonical - replication-delete-mrf_test.go:485: ErasureSD: lost-reply-canonical recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lost-reply-canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/lost-reply-legacy - replication-delete-mrf_test.go:485: ErasureSD: lost-reply-legacy recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lost-reply-legacy recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/creation - replication-delete-mrf_test.go:485: ErasureSD: creation recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: creation recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/partial-creation-block -=== RUN TestReplicationMRFMarkerRecovery/retry-budget-and-scanner - replication-delete-mrf_test.go:485: ErasureSD: retry-budget-and-scanner recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: retry-budget-and-scanner recovered; 1 target(s), persisted MRF, source/target metadata checked ---- PASS: TestReplicationMRFMarkerRecovery (8.55s) - --- PASS: TestReplicationMRFMarkerRecovery/canonical (0.51s) - --- PASS: TestReplicationMRFMarkerRecovery/lock-failure (0.56s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata (2.02s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker (0.11s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker#01 (0.11s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version#01 (0.11s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket#01 (0.11s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object#01 (0.11s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime#01 (0.11s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing#01 (0.11s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error#01 (0.11s) - --- PASS: TestReplicationMRFMarkerRecovery/legacy (0.48s) - --- PASS: TestReplicationMRFMarkerRecovery/unrecorded-purge (0.59s) - --- PASS: TestReplicationMRFMarkerRecovery/two-targets (0.47s) - --- PASS: TestReplicationMRFMarkerRecovery/two-targets-offline (0.53s) - --- PASS: TestReplicationMRFMarkerRecovery/replica-source (0.43s) - --- PASS: TestReplicationMRFMarkerRecovery/lost-reply-canonical (0.68s) - --- PASS: TestReplicationMRFMarkerRecovery/lost-reply-legacy (0.52s) - --- PASS: TestReplicationMRFMarkerRecovery/creation (0.43s) - --- PASS: TestReplicationMRFMarkerRecovery/partial-creation-block (0.78s) - --- PASS: TestReplicationMRFMarkerRecovery/retry-budget-and-scanner (0.55s) -=== RUN TestReplicationDeleteQueueFullRetryBudget ---- PASS: TestReplicationDeleteQueueFullRetryBudget (0.00s) -=== RUN TestReplicateDeleteOperationExits -=== RUN TestReplicateDeleteOperationExits/marker-creation/pending-success -=== RUN TestReplicateDeleteOperationExits/marker-creation/completed-creation -=== RUN TestReplicateDeleteOperationExits/marker-creation/existing-marker -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/marker-creation/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-creation/offline -=== RUN TestReplicateDeleteOperationExits/marker-creation/retry-failed -=== RUN TestReplicateDeleteOperationExits/marker-creation/purge-complete -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-success -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-failure -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/offline -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-failure -=== RUN TestReplicateDeleteOperationExits/marker-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/marker-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/marker-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/marker-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-purge/offline -=== RUN TestReplicateDeleteOperationExits/marker-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/marker-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-failure -=== RUN TestReplicateDeleteOperationExits/object-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/object-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/object-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/object-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/object-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/object-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/object-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/object-purge/offline -=== RUN TestReplicateDeleteOperationExits/object-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/object-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-failure ---- PASS: TestReplicateDeleteOperationExits (0.58s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-read-quorum (0.07s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-unavailable (0.11s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-unavailable (0.10s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-unavailable (0.06s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-unavailable (0.03s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-unavailable (0.16s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-failure (0.00s) -=== RUN TestReplicateDeletePurgeMissingTargetState ---- PASS: TestReplicateDeletePurgeMissingTargetState (0.00s) -=== RUN TestReplicationMRFDropsVisible -=== RUN TestReplicationMRFDropsVisible/bucket -=== RUN TestReplicationMRFDropsVisible/node ---- PASS: TestReplicationMRFDropsVisible (0.01s) - --- PASS: TestReplicationMRFDropsVisible/bucket (0.00s) - --- PASS: TestReplicationMRFDropsVisible/node (0.00s) -PASS -ok github.com/minio/minio/cmd 14.031s diff --git a/docs/investigations/r6/verification/baseline-scope.log b/docs/investigations/r6/verification/baseline-scope.log deleted file mode 100644 index 489b98293..000000000 --- a/docs/investigations/r6/verification/baseline-scope.log +++ /dev/null @@ -1,444 +0,0 @@ -=== RUN TestReplicatedInfos ---- PASS: TestReplicatedInfos (0.00s) -=== RUN TestReplicationResync ---- PASS: TestReplicationResync (0.00s) -=== RUN TestReplicationResyncwrapper ---- PASS: TestReplicationResyncwrapper (0.00s) -=== RUN TestReplicationValidationObjectUsesRulePrefix -=== RUN TestReplicationValidationObjectUsesRulePrefix/empty_prefix -=== RUN TestReplicationValidationObjectUsesRulePrefix/filter_prefix -=== RUN TestReplicationValidationObjectUsesRulePrefix/and_prefix ---- PASS: TestReplicationValidationObjectUsesRulePrefix (0.00s) - --- PASS: TestReplicationValidationObjectUsesRulePrefix/empty_prefix (0.00s) - --- PASS: TestReplicationValidationObjectUsesRulePrefix/filter_prefix (0.00s) - --- PASS: TestReplicationValidationObjectUsesRulePrefix/and_prefix (0.00s) -=== RUN TestResyncBucketFinalize -=== RUN TestResyncBucketFinalize/persists_complete_counts -=== RUN TestResyncBucketFinalize/parent_cancel_during_drain_downgrades_to_failed -=== RUN TestResyncBucketFinalize/user_cancel_persists_canceled ---- PASS: TestResyncBucketFinalize (0.11s) - --- PASS: TestResyncBucketFinalize/persists_complete_counts (0.01s) - --- PASS: TestResyncBucketFinalize/parent_cancel_during_drain_downgrades_to_failed (0.01s) - --- PASS: TestResyncBucketFinalize/user_cancel_persists_canceled (0.01s) -=== RUN TestResyncFinishDrainsResults ---- PASS: TestResyncFinishDrainsResults (0.00s) -=== RUN TestResyncFinishWaitsForInflightWorker ---- PASS: TestResyncFinishWaitsForInflightWorker (0.00s) -=== RUN TestResyncResultFor -=== RUN TestResyncResultFor/completed_update -=== RUN TestResyncResultFor/failed_update_over_existing_version -=== RUN TestResyncResultFor/completed_but_errored_is_a_failure -=== RUN TestResyncResultFor/delete_failed -=== RUN TestResyncResultFor/delete_marker_replicated_counts_zero_bytes -=== RUN TestResyncResultFor/arn_not_attempted_is_a_failure -=== RUN TestResyncResultFor/completed_with_zero_size_falls_back_to_object_size -=== RUN TestResyncResultFor/version_purge_complete_is_a_success -=== RUN TestResyncResultFor/version_purge_failed_is_a_failure -=== RUN TestResyncResultFor/benign_duplicate_412_is_a_success ---- PASS: TestResyncResultFor (0.00s) - --- PASS: TestResyncResultFor/completed_update (0.00s) - --- PASS: TestResyncResultFor/failed_update_over_existing_version (0.00s) - --- PASS: TestResyncResultFor/completed_but_errored_is_a_failure (0.00s) - --- PASS: TestResyncResultFor/delete_failed (0.00s) - --- PASS: TestResyncResultFor/delete_marker_replicated_counts_zero_bytes (0.00s) - --- PASS: TestResyncResultFor/arn_not_attempted_is_a_failure (0.00s) - --- PASS: TestResyncResultFor/completed_with_zero_size_falls_back_to_object_size (0.00s) - --- PASS: TestResyncResultFor/version_purge_complete_is_a_success (0.00s) - --- PASS: TestResyncResultFor/version_purge_failed_is_a_failure (0.00s) - --- PASS: TestResyncResultFor/benign_duplicate_412_is_a_success (0.00s) -=== RUN TestReplicationActionForTargetRetentionRemoval -=== RUN TestReplicationActionForTargetRetentionRemoval/removal_confirmed_by_destination -=== RUN TestReplicationActionForTargetRetentionRemoval/destination_still_holds_the_retention_hidden_from_HEAD -=== RUN TestReplicationActionForTargetRetentionRemoval/retention_hidden_from_HEAD_by_permissions -=== RUN TestReplicationActionForTargetRetentionRemoval/destination_reports_no_object_lock_configuration -=== RUN TestReplicationActionForTargetRetentionRemoval/version_never_had_retention_is_not_confirmed ---- PASS: TestReplicationActionForTargetRetentionRemoval (0.00s) - --- PASS: TestReplicationActionForTargetRetentionRemoval/removal_confirmed_by_destination (0.00s) - --- PASS: TestReplicationActionForTargetRetentionRemoval/destination_still_holds_the_retention_hidden_from_HEAD (0.00s) - --- PASS: TestReplicationActionForTargetRetentionRemoval/retention_hidden_from_HEAD_by_permissions (0.00s) - --- PASS: TestReplicationActionForTargetRetentionRemoval/destination_reports_no_object_lock_configuration (0.00s) - --- PASS: TestReplicationActionForTargetRetentionRemoval/version_never_had_retention_is_not_confirmed (0.00s) -=== RUN TestReplicationActionForTargetNullVersionResync -=== RUN TestReplicationActionForTargetNullVersionResync/destination_holds_retention -=== RUN TestReplicationActionForTargetNullVersionResync/retention_read_denied ---- PASS: TestReplicationActionForTargetNullVersionResync (0.00s) - --- PASS: TestReplicationActionForTargetNullVersionResync/destination_holds_retention (0.00s) - --- PASS: TestReplicationActionForTargetNullVersionResync/retention_read_denied (0.00s) -=== RUN TestReplicationActionForTargetTimestampOnlyRemoval -=== RUN TestReplicationActionForTargetTimestampOnlyRemoval/retention_hidden_from_HEAD_by_permissions -=== RUN TestReplicationActionForTargetTimestampOnlyRemoval/destination_still_holds_the_retention -=== RUN TestReplicationActionForTargetTimestampOnlyRemoval/removal_confirmed_by_destination ---- PASS: TestReplicationActionForTargetTimestampOnlyRemoval (0.00s) - --- PASS: TestReplicationActionForTargetTimestampOnlyRemoval/retention_hidden_from_HEAD_by_permissions (0.00s) - --- PASS: TestReplicationActionForTargetTimestampOnlyRemoval/destination_still_holds_the_retention (0.00s) - --- PASS: TestReplicationActionForTargetTimestampOnlyRemoval/removal_confirmed_by_destination (0.00s) -=== RUN TestReplicateDeleteMarkerPurge -=== RUN TestReplicateDeleteMarkerPurge/recover_legacy_false -=== RUN TestReplicateDeleteMarkerPurge/recover_legacy_true ---- PASS: TestReplicateDeleteMarkerPurge (0.47s) - --- PASS: TestReplicateDeleteMarkerPurge/recover_legacy_false (0.26s) - --- PASS: TestReplicateDeleteMarkerPurge/recover_legacy_true (0.21s) -=== RUN TestReplicateDeleteMarkerTargetSemantics -=== RUN TestReplicateDeleteMarkerTargetSemantics/existing_marker_is_idempotent -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_removes_an_existing_marker -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_forbidden -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_method_rejected -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_unavailable ---- PASS: TestReplicateDeleteMarkerTargetSemantics (0.17s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/existing_marker_is_idempotent (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_removes_an_existing_marker (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_forbidden (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_method_rejected (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_unavailable (0.17s) -=== RUN TestReplicationMRFMarkerRecovery -=== RUN TestReplicationMRFMarkerRecovery/canonical - replication-delete-mrf_test.go:485: ErasureSD: canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -goroutine 6684 [running]: -runtime/debug.Stack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:26 +0x64 -runtime/debug.PrintStack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:18 +0x1c -github.com/minio/minio/internal/ioutil.SafeClose[...](...) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/ioutil/ioutil.go:470 -github.com/minio/minio/internal/logger/target/http.(*Target).Cancel(0x4add905116c0) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/target/http/http.go:630 +0x74 -created by github.com/minio/minio/internal/logger.cancelTargets in goroutine 6745 - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/targets.go:213 +0x28 - replication-delete-mrf_test.go:485: Erasure: canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -goroutine 10189 [running]: -runtime/debug.Stack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:26 +0x64 -runtime/debug.PrintStack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:18 +0x1c -github.com/minio/minio/internal/ioutil.SafeClose[...](...) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/ioutil/ioutil.go:470 -github.com/minio/minio/internal/logger/target/http.(*Target).Cancel(0x4add9bf7d380) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/target/http/http.go:630 +0x74 -created by github.com/minio/minio/internal/logger.cancelTargets in goroutine 6745 - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/targets.go:213 +0x28 -=== RUN TestReplicationMRFMarkerRecovery/lock-failure - replication-delete-mrf_test.go:485: ErasureSD: lock-failure recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lock-failure recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error#01 -=== RUN TestReplicationMRFMarkerRecovery/legacy - replication-delete-mrf_test.go:485: ErasureSD: legacy recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: legacy recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/unrecorded-purge - replication-delete-mrf_test.go:485: ErasureSD: unrecorded-purge recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: unrecorded-purge recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/two-targets - replication-delete-mrf_test.go:485: ErasureSD: two-targets recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: two-targets recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/two-targets-offline - replication-delete-mrf_test.go:485: ErasureSD: two-targets-offline recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: two-targets-offline recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/replica-source - replication-delete-mrf_test.go:485: ErasureSD: replica-source recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: replica-source recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/lost-reply-canonical - replication-delete-mrf_test.go:485: ErasureSD: lost-reply-canonical recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lost-reply-canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/lost-reply-legacy - replication-delete-mrf_test.go:485: ErasureSD: lost-reply-legacy recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lost-reply-legacy recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/creation - replication-delete-mrf_test.go:485: ErasureSD: creation recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: creation recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/partial-creation-block -=== RUN TestReplicationMRFMarkerRecovery/retry-budget-and-scanner - replication-delete-mrf_test.go:485: ErasureSD: retry-budget-and-scanner recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: retry-budget-and-scanner recovered; 1 target(s), persisted MRF, source/target metadata checked ---- PASS: TestReplicationMRFMarkerRecovery (6.13s) - --- PASS: TestReplicationMRFMarkerRecovery/canonical (0.22s) - --- PASS: TestReplicationMRFMarkerRecovery/lock-failure (0.24s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata (1.90s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object#01 (0.12s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/legacy (0.26s) - --- PASS: TestReplicationMRFMarkerRecovery/unrecorded-purge (0.27s) - --- PASS: TestReplicationMRFMarkerRecovery/two-targets (0.28s) - --- PASS: TestReplicationMRFMarkerRecovery/two-targets-offline (0.29s) - --- PASS: TestReplicationMRFMarkerRecovery/replica-source (0.28s) - --- PASS: TestReplicationMRFMarkerRecovery/lost-reply-canonical (0.56s) - --- PASS: TestReplicationMRFMarkerRecovery/lost-reply-legacy (0.56s) - --- PASS: TestReplicationMRFMarkerRecovery/creation (0.34s) - --- PASS: TestReplicationMRFMarkerRecovery/partial-creation-block (0.47s) - --- PASS: TestReplicationMRFMarkerRecovery/retry-budget-and-scanner (0.45s) -=== RUN TestReplicationDeleteQueueFullRetryBudget ---- PASS: TestReplicationDeleteQueueFullRetryBudget (0.00s) -=== RUN TestReplicateDeleteOperationExits -=== RUN TestReplicateDeleteOperationExits/marker-creation/pending-success -=== RUN TestReplicateDeleteOperationExits/marker-creation/completed-creation -=== RUN TestReplicateDeleteOperationExits/marker-creation/existing-marker -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/marker-creation/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-creation/offline -=== RUN TestReplicateDeleteOperationExits/marker-creation/retry-failed -=== RUN TestReplicateDeleteOperationExits/marker-creation/purge-complete -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-success -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-failure -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/offline -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-failure -=== RUN TestReplicateDeleteOperationExits/marker-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/marker-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/marker-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/marker-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-purge/offline -=== RUN TestReplicateDeleteOperationExits/marker-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/marker-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-failure -=== RUN TestReplicateDeleteOperationExits/object-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/object-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/object-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/object-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/object-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/object-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/object-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/object-purge/offline -=== RUN TestReplicateDeleteOperationExits/object-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/object-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-failure ---- PASS: TestReplicateDeleteOperationExits (0.79s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-read-quorum (0.06s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-unavailable (0.15s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-unavailable (0.20s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-unavailable (0.05s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-unavailable (0.14s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-unavailable (0.16s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-failure (0.00s) -=== RUN TestReplicateDeletePurgeMissingTargetState ---- PASS: TestReplicateDeletePurgeMissingTargetState (0.00s) -=== RUN TestReplicationMRFDropsVisible -=== RUN TestReplicationMRFDropsVisible/bucket -=== RUN TestReplicationMRFDropsVisible/node ---- PASS: TestReplicationMRFDropsVisible (0.00s) - --- PASS: TestReplicationMRFDropsVisible/bucket (0.00s) - --- PASS: TestReplicationMRFDropsVisible/node (0.00s) -=== RUN TestReplicationObjectDeleteWorkerAffinity ---- PASS: TestReplicationObjectDeleteWorkerAffinity (0.00s) -=== RUN TestSiteResyncCancelState ---- PASS: TestSiteResyncCancelState (0.00s) -=== RUN TestResyncRecoveryOwnsLeaderContext -=== RUN TestResyncRecoveryOwnsLeaderContext/lose_leader_false -=== RUN TestResyncRecoveryOwnsLeaderContext/lose_leader_true ---- PASS: TestResyncRecoveryOwnsLeaderContext (0.00s) - --- PASS: TestResyncRecoveryOwnsLeaderContext/lose_leader_false (0.00s) - --- PASS: TestResyncRecoveryOwnsLeaderContext/lose_leader_true (0.00s) -=== RUN TestResyncCancelRouting ---- PASS: TestResyncCancelRouting (0.00s) -=== RUN TestResyncCancellationWinsFinalization ---- PASS: TestResyncCancellationWinsFinalization (0.00s) -=== RUN TestResyncCancelFullWorkerQueue ---- PASS: TestResyncCancelFullWorkerQueue (0.00s) -=== RUN TestResyncCancelBlockedWalkReceive ---- PASS: TestResyncCancelBlockedWalkReceive (0.00s) -=== RUN TestResyncCancelsOwnedWalkOnError ---- PASS: TestResyncCancelsOwnedWalkOnError (0.00s) -=== RUN TestReplicationTrustControlsInternalOptionsAndEvents ---- PASS: TestReplicationTrustControlsInternalOptionsAndEvents (0.00s) -PASS -ok github.com/minio/minio/cmd 10.118s -=== RUN TestReplicate -=== RUN TestReplicate/#00 -=== RUN TestReplicate/c1test -=== RUN TestReplicate/c1test#01 -=== RUN TestReplicate/c1test#02 -=== RUN TestReplicate/c1test#03 -=== RUN TestReplicate/c1test#04 -=== RUN TestReplicate/c1test#05 -=== RUN TestReplicate/c1test#06 -=== RUN TestReplicate/c1test#07 -=== RUN TestReplicate/c2test -=== RUN TestReplicate/c2test#01 -=== RUN TestReplicate/c2test#02 -=== RUN TestReplicate/c2test#03 -=== RUN TestReplicate/c2test#04 -=== RUN TestReplicate/c2test#05 -=== RUN TestReplicate/c2test#06 -=== RUN TestReplicate/xy/c3test -=== RUN TestReplicate/xyz/c3test -=== RUN TestReplicate/xyz/c3test#01 -=== RUN TestReplicate/xyz/c3test#02 -=== RUN TestReplicate/xyz/c3test#03 -=== RUN TestReplicate/xyz/c3test#04 -=== RUN TestReplicate/xy/c3test#01 -=== RUN TestReplicate/xyz/c3test#05 -=== RUN TestReplicate/xyz/c3test#06 -=== RUN TestReplicate/xyz/c3test#07 -=== RUN TestReplicate/abc/c3test -=== RUN TestReplicate/xy/c4test -=== RUN TestReplicate/xa/c4test -=== RUN TestReplicate/xyz/c4test -=== RUN TestReplicate/xyz/c4test#01 -=== RUN TestReplicate/xyz/c4test#02 -=== RUN TestReplicate/xyz/c4test#03 -=== RUN TestReplicate/abc/c4test -=== RUN TestReplicate/abc/c4test#01 -=== RUN TestReplicate/abc/c4test#02 -=== RUN TestReplicate/abc/c4test#03 -=== RUN TestReplicate/abc/c4test#04 -=== RUN TestReplicate/xy/c5test -=== RUN TestReplicate/xa/c5test ---- PASS: TestReplicate (0.00s) - --- PASS: TestReplicate/#00 (0.00s) - --- PASS: TestReplicate/c1test (0.00s) - --- PASS: TestReplicate/c1test#01 (0.00s) - --- PASS: TestReplicate/c1test#02 (0.00s) - --- PASS: TestReplicate/c1test#03 (0.00s) - --- PASS: TestReplicate/c1test#04 (0.00s) - --- PASS: TestReplicate/c1test#05 (0.00s) - --- PASS: TestReplicate/c1test#06 (0.00s) - --- PASS: TestReplicate/c1test#07 (0.00s) - --- PASS: TestReplicate/c2test (0.00s) - --- PASS: TestReplicate/c2test#01 (0.00s) - --- PASS: TestReplicate/c2test#02 (0.00s) - --- PASS: TestReplicate/c2test#03 (0.00s) - --- PASS: TestReplicate/c2test#04 (0.00s) - --- PASS: TestReplicate/c2test#05 (0.00s) - --- PASS: TestReplicate/c2test#06 (0.00s) - --- PASS: TestReplicate/xy/c3test (0.00s) - --- PASS: TestReplicate/xyz/c3test (0.00s) - --- PASS: TestReplicate/xyz/c3test#01 (0.00s) - --- PASS: TestReplicate/xyz/c3test#02 (0.00s) - --- PASS: TestReplicate/xyz/c3test#03 (0.00s) - --- PASS: TestReplicate/xyz/c3test#04 (0.00s) - --- PASS: TestReplicate/xy/c3test#01 (0.00s) - --- PASS: TestReplicate/xyz/c3test#05 (0.00s) - --- PASS: TestReplicate/xyz/c3test#06 (0.00s) - --- PASS: TestReplicate/xyz/c3test#07 (0.00s) - --- PASS: TestReplicate/abc/c3test (0.00s) - --- PASS: TestReplicate/xy/c4test (0.00s) - --- PASS: TestReplicate/xa/c4test (0.00s) - --- PASS: TestReplicate/xyz/c4test (0.00s) - --- PASS: TestReplicate/xyz/c4test#01 (0.00s) - --- PASS: TestReplicate/xyz/c4test#02 (0.00s) - --- PASS: TestReplicate/xyz/c4test#03 (0.00s) - --- PASS: TestReplicate/abc/c4test (0.00s) - --- PASS: TestReplicate/abc/c4test#01 (0.00s) - --- PASS: TestReplicate/abc/c4test#02 (0.00s) - --- PASS: TestReplicate/abc/c4test#03 (0.00s) - --- PASS: TestReplicate/abc/c4test#04 (0.00s) - --- PASS: TestReplicate/xy/c5test (0.00s) - --- PASS: TestReplicate/xa/c5test (0.00s) -PASS -ok github.com/minio/minio/internal/bucket/replication 0.624s diff --git a/docs/investigations/r6/verification/baseline-verification.json b/docs/investigations/r6/verification/baseline-verification.json deleted file mode 100644 index bd58e9b87..000000000 --- a/docs/investigations/r6/verification/baseline-verification.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "source_sha256": { - "cmd/bucket-replication.go": "999c2818a8980cbeb55cfcbc244840069fb44e042b4b4d6ef7668c2660ce31e0", - "cmd/bucket-replication-utils.go": "365641c760901641e8320cc5123697ab92a46f1add612048b991ed2d1cfad43b", - "cmd/replication-delete-marker_test.go": "d967787804d558ac6266b113228fdf4a4f9fcb7cab39138a4fb07558814ccca4", - "cmd/replication-delete-operation_test.go": "2888a04a543324776041316de2821f388d28c3c1a6f5d0031e5ac9d34f58d504", - "cmd/replication-delete-mrf_test.go": "5e160f7e19cbbb8cd5fa4e7ffd9cff9e09361b3fc4c5ee5ae61458f99c777781" - }, - "checks": [ - { - "name": "scope", - "command": [ - "go", - "test", - "-p", - "2", - "./cmd", - "./internal/bucket/replication", - "-run", - "TestReplication|TestReplicate|TestMRF|TestResync|TestSiteResync", - "-count=1", - "-v" - ], - "exit_code": 0, - "seconds": 38.664, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/final-scope.log", - "log_sha256": "c0f286b61b0a8e668dc92f4d62800f010970331e257ea49db504373d39574370", - "finished_at_utc": "2026-09-15T16:16:22.027596+00:00" - }, - { - "name": "race", - "command": [ - "go", - "test", - "-race", - "-p", - "2", - "./cmd", - "-run", - "TestReplicateDelete|TestReplicationMRF|TestReplicationDeleteQueueFull", - "-count=1", - "-v" - ], - "exit_code": 0, - "seconds": 72.883, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/final-race.log", - "log_sha256": "826c3de1632f026f806474328807408c8e197726a30a70ffff4376004ec51e53", - "finished_at_utc": "2026-09-15T16:17:34.912761+00:00" - }, - { - "name": "build", - "command": [ - "go", - "build", - "-p", - "2", - "./..." - ], - "exit_code": 0, - "seconds": 39.464, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/final-build.log", - "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "finished_at_utc": "2026-09-15T16:18:14.378361+00:00" - }, - { - "name": "vet", - "command": [ - "go", - "vet", - "-p", - "2", - "./cmd", - "./internal/bucket/replication" - ], - "exit_code": 0, - "seconds": 10.278, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/final-vet.log", - "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "finished_at_utc": "2026-09-15T16:18:24.657311+00:00" - }, - { - "name": "lint", - "command": [ - "/Users/vonng/pgsty/silo/.bin/golangci/v2.13.1/golangci-lint", - "run", - "--build-tags", - "kqueue", - "--timeout=10m", - "--config", - "./.golangci.yml" - ], - "exit_code": 0, - "seconds": 126.658, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/final-lint.log", - "log_sha256": "e92606b0bf483111dff0a120c315ea165821348f31365020e2468a0059095c47", - "finished_at_utc": "2026-09-15T16:20:31.317818+00:00" - } - ], - "source_unchanged": true -} diff --git a/docs/investigations/r6/verification/baseline-vet.log b/docs/investigations/r6/verification/baseline-vet.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r6/verification/rebased-build.log b/docs/investigations/r6/verification/rebased-build.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r6/verification/rebased-delete-recheck.log b/docs/investigations/r6/verification/rebased-delete-recheck.log deleted file mode 100644 index 394a1b61c..000000000 --- a/docs/investigations/r6/verification/rebased-delete-recheck.log +++ /dev/null @@ -1,30 +0,0 @@ -=== RUN TestDeleteObjectConditional -=== RUN TestDeleteObjectConditional/wrong-etag-precondition-failed -=== RUN TestDeleteObjectConditional/missing-object-not-found -=== RUN TestDeleteObjectConditional/correct-etag-succeeds ---- PASS: TestDeleteObjectConditional (0.08s) - --- PASS: TestDeleteObjectConditional/wrong-etag-precondition-failed (0.00s) - --- PASS: TestDeleteObjectConditional/missing-object-not-found (0.00s) - --- PASS: TestDeleteObjectConditional/correct-etag-succeeds (0.00s) -=== RUN TestDeleteObjectConditionalWithReadQuorumFailure ---- PASS: TestDeleteObjectConditionalWithReadQuorumFailure (0.07s) -=== RUN TestDeleteObjectConditionalVersioned -=== RUN TestDeleteObjectConditionalVersioned/wildcard-on-delete-marker-latest -=== RUN TestDeleteObjectConditionalVersioned/explicit-version-selection -=== RUN TestDeleteObjectConditionalVersioned/missing-version -=== RUN TestDeleteObjectConditionalVersioned/missing-version-absent-key -=== RUN TestDeleteObjectConditionalVersioned/wildcard-on-explicit-delete-marker-version ---- PASS: TestDeleteObjectConditionalVersioned (0.13s) - --- PASS: TestDeleteObjectConditionalVersioned/wildcard-on-delete-marker-latest (0.01s) - --- PASS: TestDeleteObjectConditionalVersioned/explicit-version-selection (0.02s) - --- PASS: TestDeleteObjectConditionalVersioned/missing-version (0.01s) - --- PASS: TestDeleteObjectConditionalVersioned/missing-version-absent-key (0.00s) - --- PASS: TestDeleteObjectConditionalVersioned/wildcard-on-explicit-delete-marker-version (0.01s) -=== RUN TestDeleteObjectsVersioned ---- PASS: TestDeleteObjectsVersioned (0.08s) -=== RUN TestDeleteObject ---- PASS: TestDeleteObject (0.45s) -=== RUN TestDeleteObjectVersionMarker ---- PASS: TestDeleteObjectVersionMarker (0.25s) -PASS -ok github.com/minio/minio/cmd 3.066s diff --git a/docs/investigations/r6/verification/rebased-delete-verification.json b/docs/investigations/r6/verification/rebased-delete-verification.json deleted file mode 100644 index bb4e81abc..000000000 --- a/docs/investigations/r6/verification/rebased-delete-verification.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "baseline": "cf381a7151ef25fc95ace5fedcd767fa19410de2", - "source_sha256": { - "cmd/bucket-replication.go": "999c2818a8980cbeb55cfcbc244840069fb44e042b4b4d6ef7668c2660ce31e0", - "cmd/bucket-replication-utils.go": "365641c760901641e8320cc5123697ab92a46f1add612048b991ed2d1cfad43b", - "cmd/replication-delete-marker_test.go": "d967787804d558ac6266b113228fdf4a4f9fcb7cab39138a4fb07558814ccca4", - "cmd/replication-delete-operation_test.go": "2888a04a543324776041316de2821f388d28c3c1a6f5d0031e5ac9d34f58d504", - "cmd/replication-delete-mrf_test.go": "5e160f7e19cbbb8cd5fa4e7ffd9cff9e09361b3fc4c5ee5ae61458f99c777781" - }, - "checks": [ - { - "name": "delete-recheck", - "command": [ - "go", - "test", - "-p", - "2", - "./cmd", - "-run", - "^(TestDeleteObjectConditional|TestDeleteObjectConditionalWithReadQuorumFailure|TestDeleteObjectConditionalVersioned|TestDeleteObjectsVersioned|TestDeleteObject|TestDeleteObjectVersionMarker)$", - "-count=1", - "-v" - ], - "exit_code": 0, - "seconds": 11.058, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/rebased-delete-recheck.log", - "log_sha256": "657a04bc3f5143f3355471f46a034ca234648374ca876927e58e5a755613c674", - "finished_at_utc": "2026-09-15T16:30:06.615423+00:00" - } - ], - "source_unchanged": true -} diff --git a/docs/investigations/r6/verification/rebased-lint.log b/docs/investigations/r6/verification/rebased-lint.log deleted file mode 100644 index 6a3ebaa7e..000000000 --- a/docs/investigations/r6/verification/rebased-lint.log +++ /dev/null @@ -1 +0,0 @@ -0 issues. diff --git a/docs/investigations/r6/verification/rebased-race.log b/docs/investigations/r6/verification/rebased-race.log deleted file mode 100644 index b91a048d5..000000000 --- a/docs/investigations/r6/verification/rebased-race.log +++ /dev/null @@ -1,276 +0,0 @@ -=== RUN TestReplicateDeleteMarkerPurge -=== RUN TestReplicateDeleteMarkerPurge/recover_legacy_false -=== RUN TestReplicateDeleteMarkerPurge/recover_legacy_true ---- PASS: TestReplicateDeleteMarkerPurge (0.56s) - --- PASS: TestReplicateDeleteMarkerPurge/recover_legacy_false (0.29s) - --- PASS: TestReplicateDeleteMarkerPurge/recover_legacy_true (0.27s) -=== RUN TestReplicateDeleteMarkerTargetSemantics -=== RUN TestReplicateDeleteMarkerTargetSemantics/existing_marker_is_idempotent -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_removes_an_existing_marker -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_forbidden -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_method_rejected -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_unavailable ---- PASS: TestReplicateDeleteMarkerTargetSemantics (0.09s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/existing_marker_is_idempotent (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_removes_an_existing_marker (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_forbidden (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_method_rejected (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_unavailable (0.09s) -=== RUN TestReplicationMRFMarkerRecovery -=== RUN TestReplicationMRFMarkerRecovery/canonical - replication-delete-mrf_test.go:485: ErasureSD: canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -goroutine 5732 [running]: -runtime/debug.Stack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:26 +0x6c -runtime/debug.PrintStack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:18 +0x24 -github.com/minio/minio/internal/ioutil.SafeClose[...](...) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/ioutil/ioutil.go:470 -github.com/minio/minio/internal/logger/target/http.(*Target).Cancel(0xc001231860) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/target/http/http.go:630 +0x98 -github.com/minio/minio/cmd.replicationTestAudit.func2() - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:593 +0xa4 -github.com/minio/minio/cmd.testReplicationMRFMarkerRecovery(0xc00137a248, {0x10a44f2c0, _}, {_, _}, {_, _}, {_, _}, {{0x10687dafa, ...}, ...}, ...) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:486 +0x5cb8 -github.com/minio/minio/cmd.TestReplicationMRFMarkerRecovery.func1.1({0x10a44f2c0, 0xc002e19930}, {0x106879eea, 0x9}, {0xc002bf60c0, 0x3c}, {0x10a3f8e30, _}, {{0x10687dafa, 0xa}, ...}, ...) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:53 +0x124 -github.com/minio/minio/cmd.ExecObjectLayerAPITest({0xc00137a248, 0xc002e65e90, {0xc002bf5cd0, 0x1, 0x1}, 0x0, {0x0, 0x0, 0x0, {0x0, ...}, ...}}) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/test-utils_test.go:1789 +0x32c -github.com/minio/minio/cmd.TestReplicationMRFMarkerRecovery.func1(0xc00137a248) - /Users/vonng/.codex/worktrees/aa3f/silo/cmd/replication-delete-mrf_test.go:52 +0x15c -testing.tRunner(0xc00137a248, 0xc002e65d70) - /opt/homebrew/Cellar/go/1.27.1/libexec/src/testing/testing.go:2193 +0x168 -created by testing.(*T).Run in goroutine 5731 - /opt/homebrew/Cellar/go/1.27.1/libexec/src/testing/testing.go:2258 +0x7c0 - replication-delete-mrf_test.go:485: Erasure: canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -goroutine 9170 [running]: -runtime/debug.Stack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:26 +0x6c -runtime/debug.PrintStack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:18 +0x24 -github.com/minio/minio/internal/ioutil.SafeClose[...](...) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/ioutil/ioutil.go:470 -github.com/minio/minio/internal/logger/target/http.(*Target).Cancel(0xc000cce000) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/target/http/http.go:630 +0x98 -created by github.com/minio/minio/internal/logger.cancelTargets in goroutine 5732 - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/targets.go:213 +0x3c -=== RUN TestReplicationMRFMarkerRecovery/lock-failure - replication-delete-mrf_test.go:485: ErasureSD: lock-failure recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lock-failure recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error#01 -=== RUN TestReplicationMRFMarkerRecovery/legacy - replication-delete-mrf_test.go:485: ErasureSD: legacy recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: legacy recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/unrecorded-purge - replication-delete-mrf_test.go:485: ErasureSD: unrecorded-purge recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: unrecorded-purge recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/two-targets - replication-delete-mrf_test.go:485: ErasureSD: two-targets recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: two-targets recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/two-targets-offline - replication-delete-mrf_test.go:485: ErasureSD: two-targets-offline recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: two-targets-offline recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/replica-source - replication-delete-mrf_test.go:485: ErasureSD: replica-source recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: replica-source recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/lost-reply-canonical - replication-delete-mrf_test.go:485: ErasureSD: lost-reply-canonical recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lost-reply-canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/lost-reply-legacy - replication-delete-mrf_test.go:485: ErasureSD: lost-reply-legacy recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lost-reply-legacy recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/creation - replication-delete-mrf_test.go:485: ErasureSD: creation recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: creation recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/partial-creation-block -=== RUN TestReplicationMRFMarkerRecovery/retry-budget-and-scanner - replication-delete-mrf_test.go:485: ErasureSD: retry-budget-and-scanner recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: retry-budget-and-scanner recovered; 1 target(s), persisted MRF, source/target metadata checked ---- PASS: TestReplicationMRFMarkerRecovery (6.77s) - --- PASS: TestReplicationMRFMarkerRecovery/canonical (0.31s) - --- PASS: TestReplicationMRFMarkerRecovery/lock-failure (0.29s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata (1.88s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version#01 (0.11s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/legacy (0.27s) - --- PASS: TestReplicationMRFMarkerRecovery/unrecorded-purge (0.35s) - --- PASS: TestReplicationMRFMarkerRecovery/two-targets (0.41s) - --- PASS: TestReplicationMRFMarkerRecovery/two-targets-offline (0.43s) - --- PASS: TestReplicationMRFMarkerRecovery/replica-source (0.40s) - --- PASS: TestReplicationMRFMarkerRecovery/lost-reply-canonical (0.54s) - --- PASS: TestReplicationMRFMarkerRecovery/lost-reply-legacy (0.75s) - --- PASS: TestReplicationMRFMarkerRecovery/creation (0.39s) - --- PASS: TestReplicationMRFMarkerRecovery/partial-creation-block (0.33s) - --- PASS: TestReplicationMRFMarkerRecovery/retry-budget-and-scanner (0.41s) -=== RUN TestReplicationDeleteQueueFullRetryBudget ---- PASS: TestReplicationDeleteQueueFullRetryBudget (0.00s) -=== RUN TestReplicateDeleteOperationExits -=== RUN TestReplicateDeleteOperationExits/marker-creation/pending-success -=== RUN TestReplicateDeleteOperationExits/marker-creation/completed-creation -=== RUN TestReplicateDeleteOperationExits/marker-creation/existing-marker -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/marker-creation/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-creation/offline -=== RUN TestReplicateDeleteOperationExits/marker-creation/retry-failed -=== RUN TestReplicateDeleteOperationExits/marker-creation/purge-complete -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-success -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-failure -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/offline -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-failure -=== RUN TestReplicateDeleteOperationExits/marker-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/marker-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/marker-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/marker-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-purge/offline -=== RUN TestReplicateDeleteOperationExits/marker-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/marker-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-failure -=== RUN TestReplicateDeleteOperationExits/object-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/object-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/object-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/object-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/object-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/object-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/object-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/object-purge/offline -=== RUN TestReplicateDeleteOperationExits/object-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/object-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-failure ---- PASS: TestReplicateDeleteOperationExits (0.43s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-read-quorum (0.08s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-unavailable (0.02s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-unavailable (0.02s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-unavailable (0.08s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-unavailable (0.12s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-unavailable (0.07s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-failure (0.00s) -=== RUN TestReplicateDeletePurgeMissingTargetState ---- PASS: TestReplicateDeletePurgeMissingTargetState (0.00s) -=== RUN TestReplicationMRFDropsVisible -=== RUN TestReplicationMRFDropsVisible/node -=== RUN TestReplicationMRFDropsVisible/bucket ---- PASS: TestReplicationMRFDropsVisible (0.01s) - --- PASS: TestReplicationMRFDropsVisible/node (0.00s) - --- PASS: TestReplicationMRFDropsVisible/bucket (0.00s) -PASS -ok github.com/minio/minio/cmd 11.683s diff --git a/docs/investigations/r6/verification/rebased-scope.log b/docs/investigations/r6/verification/rebased-scope.log deleted file mode 100644 index 32cfe5b38..000000000 --- a/docs/investigations/r6/verification/rebased-scope.log +++ /dev/null @@ -1,444 +0,0 @@ -=== RUN TestReplicatedInfos ---- PASS: TestReplicatedInfos (0.00s) -=== RUN TestReplicationResync ---- PASS: TestReplicationResync (0.00s) -=== RUN TestReplicationResyncwrapper ---- PASS: TestReplicationResyncwrapper (0.00s) -=== RUN TestReplicationValidationObjectUsesRulePrefix -=== RUN TestReplicationValidationObjectUsesRulePrefix/empty_prefix -=== RUN TestReplicationValidationObjectUsesRulePrefix/filter_prefix -=== RUN TestReplicationValidationObjectUsesRulePrefix/and_prefix ---- PASS: TestReplicationValidationObjectUsesRulePrefix (0.00s) - --- PASS: TestReplicationValidationObjectUsesRulePrefix/empty_prefix (0.00s) - --- PASS: TestReplicationValidationObjectUsesRulePrefix/filter_prefix (0.00s) - --- PASS: TestReplicationValidationObjectUsesRulePrefix/and_prefix (0.00s) -=== RUN TestResyncBucketFinalize -=== RUN TestResyncBucketFinalize/persists_complete_counts -=== RUN TestResyncBucketFinalize/parent_cancel_during_drain_downgrades_to_failed -=== RUN TestResyncBucketFinalize/user_cancel_persists_canceled ---- PASS: TestResyncBucketFinalize (0.13s) - --- PASS: TestResyncBucketFinalize/persists_complete_counts (0.01s) - --- PASS: TestResyncBucketFinalize/parent_cancel_during_drain_downgrades_to_failed (0.01s) - --- PASS: TestResyncBucketFinalize/user_cancel_persists_canceled (0.01s) -=== RUN TestResyncFinishDrainsResults ---- PASS: TestResyncFinishDrainsResults (0.00s) -=== RUN TestResyncFinishWaitsForInflightWorker ---- PASS: TestResyncFinishWaitsForInflightWorker (0.00s) -=== RUN TestResyncResultFor -=== RUN TestResyncResultFor/completed_update -=== RUN TestResyncResultFor/failed_update_over_existing_version -=== RUN TestResyncResultFor/completed_but_errored_is_a_failure -=== RUN TestResyncResultFor/delete_failed -=== RUN TestResyncResultFor/delete_marker_replicated_counts_zero_bytes -=== RUN TestResyncResultFor/arn_not_attempted_is_a_failure -=== RUN TestResyncResultFor/completed_with_zero_size_falls_back_to_object_size -=== RUN TestResyncResultFor/version_purge_complete_is_a_success -=== RUN TestResyncResultFor/version_purge_failed_is_a_failure -=== RUN TestResyncResultFor/benign_duplicate_412_is_a_success ---- PASS: TestResyncResultFor (0.00s) - --- PASS: TestResyncResultFor/completed_update (0.00s) - --- PASS: TestResyncResultFor/failed_update_over_existing_version (0.00s) - --- PASS: TestResyncResultFor/completed_but_errored_is_a_failure (0.00s) - --- PASS: TestResyncResultFor/delete_failed (0.00s) - --- PASS: TestResyncResultFor/delete_marker_replicated_counts_zero_bytes (0.00s) - --- PASS: TestResyncResultFor/arn_not_attempted_is_a_failure (0.00s) - --- PASS: TestResyncResultFor/completed_with_zero_size_falls_back_to_object_size (0.00s) - --- PASS: TestResyncResultFor/version_purge_complete_is_a_success (0.00s) - --- PASS: TestResyncResultFor/version_purge_failed_is_a_failure (0.00s) - --- PASS: TestResyncResultFor/benign_duplicate_412_is_a_success (0.00s) -=== RUN TestReplicationActionForTargetRetentionRemoval -=== RUN TestReplicationActionForTargetRetentionRemoval/removal_confirmed_by_destination -=== RUN TestReplicationActionForTargetRetentionRemoval/destination_still_holds_the_retention_hidden_from_HEAD -=== RUN TestReplicationActionForTargetRetentionRemoval/retention_hidden_from_HEAD_by_permissions -=== RUN TestReplicationActionForTargetRetentionRemoval/destination_reports_no_object_lock_configuration -=== RUN TestReplicationActionForTargetRetentionRemoval/version_never_had_retention_is_not_confirmed ---- PASS: TestReplicationActionForTargetRetentionRemoval (0.00s) - --- PASS: TestReplicationActionForTargetRetentionRemoval/removal_confirmed_by_destination (0.00s) - --- PASS: TestReplicationActionForTargetRetentionRemoval/destination_still_holds_the_retention_hidden_from_HEAD (0.00s) - --- PASS: TestReplicationActionForTargetRetentionRemoval/retention_hidden_from_HEAD_by_permissions (0.00s) - --- PASS: TestReplicationActionForTargetRetentionRemoval/destination_reports_no_object_lock_configuration (0.00s) - --- PASS: TestReplicationActionForTargetRetentionRemoval/version_never_had_retention_is_not_confirmed (0.00s) -=== RUN TestReplicationActionForTargetNullVersionResync -=== RUN TestReplicationActionForTargetNullVersionResync/destination_holds_retention -=== RUN TestReplicationActionForTargetNullVersionResync/retention_read_denied ---- PASS: TestReplicationActionForTargetNullVersionResync (0.00s) - --- PASS: TestReplicationActionForTargetNullVersionResync/destination_holds_retention (0.00s) - --- PASS: TestReplicationActionForTargetNullVersionResync/retention_read_denied (0.00s) -=== RUN TestReplicationActionForTargetTimestampOnlyRemoval -=== RUN TestReplicationActionForTargetTimestampOnlyRemoval/retention_hidden_from_HEAD_by_permissions -=== RUN TestReplicationActionForTargetTimestampOnlyRemoval/destination_still_holds_the_retention -=== RUN TestReplicationActionForTargetTimestampOnlyRemoval/removal_confirmed_by_destination ---- PASS: TestReplicationActionForTargetTimestampOnlyRemoval (0.00s) - --- PASS: TestReplicationActionForTargetTimestampOnlyRemoval/retention_hidden_from_HEAD_by_permissions (0.00s) - --- PASS: TestReplicationActionForTargetTimestampOnlyRemoval/destination_still_holds_the_retention (0.00s) - --- PASS: TestReplicationActionForTargetTimestampOnlyRemoval/removal_confirmed_by_destination (0.00s) -=== RUN TestReplicateDeleteMarkerPurge -=== RUN TestReplicateDeleteMarkerPurge/recover_legacy_false -=== RUN TestReplicateDeleteMarkerPurge/recover_legacy_true ---- PASS: TestReplicateDeleteMarkerPurge (0.61s) - --- PASS: TestReplicateDeleteMarkerPurge/recover_legacy_false (0.31s) - --- PASS: TestReplicateDeleteMarkerPurge/recover_legacy_true (0.30s) -=== RUN TestReplicateDeleteMarkerTargetSemantics -=== RUN TestReplicateDeleteMarkerTargetSemantics/existing_marker_is_idempotent -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_removes_an_existing_marker -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_forbidden -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_method_rejected -=== RUN TestReplicateDeleteMarkerTargetSemantics/purge_unavailable ---- PASS: TestReplicateDeleteMarkerTargetSemantics (0.15s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/existing_marker_is_idempotent (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_removes_an_existing_marker (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_forbidden (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_method_rejected (0.00s) - --- PASS: TestReplicateDeleteMarkerTargetSemantics/purge_unavailable (0.15s) -=== RUN TestReplicationMRFMarkerRecovery -=== RUN TestReplicationMRFMarkerRecovery/canonical - replication-delete-mrf_test.go:485: ErasureSD: canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -goroutine 6994 [running]: -runtime/debug.Stack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:26 +0x64 -runtime/debug.PrintStack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:18 +0x1c -github.com/minio/minio/internal/ioutil.SafeClose[...](...) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/ioutil/ioutil.go:470 -github.com/minio/minio/internal/logger/target/http.(*Target).Cancel(0x132a6cb8d520) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/target/http/http.go:630 +0x74 -created by github.com/minio/minio/internal/logger.cancelTargets in goroutine 6639 - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/targets.go:213 +0x28 - replication-delete-mrf_test.go:485: Erasure: canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -goroutine 10069 [running]: -runtime/debug.Stack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:26 +0x64 -runtime/debug.PrintStack() - /opt/homebrew/Cellar/go/1.27.1/libexec/src/runtime/debug/stack.go:18 +0x1c -github.com/minio/minio/internal/ioutil.SafeClose[...](...) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/ioutil/ioutil.go:470 -github.com/minio/minio/internal/logger/target/http.(*Target).Cancel(0x132a6bf75a00) - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/target/http/http.go:630 +0x74 -created by github.com/minio/minio/internal/logger.cancelTargets in goroutine 6639 - /Users/vonng/.codex/worktrees/aa3f/silo/internal/logger/targets.go:213 +0x28 -=== RUN TestReplicationMRFMarkerRecovery/lock-failure - replication-delete-mrf_test.go:485: ErasureSD: lock-failure recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lock-failure recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing#01 -=== RUN TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error#01 -=== RUN TestReplicationMRFMarkerRecovery/legacy - replication-delete-mrf_test.go:485: ErasureSD: legacy recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: legacy recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/unrecorded-purge - replication-delete-mrf_test.go:485: ErasureSD: unrecorded-purge recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: unrecorded-purge recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/two-targets - replication-delete-mrf_test.go:485: ErasureSD: two-targets recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: two-targets recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/two-targets-offline - replication-delete-mrf_test.go:485: ErasureSD: two-targets-offline recovered; 2 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: two-targets-offline recovered; 2 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/replica-source - replication-delete-mrf_test.go:485: ErasureSD: replica-source recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: replica-source recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/lost-reply-canonical - replication-delete-mrf_test.go:485: ErasureSD: lost-reply-canonical recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lost-reply-canonical recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/lost-reply-legacy - replication-delete-mrf_test.go:485: ErasureSD: lost-reply-legacy recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: lost-reply-legacy recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/creation - replication-delete-mrf_test.go:485: ErasureSD: creation recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: creation recovered; 1 target(s), persisted MRF, source/target metadata checked -=== RUN TestReplicationMRFMarkerRecovery/partial-creation-block -=== RUN TestReplicationMRFMarkerRecovery/retry-budget-and-scanner - replication-delete-mrf_test.go:485: ErasureSD: retry-budget-and-scanner recovered; 1 target(s), persisted MRF, source/target metadata checked - replication-delete-mrf_test.go:485: Erasure: retry-budget-and-scanner recovered; 1 target(s), persisted MRF, source/target metadata checked ---- PASS: TestReplicationMRFMarkerRecovery (8.35s) - --- PASS: TestReplicationMRFMarkerRecovery/canonical (0.36s) - --- PASS: TestReplicationMRFMarkerRecovery/lock-failure (0.38s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata (1.89s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/empty-info#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/not-a-marker#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-version#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-bucket#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/wrong-object#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/zero-modtime#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/missing#01 (0.11s) - --- PASS: TestReplicationMRFMarkerRecovery/invalid-MRF-metadata/read-error#01 (0.10s) - --- PASS: TestReplicationMRFMarkerRecovery/legacy (0.31s) - --- PASS: TestReplicationMRFMarkerRecovery/unrecorded-purge (0.50s) - --- PASS: TestReplicationMRFMarkerRecovery/two-targets (0.64s) - --- PASS: TestReplicationMRFMarkerRecovery/two-targets-offline (0.48s) - --- PASS: TestReplicationMRFMarkerRecovery/replica-source (0.48s) - --- PASS: TestReplicationMRFMarkerRecovery/lost-reply-canonical (0.99s) - --- PASS: TestReplicationMRFMarkerRecovery/lost-reply-legacy (0.72s) - --- PASS: TestReplicationMRFMarkerRecovery/creation (0.48s) - --- PASS: TestReplicationMRFMarkerRecovery/partial-creation-block (0.56s) - --- PASS: TestReplicationMRFMarkerRecovery/retry-budget-and-scanner (0.55s) -=== RUN TestReplicationDeleteQueueFullRetryBudget ---- PASS: TestReplicationDeleteQueueFullRetryBudget (0.00s) -=== RUN TestReplicateDeleteOperationExits -=== RUN TestReplicateDeleteOperationExits/marker-creation/pending-success -=== RUN TestReplicateDeleteOperationExits/marker-creation/completed-creation -=== RUN TestReplicateDeleteOperationExits/marker-creation/existing-marker -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/marker-creation/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-creation/head-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/marker-creation/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-creation/offline -=== RUN TestReplicateDeleteOperationExits/marker-creation/retry-failed -=== RUN TestReplicateDeleteOperationExits/marker-creation/purge-complete -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-success -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/marker-creation/resync-failure -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/offline -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/legacy-marker-purge/resync-failure -=== RUN TestReplicateDeleteOperationExits/marker-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/marker-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/marker-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/marker-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/marker-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/marker-purge/offline -=== RUN TestReplicateDeleteOperationExits/marker-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/marker-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/marker-purge/resync-failure -=== RUN TestReplicateDeleteOperationExits/object-purge/pending-success -=== RUN TestReplicateDeleteOperationExits/object-purge/completed-creation -=== RUN TestReplicateDeleteOperationExits/object-purge/existing-marker -=== RUN TestReplicateDeleteOperationExits/object-purge/head-read-quorum -=== RUN TestReplicateDeleteOperationExits/object-purge/replica-creation-status -=== RUN TestReplicateDeleteOperationExits/object-purge/head-unavailable -=== RUN TestReplicateDeleteOperationExits/object-purge/head-forbidden -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-forbidden -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-method-rejected -=== RUN TestReplicateDeleteOperationExits/object-purge/delete-unavailable -=== RUN TestReplicateDeleteOperationExits/object-purge/offline -=== RUN TestReplicateDeleteOperationExits/object-purge/retry-failed -=== RUN TestReplicateDeleteOperationExits/object-purge/purge-complete -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-success -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-already-purged -=== RUN TestReplicateDeleteOperationExits/object-purge/resync-failure ---- PASS: TestReplicateDeleteOperationExits (0.70s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-read-quorum (0.09s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-unavailable (0.19s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/delete-unavailable (0.15s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-creation/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/delete-unavailable (0.07s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/legacy-marker-purge/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/delete-unavailable (0.04s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/marker-purge/resync-failure (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/pending-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/completed-creation (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/existing-marker (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-read-quorum (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/replica-creation-status (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-unavailable (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/head-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-forbidden (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-method-rejected (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/delete-unavailable (0.11s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/offline (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/retry-failed (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/purge-complete (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-success (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-already-purged (0.00s) - --- PASS: TestReplicateDeleteOperationExits/object-purge/resync-failure (0.00s) -=== RUN TestReplicateDeletePurgeMissingTargetState ---- PASS: TestReplicateDeletePurgeMissingTargetState (0.00s) -=== RUN TestReplicationMRFDropsVisible -=== RUN TestReplicationMRFDropsVisible/bucket -=== RUN TestReplicationMRFDropsVisible/node ---- PASS: TestReplicationMRFDropsVisible (0.00s) - --- PASS: TestReplicationMRFDropsVisible/bucket (0.00s) - --- PASS: TestReplicationMRFDropsVisible/node (0.00s) -=== RUN TestReplicationObjectDeleteWorkerAffinity ---- PASS: TestReplicationObjectDeleteWorkerAffinity (0.00s) -=== RUN TestSiteResyncCancelState ---- PASS: TestSiteResyncCancelState (0.00s) -=== RUN TestResyncRecoveryOwnsLeaderContext -=== RUN TestResyncRecoveryOwnsLeaderContext/lose_leader_false -=== RUN TestResyncRecoveryOwnsLeaderContext/lose_leader_true ---- PASS: TestResyncRecoveryOwnsLeaderContext (0.00s) - --- PASS: TestResyncRecoveryOwnsLeaderContext/lose_leader_false (0.00s) - --- PASS: TestResyncRecoveryOwnsLeaderContext/lose_leader_true (0.00s) -=== RUN TestResyncCancelRouting ---- PASS: TestResyncCancelRouting (0.00s) -=== RUN TestResyncCancellationWinsFinalization ---- PASS: TestResyncCancellationWinsFinalization (0.00s) -=== RUN TestResyncCancelFullWorkerQueue ---- PASS: TestResyncCancelFullWorkerQueue (0.00s) -=== RUN TestResyncCancelBlockedWalkReceive ---- PASS: TestResyncCancelBlockedWalkReceive (0.00s) -=== RUN TestResyncCancelsOwnedWalkOnError ---- PASS: TestResyncCancelsOwnedWalkOnError (0.00s) -=== RUN TestReplicationTrustControlsInternalOptionsAndEvents ---- PASS: TestReplicationTrustControlsInternalOptionsAndEvents (0.00s) -PASS -ok github.com/minio/minio/cmd 12.620s -=== RUN TestReplicate -=== RUN TestReplicate/#00 -=== RUN TestReplicate/c1test -=== RUN TestReplicate/c1test#01 -=== RUN TestReplicate/c1test#02 -=== RUN TestReplicate/c1test#03 -=== RUN TestReplicate/c1test#04 -=== RUN TestReplicate/c1test#05 -=== RUN TestReplicate/c1test#06 -=== RUN TestReplicate/c1test#07 -=== RUN TestReplicate/c2test -=== RUN TestReplicate/c2test#01 -=== RUN TestReplicate/c2test#02 -=== RUN TestReplicate/c2test#03 -=== RUN TestReplicate/c2test#04 -=== RUN TestReplicate/c2test#05 -=== RUN TestReplicate/c2test#06 -=== RUN TestReplicate/xy/c3test -=== RUN TestReplicate/xyz/c3test -=== RUN TestReplicate/xyz/c3test#01 -=== RUN TestReplicate/xyz/c3test#02 -=== RUN TestReplicate/xyz/c3test#03 -=== RUN TestReplicate/xyz/c3test#04 -=== RUN TestReplicate/xy/c3test#01 -=== RUN TestReplicate/xyz/c3test#05 -=== RUN TestReplicate/xyz/c3test#06 -=== RUN TestReplicate/xyz/c3test#07 -=== RUN TestReplicate/abc/c3test -=== RUN TestReplicate/xy/c4test -=== RUN TestReplicate/xa/c4test -=== RUN TestReplicate/xyz/c4test -=== RUN TestReplicate/xyz/c4test#01 -=== RUN TestReplicate/xyz/c4test#02 -=== RUN TestReplicate/xyz/c4test#03 -=== RUN TestReplicate/abc/c4test -=== RUN TestReplicate/abc/c4test#01 -=== RUN TestReplicate/abc/c4test#02 -=== RUN TestReplicate/abc/c4test#03 -=== RUN TestReplicate/abc/c4test#04 -=== RUN TestReplicate/xy/c5test -=== RUN TestReplicate/xa/c5test ---- PASS: TestReplicate (0.00s) - --- PASS: TestReplicate/#00 (0.00s) - --- PASS: TestReplicate/c1test (0.00s) - --- PASS: TestReplicate/c1test#01 (0.00s) - --- PASS: TestReplicate/c1test#02 (0.00s) - --- PASS: TestReplicate/c1test#03 (0.00s) - --- PASS: TestReplicate/c1test#04 (0.00s) - --- PASS: TestReplicate/c1test#05 (0.00s) - --- PASS: TestReplicate/c1test#06 (0.00s) - --- PASS: TestReplicate/c1test#07 (0.00s) - --- PASS: TestReplicate/c2test (0.00s) - --- PASS: TestReplicate/c2test#01 (0.00s) - --- PASS: TestReplicate/c2test#02 (0.00s) - --- PASS: TestReplicate/c2test#03 (0.00s) - --- PASS: TestReplicate/c2test#04 (0.00s) - --- PASS: TestReplicate/c2test#05 (0.00s) - --- PASS: TestReplicate/c2test#06 (0.00s) - --- PASS: TestReplicate/xy/c3test (0.00s) - --- PASS: TestReplicate/xyz/c3test (0.00s) - --- PASS: TestReplicate/xyz/c3test#01 (0.00s) - --- PASS: TestReplicate/xyz/c3test#02 (0.00s) - --- PASS: TestReplicate/xyz/c3test#03 (0.00s) - --- PASS: TestReplicate/xyz/c3test#04 (0.00s) - --- PASS: TestReplicate/xy/c3test#01 (0.00s) - --- PASS: TestReplicate/xyz/c3test#05 (0.00s) - --- PASS: TestReplicate/xyz/c3test#06 (0.00s) - --- PASS: TestReplicate/xyz/c3test#07 (0.00s) - --- PASS: TestReplicate/abc/c3test (0.00s) - --- PASS: TestReplicate/xy/c4test (0.00s) - --- PASS: TestReplicate/xa/c4test (0.00s) - --- PASS: TestReplicate/xyz/c4test (0.00s) - --- PASS: TestReplicate/xyz/c4test#01 (0.00s) - --- PASS: TestReplicate/xyz/c4test#02 (0.00s) - --- PASS: TestReplicate/xyz/c4test#03 (0.00s) - --- PASS: TestReplicate/abc/c4test (0.00s) - --- PASS: TestReplicate/abc/c4test#01 (0.00s) - --- PASS: TestReplicate/abc/c4test#02 (0.00s) - --- PASS: TestReplicate/abc/c4test#03 (0.00s) - --- PASS: TestReplicate/abc/c4test#04 (0.00s) - --- PASS: TestReplicate/xy/c5test (0.00s) - --- PASS: TestReplicate/xa/c5test (0.00s) -PASS -ok github.com/minio/minio/internal/bucket/replication 0.497s diff --git a/docs/investigations/r6/verification/rebased-verification.json b/docs/investigations/r6/verification/rebased-verification.json deleted file mode 100644 index b96ab40d0..000000000 --- a/docs/investigations/r6/verification/rebased-verification.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "baseline": "cf381a7151ef25fc95ace5fedcd767fa19410de2", - "source_sha256": { - "cmd/bucket-replication.go": "999c2818a8980cbeb55cfcbc244840069fb44e042b4b4d6ef7668c2660ce31e0", - "cmd/bucket-replication-utils.go": "365641c760901641e8320cc5123697ab92a46f1add612048b991ed2d1cfad43b", - "cmd/replication-delete-marker_test.go": "d967787804d558ac6266b113228fdf4a4f9fcb7cab39138a4fb07558814ccca4", - "cmd/replication-delete-operation_test.go": "2888a04a543324776041316de2821f388d28c3c1a6f5d0031e5ac9d34f58d504", - "cmd/replication-delete-mrf_test.go": "5e160f7e19cbbb8cd5fa4e7ffd9cff9e09361b3fc4c5ee5ae61458f99c777781" - }, - "checks": [ - { - "name": "scope", - "command": [ - "go", - "test", - "-p", - "2", - "./cmd", - "./internal/bucket/replication", - "-run", - "TestReplication|TestReplicate|TestMRF|TestResync|TestSiteResync", - "-count=1", - "-v" - ], - "exit_code": 0, - "seconds": 38.805, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/rebased-scope.log", - "log_sha256": "5a038acd48f99ccac00646e490a51270a2e426d8b8b3c7841330d739d773f06d", - "finished_at_utc": "2026-09-15T16:26:02.042157+00:00" - }, - { - "name": "race", - "command": [ - "go", - "test", - "-race", - "-p", - "2", - "./cmd", - "-run", - "TestReplicateDelete|TestReplicationMRF|TestReplicationDeleteQueueFull", - "-count=1", - "-v" - ], - "exit_code": 0, - "seconds": 67.636, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/rebased-race.log", - "log_sha256": "1d4016ab2e69d795c9cab377aed980127a823b38d9a530101f8eb5fc470d920b", - "finished_at_utc": "2026-09-15T16:27:09.680875+00:00" - }, - { - "name": "build", - "command": [ - "go", - "build", - "-p", - "2", - "./..." - ], - "exit_code": 0, - "seconds": 25.522, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/rebased-build.log", - "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "finished_at_utc": "2026-09-15T16:27:35.203431+00:00" - }, - { - "name": "vet", - "command": [ - "go", - "vet", - "-p", - "2", - "./cmd", - "./internal/bucket/replication" - ], - "exit_code": 0, - "seconds": 3.399, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/rebased-vet.log", - "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "finished_at_utc": "2026-09-15T16:27:38.603261+00:00" - }, - { - "name": "lint", - "command": [ - "/Users/vonng/pgsty/silo/.bin/golangci/v2.13.1/golangci-lint", - "run", - "--build-tags", - "kqueue", - "--timeout=10m", - "--config", - "./.golangci.yml" - ], - "exit_code": 0, - "seconds": 114.749, - "log": "/Users/vonng/tmp/silo-r6-20260915-aa3f/rebased-lint.log", - "log_sha256": "e92606b0bf483111dff0a120c315ea165821348f31365020e2468a0059095c47", - "finished_at_utc": "2026-09-15T16:29:33.354263+00:00" - } - ], - "source_unchanged": true -} diff --git a/docs/investigations/r6/verification/rebased-vet.log b/docs/investigations/r6/verification/rebased-vet.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r7/README.md b/docs/investigations/r7/README.md deleted file mode 100644 index cde2af8cc..000000000 --- a/docs/investigations/r7/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# R7:可信复制 Content-Encoding 修复 - -## 交付摘要 - -生产修复只改 `cmd/handler-utils.go`:可信复制只恢复六个复制专用字段,保留调用方已规范化的普通元数据。采用 [PR #187](https://github.com/pgsty/silo/pull/187) 的生产逻辑,增加准确说明 Snowball 调用方的注释。PR 原作者:Mikhail Khadarenka;本地新增回归与调查记录由本任务提供。 - -- `aws-chunked`:对象元数据和 GET/HEAD 不包含 Content-Encoding。 -- `aws-chunked,gzip`:只保留 `gzip`,原始 gzip 字节不变。 -- `gzip`:保持原值与原字节。 -- 六个复制字段保留,包括空 multipart 标记和 SSE-C checksum;认证/权限门控保持原语义。 -- 普通提取删除的旧 unencrypted length/MD5 用户元数据不会被复制恢复阶段重新注入。 - -## 方案和真实 Opus 共识 - -- [调查与基线复现](research.md) -- [冻结方案 v1](plan-v1.md) 与 [逐项处置附录](plan-v1.dispositions.md) -- [最终共识](consensus.md):真实 Claude Code 2.1.270,两轮显式 `claude-opus-5 --effort max`;所有实际评审 assistant 消息均为 `claude-opus-5`。第二轮 `APPROVE`,阻断 0。 -- [首轮原文](review/opus-v1.md)、[第二轮原文](review/opus-v1-confirmation.md);相邻 metadata 文件记录模型、命令、源 SHA、方案/原文哈希与原始 JSONL 路径。 - -共识在产品代码修改前记录;Opus 审阅代码和方案,测试由本任务执行,二者分别留证。 - -## 最终实现复核与合并准备 - -用户随后追加授权:使用 Opus 5 Max 核实,确认无误后合并 main。已对提交 `4fcdf37ce656152b32ad0f615d47f5e3f9748c3a`(基线 `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`)执行新的独立实现复核,实际评审模型为 `claude-opus-5`,显式 `--effort max`。结论为 **APPROVE_WITH_NONBLOCKING_NOTES,阻塞 0**;适用 PR CI 实际通过后才可合并。 - -[实现复核原文](implementation-review/opus-implementation.md)、[身份与哈希](implementation-review/opus-implementation.metadata.json)、[逐项处置](implementation-review/dispositions.md) 分别记录审阅与执行方验证边界。本次补充仅为文档;生产代码与测试维持已审阅、已本地验证版本。 - -## 兼容性与边界 - -Snowball 无 PAX 的可信复制条目不再继承外层归档的 content-type/cache-control/用户元数据,与普通 Snowball 一致。外层的六个复制专用字段仍可按既有规则作用于已授权条目。相同 tar 的普通和 replica 写入已纳入条目元数据一致性回归。 - -现有精确 token 裁剪规则保持不变:`aws-chunked, gzip` 留下带前导空格的 ` gzip`;`gzip, aws-chunked` 中带空格的 token 仍不会被去掉。这两条记录现状的断言不代表它们已被修复。POST 表单低层元数据提取行为也保持原样。 - -旧对象不会因升级自动修正;普通 COPY 保留来源已有元数据。若权威来源仍受污染,后续对账可能继续认为目标不一致并再次选择元数据复制。先核实来源版本、再协调副本的操作提案见 [存量处理设计](stored-metadata-remediation.md)。本任务未扫描或改写现网对象。 - -## 复验命令 - -在有充足空闲比例的普通测试机器上,正式测试不需要容量 overlay: - -```sh -go test ./cmd -run '^Test(ExtractReplicationMetadata.*|APIReplicaContentEncoding|APISnowballReplicaContentEncoding)$' -count=1 -go test -race ./cmd -run '^Test(ExtractReplicationMetadata.*|APIReplicaContentEncoding|APISnowballReplicaContentEncoding|APISnowballReplicationTrustIsPerEntry|APISSECReplicaSkipsDestinationTransforms|APISSECMultipartReplicaRoundTripWithCompression)$' -count=1 -make verifiers -make build -``` - -本机实际命令见下述每次运行的 JSON;其中包含容量 overlay、并行度和使用的本地 golangci-lint 路径。 - -## 验证证据 - -完整命令、源文件/方案哈希、构建身份和检查结果汇总于 [verification.json](verification.json)。构建发生在本地提交前,二进制嵌入基线提交号;代码内容以验证清单中的文件哈希为准,不作为发布制品。 - -原始材料目录:`/Users/vonng/tmp/silo-r7-20260915-ad51/`。除原始发现阶段外,每次正式验证的 `.json` 记录命令、退出码、时间、日志哈希和四个代码文件的 SHA-256。 - -| 验证 | 状态与材料 | -| --- | --- | -| 原始 helper 基线 | `baseline.log`:裸/混合可信恢复失败,gzip 控制通过 | -| 原始 HTTP 基线 | `http-baseline-v2.log`:单盘及 16 盘,44 个控制通过,20 个已知缺陷失败 | -| 最终测试回退原始 helper | `exact-baseline-regression.{json,log}`:测试不变,只覆盖回基线产品文件;44 控制通过、36 预期失败(20 HTTP + 16 helper) | -| 修复后的定向测试 | `fixed-targeted.{json,log}`:9 个顶层测试、80 个具名子用例全部通过 | -| 既有 SSE 与信任边界 | `fixed-sse-trust.{json,log}`:SSE-C 单段/多段、SSE multipart trust、PUT/COPY 投毒、普通/复制权限、Snowball per-entry、默认桶加密、streaming trailer 等全部通过 | -| Race | `fixed-race.{json,log}`:新增 helper/HTTP/Snowball、既有 Snowball per-entry 与 SSE-C 单段/多段全部通过 | -| 仓库 verifiers | `verifiers.{json,log}`:make verifiers 通过,golangci-lint 0 issues,生成文件与兼容标识检查通过;typos 未安装,按 Makefile 跳过 | -| 构建 | `build.{json,log}`:make build 通过;本地 silo --version 已核对,二进制身份见 verification.json | - -### 本机容量条件 - -未调整的 HTTP 夹具返回 507 / XMinioStorageFull,原始日志为 `http-baseline-unadapted.log`。宿主 APFS 接近满盘,触发相对空闲阈值。HTTP/既有 SSE/race 验证使用临时 Go overlay 复用仓库的 `tagTestCapacityDisk`,只改变 API 测试夹具看到的容量比率,实际对象和元数据仍读写测试磁盘。该临时文件在仓库外,不进入交付;生产容量策略没有变化。 - -证据为本机认证请求处理链路及实际存储、读取和既有 SSE 往返,不是双站点调度器、进程重启、网络故障或线上验收。 - -## 初始交付状态 - -初始提交形成时,研究、真实 Opus 方案共识、本地实现与验证均完成,结果保存在 `codex/r7-replication-content-encoding` 分支;当时尚未推送或合并。随后按用户追加授权进行上述最终实现复核与合并准备。实际 PR、CI 与主干合并状态以对应远端记录和 `/Users/vonng/tmp/silo-r7-merge-20260916-ad51/` 中的执行回执为准。 diff --git a/docs/investigations/r7/consensus.md b/docs/investigations/r7/consensus.md deleted file mode 100644 index 667f1c80e..000000000 --- a/docs/investigations/r7/consensus.md +++ /dev/null @@ -1,25 +0,0 @@ -# R7 最终方案共识 - -记录时间:2026-09-15T15:53:09.813317+00:00。此记录写入时产品代码仍为基线,只有调查文件和仓库外的临时测试。 - -## 同一版方案 - -- 基线:`9ebe81c1b3611f9cc73e676b5b741c2be62c467a`。 -- [plan-v1.md](plan-v1.md):`7af5705ebbfb0a375956d38dba059095dc16e24558290b1bd35a6ce85b9e1f96`。 -- [plan-v1.dispositions.md](plan-v1.dispositions.md):`d32d30f8a420f904da6ee039f0461d6281e7da44bd805d416b4970d071f0af32`。 -- Codex 重新计算并确认上述两个哈希未变。Opus 只读源码,明确未计算哈希、未运行测试。 - -## 实际讨论结果 - -两轮均使用 Claude Code 2.1.270,显式 `--model claude-opus-5 --effort max`。原始记录中两轮所有评审 assistant 消息均为 `claude-opus-5`;辅助模型用量与主评审模型分开记录。 - -1. [首轮独立评审](review/opus-v1.md):APPROVE_WITH_NONBLOCKING_NOTES,阻断 0,9 项非阻断意见。 -2. Codex 逐项核验:采纳验证/文档建议;纠正 N1 的单包权限比较方式、收窄 N5 的重试风险表述、以源码反驳 N6 的容量适配器不存在判断。详见绑定处置附录。 -3. [第二轮确认](review/opus-v1-confirmation.md):**APPROVE,阻断 0**;Opus 明确接受 N1/N5 的纠正,撤回 N6 的事实判断,并同意这两个哈希所标识的 v1 组合直接进入实现。 -4. Codex 同意该方案及全部最终处置。没有剩余阻断分歧;共识完成,现在开始本地实现与验证。 - -评审原始 JSONL、stderr、实际模型、命令、耗时及输出哈希均由 `review/*.metadata.json` 指向 `/Users/vonng/tmp/silo-r7-20260915-ad51/` 中的原始记录。首轮 Claude plan 模式尝试写自己的 plan 文件但 Write 工具被禁用,最后只以文本返回评审;未写产品文件。没有把失败、限流或别的模型当成通过。 - -## 授权及证据边界 - -共识是源代码与修复方案的认可。实现、测试、合并、发布和部署仍分别记录。本地常规修复已获工作流授权,无需再次询问;主干合并、远端发布、部署和现网存量改写不在此次范围。 diff --git a/docs/investigations/r7/implementation-review/dispositions.md b/docs/investigations/r7/implementation-review/dispositions.md deleted file mode 100644 index 5ede20507..000000000 --- a/docs/investigations/r7/implementation-review/dispositions.md +++ /dev/null @@ -1,27 +0,0 @@ -# R7 最终实现复核意见处置 - -## 审阅身份与范围 - -用户追加指令:使用 Opus 5 Max 核实,确认无误后合并 main。该指令授权此次推送、PR 与主干合并。 - -候选提交 `4fcdf37ce656152b32ad0f615d47f5e3f9748c3a`,基线 `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`。实际评审 assistant 消息均为 `claude-opus-5`,命令显式指定 `--effort max`;辅助 Haiku 用量与评审模型分开记录。结果为 **APPROVE_WITH_NONBLOCKING_NOTES,阻塞 0**。 - -完整独立意见见 [Opus 原文](opus-implementation.md),模型、源文件、diff 和原始日志哈希见 [metadata](opus-implementation.metadata.json)。原始 JSONL 保存在 `/Users/vonng/tmp/silo-r7-merge-20260916-ad51/opus-implementation.jsonl`。 - -## 七项非阻塞意见 - -| 意见 | 处置与证据 | -| --- | --- | -| 1. 必须等待本分支自己的真实 CI | 接受。合并前核验适用检查全部通过,尤其是不使用本机容量 overlay 的完整 `cmd` 测试。现有 main CI 通过不能替代候选 PR 的 CI。 | -| 2. 自检提交对象、署名和 diff | 已核对实际提交:树 `d5cc904bf2a8b57aea1aac53999b5007522279fc`,包含 Mikhail Khadarenka 的 Co-authored-by 与提交作者匹配的 DCO Signed-off-by。相对基线仅一个生产文件、三个测试文件及调查文档变化;四个 Go 文件哈希与通过的验证日志一致。 | -| 3. POST 表单路径仍未归一化 | 确认是原有低层调用路径,本修复不改变它。作为独立后续研究项记录;没有把本次结果宣称为所有上传方式的编码归一化。 | -| 4. 普通请求还可断言六个 wire 字段不泄漏 | 现有 `TestExtractMetadataHeaders` 已输入全部六个 wire 字段,仅期望 `content-type`,并用 `reflect.DeepEqual` 比较完整 metadata map,任何 wire 或 internal 字段泄漏都会失败。该测试已包含在通过的 `fixed-targeted` 验证中;无需增加重复断言。新增 canonical/lowercase 矩阵进一步覆盖恢复行为。 | -| 5. 可补充 Snowball untrusted-marker 对照 | 保留为可选增强。当前测试含同一归档 ordinary/replica 元数据一致性,以及既有 Snowball 逐条目权限回归;此次调用点与授权门控未改。 | -| 6. GET/HEAD 使用 getPutObjectURL 命名不够直观 | 确认 URL 构造等价,不影响方法、签名或断言。无需为命名改动已经通过的测试。 | -| 7. PR 描述必须说明存量与来源污染限制 | 接受并写入 PR 描述。旧对象不会自动修复;来源仍受污染时,后续 heal/resync/比较可能反复选择元数据复制。参见既有存量处理设计;本次没有现网扫描或改写。 | - -## 合并条件 - -此次处置只增加审阅文档,生产与测试代码维持 Opus 审阅版本。推送前再次核对源文件哈希、DCO 和 main 基线;main 若前进,先检查集成增量,相关行为改变时重新验证和评审。通过正常 PR 合并流程保留 #187 作者署名,不修改贡献者分支。 - -本记录形成时尚未发布本分支的 PR,不能作为 CI 通过或已合并的证据。实际 PR、CI 与合并回执另行核验并保存于 `/Users/vonng/tmp/silo-r7-merge-20260916-ad51/`。发布、部署、线上对象改写和双站点故障验收不在此次执行范围。 diff --git a/docs/investigations/r7/implementation-review/opus-implementation.md b/docs/investigations/r7/implementation-review/opus-implementation.md deleted file mode 100644 index 0f2350e95..000000000 --- a/docs/investigations/r7/implementation-review/opus-implementation.md +++ /dev/null @@ -1,82 +0,0 @@ -## R7 实现与可合并性独立复审(Opus 5,只读会话) - -### 1. 审阅对象 - -| 项 | 值 | -| --- | --- | -| 候选提交 | `4fcdf37ce656152b32ad0f615d47f5e3f9748c3a` | -| 候选树 | `d5cc904bf2a8b57aea1aac53999b5007522279fc` | -| 现场 main/base | `9ebe81c1b3611f9cc73e676b5b741c2be62c467a` | -| 审阅 diff | `/Users/vonng/tmp/silo-r7-merge-20260916-ad51/reviewed-code.diff` | -| diff SHA-256(按调用方声明采信) | `5af32dc2cb4acc34ac91a3e48c5decfcac0d25c70bfa7d6399934c66f629df2a` | -| 原始 PR | #187,head `b8f2fdde41dff3dc3b8db669c1d42d30ca5c1d3d`,未合并 | - -本会话无 shell,未运行任何测试、未计算任何哈希、未做任何网络操作,未修改任何文件。 - -### 2. 结论 - -**APPROVE_WITH_NONBLOCKING_NOTES。阻塞问题 0。** - -**这份代码在其自身 PR 的 CI 实际变绿后可以合入 main。** 合并前提是 CI 真实通过,而不是"预期会通过"(理由见 §5.1)。 - -### 3. 我实际核验的内容(非采信) - -逐行对读了 diff 与工作区当前源码,确认两者一致(`cmd/handler-utils.go:247-308` 就是 diff 后的内容;三个测试文件的 grep 行号与 diff 偏移量精确对应,如 `replication-content-encoding_test.go:108` = diff:246−138,`replication-metadata_test.go:68/90` = diff:488/510−420)。 - -**3.1 等价性(普通路径零行为变化)** -基线 `allowReplication=false` 分支是"命中 supportedHeader → 若属复制映射则 continue";新代码是"若属复制映射则 continue → 再查表写入"。对 `nv` 的查找、`strings.Join(value, ",")` 的拼接、user-metadata 前缀循环全部逐字保留(`handler-utils.go:262-281`)。普通提取路径字节级等价。 - -**3.2 六个 SSE 字段与空标记** -`supportedHeaders` 的后六项(`handler-utils.go:86-91`)与 `replicationToInternalHeaders` 的键集完全相同(`:106-114`),且六→六为单射(含 `ReplicationSsecChecksumHeader` 的恒等映射,`bucket-replication.go:81`),因此由"遍历 slice"改为"遍历 map"后迭代顺序无关,无覆盖歧义。空标记 `X-Minio-Replication-Encrypted-Multipart: ""` 走 `ok=true` 分支、`Join([""])==""`,与基线一致,且 `internal/crypto` 按键存在性消费——测试 `replication-metadata_test.go:82-86` 的 want 映射显式固定了这一点。 - -**3.3 信任边界(PUT/COPY/MPU/Snowball)** -diff **未触碰任何调用点与信任判定**。我复核了全部五处调用与门控: -- PUT:`object-handlers.go:2199` 先提取 → `:2263` `evaluateReplicationTrust` → `:2278-2280` `applyReplicationTrust` → `:2281-2282` 仅 `replicaTrusted` 恢复。注意提取发生在剥离之前,因此普通提取器**必须**无条件跳过复制专用头——新代码正是无条件 `continue`(`:263-265`),这道纵深防御被完整保留。 -- COPY:`object-handlers.go:1144-1152`,仅 REPLACE 分支且 `allowReplication` 为真时恢复;COPY 指令分支语义不变。 -- MPU:`object-multipart-handlers.go:233` + `:246`,仅初始化阶段恢复;Part/Complete 不再提取元数据,测试里的 `Content-Encoding: br` 因此是未来回归护栏。 -- Snowball:`replication-trust.go:78-90` 的整包判定 + `object-handlers.go:2786-2793` 的逐条目 `ReplicateObjectAction` 复核未变;`replicationRequestHeaders`(`replication-trust.go:96-112`)仍覆盖六个 wire 头与 REPLICA 状态。 -无新增投毒面。 - -**3.4 Snowball 普通/replica parity(最实质的行为变化,判定为修复而非回归)** -`object-handlers.go:2802-2804` 的 `metadata` 只含 storage class。基线在 `:2836` 的恢复调用会把**外层 archive 请求**的 supportedHeaders + 用户元数据整体灌进每个条目(普通条目则完全没有),补丁后只剩六个映射;PAX 分支 `:2864-2872` 同理不再用原始 wire 值覆盖 `extractMetadata` 已归一化的 `m`。我另行确认仓库内**没有生产代码发送** `X-Amz-Meta-Snowball-Auto-Extract`(只有 `api-router.go:391` 接收),即不存在依赖旧继承行为的内部生产者。 - -**3.5 空格 token 行为** -`trimAwsChunkedContentEncoding`(`handler-utils.go:367-378`)按 `,` 切分后做**精确等值**比较、不做 TrimSpace。因此 `"aws-chunked, gzip" → " gzip"`(保留前导空格)、`"gzip, aws-chunked" → "gzip, aws-chunked"`(整串不变)。测试 `replication-metadata_test.go:44-51` 的两条期望与代码一致,属于**现状固化**,不得对外宣称为本次修复。 - -**3.6 原始普通元数据的删除(GHSA 相关,正面收益)** -`extractMetadata:219-223` 删除 `X-Amz-Meta-X-Amz-Unencrypted-Content-Length/-Md5`。基线的恢复函数会通过 `x-amz-meta-` 前缀循环把它们**重新注入**,这是对该 advisory 缓解的实际回退(仅限可信 replica 写)。补丁消除了该路径,测试在 canonical/lowercase 两种写法下都做了断言(`:100-105`)。 - -**3.7 GET/HEAD 与原始字节** -`ObjectInfo.ContentEncoding` 来自 `fi.Metadata["content-encoding"]`(`erasure-metadata.go:138`),`setObjectHeaders` 仅在非空时下发(`api-headers.go:129-131`)。所以测试同时断言"落盘 UserDefined 无该键"和"响应头不存在该键"是有意义且互相独立的。GET 分支比较原始字节,replica/gzip 用例写入的是真实 gzip 字节。 - -**3.8 签名与请求体** -`replicaEncodingStream` 的顺序正确:`newTestStreamingRequest` → 设置全部头 → `signStreamingRequest` → `assembleStreamingChunks`,chunk 签名逐块校验;非 chunked 分支 `newTestSignedRequestV4` 对 payload 计算 `x-amz-content-sha256` 并走 `authTypeSigned` 校验。测试确实经过签名验证链路,不是绕过。无权 replica 用例断言 XML `Code == AccessDenied` 并复核对象未被创建,能区分"签名失败"与"授权拒绝"。 - -**3.9 生产代码与 PR #187 的关系** -逐行比对 `pr187.diff` 与候选 diff:`cmd/handler-utils.go` 与 `cmd/handler-utils_test.go` **完全一致**,唯一差异是恢复函数的三行注释(pr187.diff:76-78 vs 候选:76-78),候选版补充了 Snowball 语义。"仅澄清注释"的说法属实。`commit-message.txt` 含 `Co-authored-by: Mikhail Khadarenka` 与 PR #187 归属声明。 - -**3.10 容量夹具是否削弱证据:不削弱** -`capacity-overlay.json` 只替换 `cmd/test-utils_test.go`;夹具唯一的功能性改动是 `ExecObjectLayerAPITest` 开头用仓库**既有**的 `tagTestCapacityDisk`(`cmd/erasure-server-pool-tags_test.go:258-264`,`DiskInfo` 返回 `Total=Free, Used=0`)包装 set 磁盘。对象字节与 xl.meta 仍写真实临时盘。未适配时的 `507/XMinioStorageFull` 原始日志保留(`http-baseline-unadapted.log`)。该文件在仓库外,**不在交付 diff 中**。 -反事实同样成立:`exact-baseline-overlay.json` 只额外把**生产文件**换成 `baseline-handler-utils.go`(我核对该文件确含 `extractMetadataFromMimeWithReplication`/`allowReplication` 布尔开关),测试文件一字未改。失败点精确落在 `replica/bare`、`replica/mixed`、全部 8 个 Snowball 用例与全部 16 个 helper 子用例,`ordinary`/`untrusted-marker`/`gzip`/`unauthorized-replica` 全通过——44 通过 / 36 失败与声明吻合。 - -### 4. 我采信而未独立验证的部分 - -- 四个源文件与各日志的 SHA-256、diff SHA-256、提交/树对象哈希(无 shell,无法计算)。 -- 所有测试的**执行事实**:`fixed-targeted`(80 叶子)、`fixed-sse-trust`、`fixed-race`、`make verifiers`、`make build` 的通过是读日志所得(我确认了 `fixed-targeted.log:179-180` 的 `PASS/ok`、`fixed-race.log:209-210`、`fixed-sse-trust.log:125-126`,以及四个 fixed 日志中无 `--- FAIL`),但这些进程不是本会话运行的。 -- 提交对象实际携带的 author/trailer(`commit-message.txt` 内容正确,但我无法确认它就是 `4fcdf37ce` 的提交信息)。 -- PR #187 远端当前状态(读取的是本地缓存 `pr187.json` / `pr187.diff`)。 -- 无任何两站点调度器 / 进程重启 / 网络故障 / 线上验收;本次结论只覆盖本机认证 handler 接收链路与既有 SSE 回归。 - -### 5. 非阻塞意见 - -1. **CI 是真正的门,不是形式。** 这两个 HTTP 级测试从未在"无容量 overlay"的环境里跑过(本机盘满,未适配时直接 507)。CI 机器盘空间正常时不需要 overlay,预期可过,但这一点**本地无证据**。请等该分支自己的 CI 变绿再合,不要用本地日志替代。 -2. **提交对象自检**(推送前,一条命令):`git log -1 --format='%H %T %an %ae%n%b' 4fcdf37ce` 与 `git diff --stat 9ebe81c1b..4fcdf37ce`,确认树哈希、`Co-authored-by` 与"只动 2 个产品/测试文件 + 2 个新测试 + docs"。 -3. **POST 表单路径仍未归一化**:`bucket-handlers.go:1262` 直接调用低层 `extractMetadataFromMime`,不走 `extractMetadata` 的 `aws-chunked` 裁剪与默认 content-type。本补丁按 N9 刻意不动它,但这意味着 POST policy 上传仍可能落盘原始 `aws-chunked`。建议单独开 issue,不要在本 PR 顺手统一。 -4. **测试可加一条断言**:`replication-metadata_test.go:68-72` 只断言普通路径不出现六个 **internal** 键;再断言六个 **wire** 键名也不在 metadata 中,可完整封死这一面(当前实现确实不会写入,属加固)。 -5. **Snowball 可选对照**:现有 parity 只比 ordinary vs replica,可再加一条 untrusted-marker archive 对照(Opus v1 的可选建议,非必须)。 -6. **小瑕疵**:`assertReplicaEncodingObject` 用 `getPutObjectURL` 构造 GET/HEAD 目标,功能等价但读起来别扭,`getGetObjectURL` 更清楚。 -7. **存量限制需在 PR 描述里写明**:本补丁只阻止目的端再次污染,不修复既有对象;若来源端仍存错误编码,`bucket-replication.go` 的逐字符串比较会在后续 heal/resync/比较时反复选择元数据复制。措辞按 N5 收窄("可能反复选中",不是"不间断热循环")。设计见 `stored-metadata-remediation.md`,本次不授权任何现网扫描或改写。 - -### 6. 明确许可声明 - -**本次审阅的这份代码(候选 `4fcdf37ce…`,基线 `9ebe81c1b…`,即上述 diff)在其 PR 的适用 CI 实际通过后,允许合入 main。** 不得修改原贡献者的 #187 分支;不授权发布、部署或线上对象改写。若在此期间 main 前进,需重新查看集成增量,并在相关行为改变时重跑相应测试并重新评审。 diff --git a/docs/investigations/r7/implementation-review/opus-implementation.metadata.json b/docs/investigations/r7/implementation-review/opus-implementation.metadata.json deleted file mode 100644 index addb28e6b..000000000 --- a/docs/investigations/r7/implementation-review/opus-implementation.metadata.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "candidate_commit": "4fcdf37ce656152b32ad0f615d47f5e3f9748c3a", - "candidate_tree": "d5cc904bf2a8b57aea1aac53999b5007522279fc", - "code_diff_sha256": "5af32dc2cb4acc34ac91a3e48c5decfcac0d25c70bfa7d6399934c66f629df2a", - "source_sha256": { - "cmd/handler-utils.go": "76d6f98a8c9b04fcf1ae7c79d5832396bbf234bd5b5efc32a7f80dec64179b1a", - "cmd/handler-utils_test.go": "f8a53dd29170280eb007c4e43773187710f027e2aa49aadda91d2777bd0e034e", - "cmd/replication-content-encoding_test.go": "c71c4cf79a3ea6336febaf14bcb8ddfd68fcbef7625ff18830c14e9143749ee7", - "cmd/replication-metadata_test.go": "2cd3c45687d7333a9466906a278d7953fe4eeb008e07e2c7e2d167480c8b64bb" - }, - "prior_validation_log_hashes_verified": true, - "requested_model": "claude-opus-5", - "requested_effort": "max", - "cli_version": "2.1.270", - "started_at": "2026-09-15T16:03:50.465537+00:00", - "raw_output": "/Users/vonng/tmp/silo-r7-merge-20260916-ad51/opus-implementation.jsonl", - "stderr": "/Users/vonng/tmp/silo-r7-merge-20260916-ad51/opus-implementation.stderr.log", - "status": "completed", - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --output-format stream-json --verbose --add-dir /Users/vonng/tmp/silo-r7-20260915-ad51 --add-dir /Users/vonng/tmp/silo-r7-merge-20260916-ad51", - "exit_code": 0, - "actual_review_models": [ - "claude-opus-5" - ], - "auxiliary_usage_models": [ - "claude-haiku-4-5-20251001" - ], - "session_id": "80cab374-7884-47ab-a83f-4cc6d4aa4303", - "duration_ms": 179439, - "num_turns": 40, - "verdict": "APPROVE_WITH_NONBLOCKING_NOTES", - "blocking_issues": 0, - "merge_condition": "The exact candidate must pass its own applicable PR CI. Inspect main integration changes before merge.", - "raw_sha256": "d77c1f1ad308640aefa035a6ebefbe0e15f376613561aa38aa8890cded320485", - "stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "review_sha256": "cf0f5d4c19c0477b3742dd2de24387886cdd77123b1ca8186768319674e41573", - "prompt_sha256": "c95202843857c6b4d079820189473119d8911b09715d012fae4e2862c9a2f780", - "permission_denials": [] -} diff --git a/docs/investigations/r7/implementation-review/opus-implementation.prompt.md b/docs/investigations/r7/implementation-review/opus-implementation.prompt.md deleted file mode 100644 index a4e3e84e8..000000000 --- a/docs/investigations/r7/implementation-review/opus-implementation.prompt.md +++ /dev/null @@ -1,17 +0,0 @@ -Perform a new independent IMPLEMENTATION and merge-readiness review for R7, not another plan approval. User explicitly requests real "opus 5 max" verification and authorizes merging main only if correct. Do not edit files or attempt Write, do not make any network mutations or claim tests you did not run. Return review text only. - -Exact candidate commit: 4fcdf37ce656152b32ad0f615d47f5e3f9748c3a -Exact live main/base fetched now: 9ebe81c1b3611f9cc73e676b5b741c2be62c467a -Candidate git tree: d5cc904bf2a8b57aea1aac53999b5007522279fc -Reviewed implementation diff: /Users/vonng/tmp/silo-r7-merge-20260916-ad51/reviewed-code.diff -Diff SHA-256: 5af32dc2cb4acc34ac91a3e48c5decfcac0d25c70bfa7d6399934c66f629df2a -Source hashes and full local test evidence: /Users/vonng/.codex/worktrees/ad51/silo/docs/investigations/r7/verification.json -Caller just recomputed all four code hashes and previous validation log hashes: all match. No source changes since tests passed. The prior evidence includes 80 focused cases, existing SSE-C and trust/poisoning/trailer/Snowball regressions, race, make verifiers and build. Capacity adaptation is explicit, temporary, and real storage I/O still runs. Exact baseline counterfactual: same final tests, old product extractor, 44 controls pass/36 fail. Review logs are available at /Users/vonng/tmp/silo-r7-20260915-ad51/ and permitted via add-dir. - -Read the complete actual diff and relevant current call sites/source/tests. Prior agreed plan: docs/investigations/r7/plan-v1.md; prior dispositions and final Opus agreement: plan-v1.dispositions.md and review/opus-v1-confirmation.md. Treat them as context, not a reason to rubber-stamp implementation. Production change adopts PR #187, with only a clarified Snowball comment; candidate also adds stronger tests and documents. PR #187 head remains b8f2fdde41dff3dc3b8db669c1d42d30ca5c1d3d and unmerged; its checks were awaiting outside-contributor workflow approval, not successful CI. - -Assess correctness, unintended regressions, trust boundary (PUT/COPY/MPU/Snowball), six SSE metadata fields including empty marker/checksum, request-body signature handling, GET/HEAD/raw-byte expectations, whether tests actually exercise promised paths, and whether capacity fixtures or evidence fail to justify the narrow fix. Inspect especially Snowball ordinary-vs-replica parity, source polluted-metadata limitation, exact token whitespace behavior, and raw ordinary metadata redaction. Do not broaden to R4/R5/R8 or claim full distributed testing. Identify real merge blockers with file/line/evidence and smallest repair; separate optional improvements. - -Intended integration: publish this complete reviewed local branch to a normal pgsty/silo PR, wait for its applicable CI, and merge into main. This includes the originally credited PR #187 implementation plus local tests/docs. Do not mutate the original contributor's branch. If main advances first, inspect integration delta, revalidate necessary tests and review again if relevant behavior changed. Merely adding this review report changes documentation only. Merging main is now explicitly authorized by the user, superseding the earlier workflow's no-merge boundary; no release/deploy/live object rewrite is authorized. - -Return in Chinese: exact reviewed candidate/base/hash; APPROVE / APPROVE_WITH_NONBLOCKING_NOTES / REQUEST_CHANGES; each blocking finding and optional note; what you inspected vs trusted as supplied test evidence; and explicit whether this exact code may merge once CI is verified. Be concise but evidence-based. No model substitution, no fake test execution. diff --git a/docs/investigations/r7/plan-v1.dispositions.md b/docs/investigations/r7/plan-v1.dispositions.md deleted file mode 100644 index c5baa5a30..000000000 --- a/docs/investigations/r7/plan-v1.dispositions.md +++ /dev/null @@ -1,23 +0,0 @@ -# R7 v1 评审意见处置与验收补充 - -- 冻结方案仍为 `plan-v1.md`,SHA-256 `7af5705ebbfb0a375956d38dba059095dc16e24558290b1bd35a6ce85b9e1f96`。 -- Opus 首轮:`APPROVE_WITH_NONBLOCKING_NOTES`,阻断 0;原始评审见 `review/opus-v1.md`。 -- 本文件只澄清兼容边界与验收,不改变生产补丁范围;作为 v1 的绑定附录交给 Opus 再确认。确认前仍不修改产品代码。 - -| 意见 | Codex 处置与证据 | -| --- | --- | -| N1:Snowball 无 PAX 行为变化与 parity | 接受。可信 replica 的无 PAX 条目不再继承外层 archive 的 ordinary content-type/content-encoding/cache-control/user metadata;这一可见变化使它与普通 Snowball 一致,纳入报告。六个复制专用字段仍能由外层传给每个已授权条目,PAX 的专用字段可按现有次序覆盖。parity 测试用相同 tar 分别执行 ordinary 与 replica 请求并比较条目元数据(排除 replica 状态/时间/ETag);不能按建议字面在一个 REPLICA 请求中混入无 ReplicateObject 权限的条目并期待它成功,因为 `object-handlers.go:2788-2791` 会拒绝该条目。现有 per-entry 权限回归另行保持。 | -| N2:HTTP baseline 回归护栏 | 接受,已实测。`/Users/vonng/tmp/silo-r7-20260915-ad51/http-baseline-v2.log` 包含单盘及 16 盘的真实 streaming PUT -> ObjectInfo -> GET/HEAD 失败,同期 ordinary/gzip 控制通过;还覆盖 COPY/multipart/Snowball。64 个叶子:44 控制通过,20 缺陷失败。 | -| N3:签名前注入所有头 | 接受,已落实在临时测试 `replicaEncodingStream`:先 newTestStreamingRequest、设置所有头,再 signStreamingRequest 和 assembleStreamingChunks。拒绝测试还应断言 XML 错误码为 AccessDenied,区分签名失败。 | -| N4:空格 token 现状 | 接受。helper 增加 `aws-chunked, gzip -> " gzip"`、`gzip, aws-chunked -> "gzip, aws-chunked"`,仅固定现有精确 token 规则,生产 normalizer 不改。后一例属于既有 token 语法限制,不能宣传为本次已修复。 | -| N5:历史污染来源反复不一致 | 接受风险并限定措辞。`bucket-replication.go:987-997` 的逐字符串比较可让仍有错误编码的来源与修正后目的对象持续不一致;在再次 heal/resync/比较时可再次选择 metadata 复制。源码证据不单独证明一个不间断热循环。存量提案应先确认并处理权威源版本,再协调各副本;记录重复元数据复制/不一致,而不是只修目的端。自动清理历史来源不进入本次生产补丁。 | -| N6:容量 adapter 不存在 | 不采纳此事实判断,但接受“临时调整不入交付”的要求。请直接读取基线 `cmd/erasure-server-pool-tags_test.go:258-265`:`type tagTestCapacityDisk struct{ StorageAPI }` 的 DiskInfo 返回当前 Free 作为 Total、Used=0;该文件 :131 有现有调用。此前按字符串 adapter 搜索漏掉了该类型。临时 `capacity-test-utils_test.go` 仅复用它来包装 API 夹具;原始 507 和适配日志均保留,产品容量策略不改。 | -| N7:map 迭代等价性 | 接受。现有六个 wire key 到六个 internal key 为单射,遍历顺序无关;重复不同大小写头的 canonical map 碰撞行为沿用基线,不引入新的解析规则。 | -| N8:被删除的旧加密用户字段 | 接受,根因和测试均包含 `X-Amz-Meta-X-Amz-Unencrypted-Content-Length/-Md5` 的再次注入。历史污染可能继续从来源传来;此次目标端普通提取删除后不会恢复这些字段。helper 对 canonical/lowercase 均断言不存在。 | -| N9:POST 表单路径 | 接受边界说明。POST 表单直接调用低层 extractMetadataFromMime,原本就不执行 extractMetadata 的完整归一化;本补丁保持它的现状,不顺带统一逻辑。 | - -## 最终验收范围补充 - -正式回归保留真实分块签名、有效 gzip 字节、GET 原始字节比较;没有两站点调度器/进程重启/网络故障验收时,就只报告本地复制接收链路和既有 SSE 测试的结论。 - -存量修复有单独可审阅文件 `stored-metadata-remediation.md`,须按 N5 增补“权威来源优先”的顺序;没有扫描/改写现网对象的授权或动作。 diff --git a/docs/investigations/r7/plan-v1.md b/docs/investigations/r7/plan-v1.md deleted file mode 100644 index 8466f0f4e..000000000 --- a/docs/investigations/r7/plan-v1.md +++ /dev/null @@ -1,71 +0,0 @@ -# R7 plan v1: preserve normalized replica object metadata - -## Frozen scope and source - -- Date: 2026-09-15. Worktree: `/Users/vonng/.codex/worktrees/ad51/silo`. -- Local branch: `codex/r7-replication-content-encoding`. -- Baseline: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`, also returned by current `gh api repos/pgsty/silo/commits/main` and fetched `origin/main`. -- PR [#187](https://github.com/pgsty/silo/pull/187): OPEN, unmerged, no reviews/checks returned; head `b8f2fdde41dff3dc3b8db669c1d42d30ca5c1d3d`. GraphQL's PR baseRefOid is `89637554d60c27cfc51d2281d0a4fe15e415f06d`; it is not the live main checked above. Snapshot and exact diff: `/Users/vonng/tmp/silo-r7-20260915-ad51/pr187.{json,diff}`. -- Introduction: `56fa63bfd155154157cd7e1fb6dc295a3b3104ed` (2026-04-15), replication-header injection hardening. Keep its trust protections intact. -- Governing scope: PGSTY maintained stack, minimal compatible fix. No dependency, wire-format, credential, encryption algorithm or API changes. - -## Root cause and observable contract - -`extractMetadata` calls the ordinary extractor, removes disallowed unencrypted-length/MD5 user metadata, and trims the exact `aws-chunked` transport token from `content-encoding`. The trusted-replica restoration currently calls the same broad extractor with `allowReplication=true`. That replays all supported headers and user metadata, reversing normalization and redaction. - -Expected mappings are `aws-chunked` -> absent Content-Encoding, `aws-chunked,gzip` -> `gzip`, and `gzip` -> `gzip`. Object bytes are not transformed by this fix. AWS documents this behavior in [SigV4 streaming](https://docs.aws.amazon.com/AmazonS3/latest/developerguide/sigv4-streaming.html). The helper reproduction is `/Users/vonng/tmp/silo-r7-20260915-ad51/baseline_test.go` with Go overlay and `baseline.log`; its failing expectations are evidence of the current defect, not implementation validation. - -Persistence/read path: `erasure-metadata.go` reads `fi.Metadata["content-encoding"]` into ObjectInfo.ContentEncoding; `api-headers.go` exposes it on GET/HEAD. Outbound `putReplicationOpts` and metadata-only replication copy also use the object's content encoding. Preventing raw request metadata from being replayed at ingress is sufficient for this defect and avoids read-path masking. - -## Input and trust boundary audit - -The helper does not authenticate; callers own authentication and authorization. `evaluateReplicationTrust` requires an authenticated principal, the exact single replication marker `true`, and `s3:ReplicateObject`; restoring replica-only metadata also requires `REPLICA`. Unauthorized declared replicas are rejected; marker-only/untrusted requests retain their existing sanitized behavior. Do not move restoration earlier or make headers themselves establish trust. - -| Actual caller | Metadata before restoration | Trust gate and intended result | -| --- | --- | --- | -| PutObjectHandler | extractMetadataFromReq before trust evaluation | after successful signature verification, evaluate/apply trust; only replicaTrusted restores six fields | -| CopyObjectHandler via getCpObjMetadataFromHeader | REPLACE calls extractMetadataFromReq; COPY uses source metadata | authenticated source/destination checks; allowReplicationMetadata=replicaTrusted; REPLACE preserves normalization, COPY retains existing semantics | -| NewMultipartUploadHandler | extractMetadataFromReq after trust/sanitization | replicaTrusted restores six fields into initiation metadata; parts and completion reuse saved metadata | -| PutObjectExtractHandler, outer Snowball headers | only storage class and per-entry transform metadata, not generic extractMetadata | per-entry PutObject and ReplicateObject authorization; only replicaTrusted restores six fields. No-PAX entries must not inherit ordinary outer archive metadata | -| PutObjectExtractHandler, PAX entry metadata | extractMetadata on minio.metadata.* records | reuse per-entry trust; merge normalized entry metadata plus six allowed fields. Outer ordinary archive encoding must not leak even if the PAX map omits it | - -PutObjectPart, CopyObjectPart and CompleteMultipartUpload do not call this helper; no additional restoration is needed there. Validate completion persistence to catch assumptions at this boundary. POST form upload does not restore replication metadata. Metadata COPY does not normalize historical source metadata; that is deliberately outside this preventive fix. - -## Proposed production patch - -Adopt the production change in PR #187, adjusted only if current-context application requires it: - -1. Remove the `extractMetadataFromMimeWithReplication` boolean-mode helper. -2. Ordinary `extractMetadataFromMime` keeps header canonicalization and supported/user metadata extraction, always skips the replication-only mapping keys. -3. `extractReplicationMetadataFromMime` keeps nil-input error behavior and canonical header lookup; loops only over `replicationToInternalHeaders` and joins multi-values exactly as before. -4. Never re-read ordinary supported or user metadata in the restoration helper. Preserve keys already normalized, defaulted, redacted, or set by the caller. -5. Preserve all six mappings: sealed SSE-C key, seal algorithm, IV, encrypted-multipart marker (including its empty value), actual object size, and ReplicationSsecChecksumHeader (identity mapping). Preserve canonicalized/lowercase input header compatibility. -6. Clarify the comment to cover Snowball: ordinary metadata is owned by the caller; the common normalizing path runs before restoration, while outer archive metadata is not per-entry object metadata. - -No normalizer/token grammar rewrite. The current exact-token trimming semantics, malformed duplicate-cased headers, and validation of SSE field payloads are outside this bug; retain existing behavior rather than expanding accepted formats or validation rules. - -## Verification matrix and acceptance - -Use temporary overlay reproductions before consensus. Promote focused regressions only after recorded Opus agreement. Run targeted tests with bounded Go parallelism because sibling tasks share this host. - -1. Helper pipeline: absent encoding, bare aws-chunked, aws-chunked,gzip, gzip, gzip,aws-chunked, multi-valued encoding; ordinary vs restoration; key absence for bare encoding; legitimate gzip retained; ordinary/user metadata sentinel values and redacted unencrypted metadata not restored. Exact expected six-field map, canonical/lowercase headers, empty multipart marker, nil input handling. Repeat restoration should not change ordinary metadata. -2. Real signed HTTP PUT -> persisted ObjectInfo -> GET and HEAD on the existing single-drive and 16-drive erasure fixtures. Use real streaming chunk signatures for transport cases and a valid gzip payload for gzip cases. Compare raw response bytes and content encoding; bare transport must have no header. Test authenticated ordinary, trusted replica, and marker without replication permission; declared replica without permission returns 403 and creates nothing. -3. Signed COPY REPLACE and multipart initiate/part/complete -> persisted ObjectInfo -> GET/HEAD for bare, mixed, and plain gzip. Include ordinary controls. Initiation carries object metadata; part/completion carry contrasting content encoding to prove they cannot replace it. COPY preserves existing source metadata semantics. -4. Snowball trusted entry tests with and without PAX, including PAX no Content-Encoding and PAX mixed encoding; outer aws-chunked never leaks. Existing per-entry trust test stays green. -5. Existing SSE-C single PUT and multipart replication round trips plus replication-header poisoning regressions. Helper matrix verifies all six field mappings; actual SSE-C tests verify readable ciphertext replicas, encryption metadata, checksum and multipart layout. Preserve bucket default encryption/compression behavior. Run relevant SSE-KMS/SSE-S3 option/replica tests if available without broadening R4 scope. -6. Targeted package tests, focused race run, build and repository verifiers. If environmental failures (e.g. disk free-space threshold) prevent existing tests from reaching the path, retain the original failure and use an explicitly documented temporary capacity adapter already used by the repository, keeping actual object I/O on test disks. Do not mislabel that as an unmodified pass. -7. Baseline regression must fail for raw/mixed trusted metadata and fixed code must pass identical expectations. Record exact commands, exit status, baseline/diff hashes and fixture limits. No full distributed sites/deployment acceptance claim from local handler tests. - -## Stored-object remediation proposal (separate, no execution) - -Upgrade only prevents new pollution. Existing source/COPY metadata can remain wrong, and rollback reopens ingress pollution without undoing repairs. Do not rewrite production objects or private xl.meta files. - -A separate operator-reviewed job must inventory bucket/key/version, original Content-Encoding and complete metadata, source/replica provenance, version/ETag/size/checksum and encryption/retention settings. Identify exact aws-chunked tokens and preserve other encodings/order. Verify source bytes/encoding before deciding; gzip must not be guessed or decompressed merely from the broken label. Keep an immutable manifest and metadata backup. Test a version-preserving supported metadata operation on a local replica of the relevant setup; ordinary S3 self-COPY can create a new version/change metadata timestamps and is not a universal version-preserving repair. Resolve object-lock, SSE-C keys, concurrent changes and replication ordering before approving the concrete write plan. Apply a small approved batch with concurrency guards, re-read exact versions, verify GET/HEAD and raw bytes/checksums, then reconcile replicas. Skips/conflicts need explicit reporting and a tested rollback. This task supplies the reviewable design only. - -## Effort, delivery and gate - -Expected 0.5-1 engineer-day for patch, targeted tests and evidence on a familiar checkout; stored-object repair and release are separate work. Production patch is about 30 added/20 removed lines in one helper file; tests provide most of the new code. - -Before implementation: actual Claude Code `--model claude-opus-5 --effort max`, read-only tools, same frozen plan hash + baseline + PR snapshot. Record raw review, actual assistant model(s), objections and dispositions. Any model mismatch/error/rate-limit is not consensus. Resolve substantive findings and get explicit approval of the same plan version before production changes. After consensus, implement and verify without another user permission request. - -Deliver research, versioned plan, consensus/dispositions, minimal production diff, tests and verification summary. Local commit may package the reviewable result. No main merge, remote PR mutation, push, release, deployment, or existing-object rewrite is included. diff --git a/docs/investigations/r7/research.md b/docs/investigations/r7/research.md deleted file mode 100644 index 819b36163..000000000 --- a/docs/investigations/r7/research.md +++ /dev/null @@ -1,43 +0,0 @@ -# R7 调查与复现 - -## 结论 - -基线 `9ebe81c1b3611f9cc73e676b5b741c2be62c467a` 中,可信复制恢复过程把已规范化的普通元数据从原始请求中重新提取。`aws-chunked` 是传输编码,重新落盘后会被 GET/HEAD 返回。SILO 在 2026-04-15 的 `56fa63bfd155154157cd7e1fb6dc295a3b3104ed` 中引入此回归;该提交修复的复制头信任边界仍需保留。 - -实时核对 [PR #187](https://github.com/pgsty/silo/pull/187):OPEN、未合并,head `b8f2fdde41dff3dc3b8db669c1d42d30ca5c1d3d`;只恢复复制专用字段的方向与根因吻合。没有将 PR 自报测试当成本轮验证。 - -## 当前调用链 - -五处调用:PUT、COPY REPLACE、NewMultipartUpload、Snowball 外层请求、Snowball PAX 条目。 - -- PUT/COPY/multipart 使用规范化普通元数据;可信分支不应再覆盖它。 -- Snowball 外层只有 storage class/条目转换信息,没有通用提取;归档外层 Content-Type/Content-Encoding 不是条目元数据。 -- PAX 元数据经过普通提取;无 Content-Encoding 的 PAX 映射也不能留下先前泄漏的外层编码。 -- multipart 的 Part/CopyPart/Complete 不调用该恢复函数;完成后必须检验初始化元数据确实被保留。 -- 所有可信恢复均需通过认证、精确复制标记、ReplicateObject 权限和 REPLICA 状态的组合判断;Snowball 对每个条目分别鉴权。 - -`erasure-metadata.go` 将落盘 `content-encoding` 读入 ObjectInfo;`api-headers.go` 在 GET/HEAD 返回该值。对象字节并非因此一定受损。 - -AWS [SigV4 streaming 规范](https://docs.aws.amazon.com/AmazonS3/latest/developerguide/sigv4-streaming.html) 要求保存对象时去掉 aws-chunked,只保留实际的内容编码;只有 aws-chunked 时读取响应不应有 Content-Encoding。 - -## 实测记录 - -原始证据根目录:`/Users/vonng/tmp/silo-r7-20260915-ad51/`。 - -| 记录 | 结果与边界 | -| --- | --- | -| `baseline_test.go` / `baseline-overlay.json` / `baseline.log` | 原始产品代码,临时 Go 测试覆盖:ordinary bare/mixed 正常,trusted bare/mixed 重新污染,纯 gzip 正常 | -| `http-baseline-unadapted.log` | 未调整夹具的本机单盘 HTTP 上传被 507 / XMinioStorageFull 拒绝;不是 R7 结果 | -| `http-baseline.log` | 首个容量适配 HTTP 运行;PUT/COPY/Snowball 可复现;multipart 夹具错误用 Header.Get 读取了仓库直接写入的 ETag 键,完成时 InvalidPart,不能用于 multipart 结论 | -| `http_test.go` / `http-capacity-overlay.json` / `http-baseline-v2.log` | 修正 ETag 读取后:PUT、COPY REPLACE、multipart 的普通和 untrusted-marker 对照通过;trusted bare/mixed 在落盘和 GET/HEAD 中失败;trusted gzip 通过;无权 replica 返回 403 且不建对象 | -| 同一最终 HTTP 基线日志的 Snowball 部分 | no-PAX、PAX 无编码、PAX 裸编码、PAX 混合编码均复现 GET/HEAD 污染 | - -最终 HTTP 基线总计 64 个叶子用例:44 个控制通过,20 个预期缺陷失败。每个用例均使用认证 HTTP handler + 实际对象磁盘。PUT 传输案例和 Snowball 使用真实流式分块签名,gzip 案例使用实际 gzip 字节;读取比较原始字节并检查 Content-Encoding。 - -## 容量适配的准确含义 - -宿主 APFS 报告约 100% 使用率,触发 SILO 的相对空闲阈值。临时 `capacity-test-utils_test.go` 使用仓库既有 `tagTestCapacityDisk` 包装 API 测试夹具,将总量/已用量报告为当前空闲量/0;实际对象写入、读取和元数据持久化仍落在测试磁盘。该 overlay 不进入产品 diff,也不调整生产容量策略。结果属于明确容量适配后的本地验证,不是未改夹具的通过。 - -## 方案与存量 - -冻结方案见 [plan-v1.md](plan-v1.md)。存量对象处理见 [stored-metadata-remediation.md](stored-metadata-remediation.md),只提供设计,本任务不执行现网对象改写。 diff --git a/docs/investigations/r7/review/opus-v1-confirmation.md b/docs/investigations/r7/review/opus-v1-confirmation.md deleted file mode 100644 index bfdec89af..000000000 --- a/docs/investigations/r7/review/opus-v1-confirmation.md +++ /dev/null @@ -1,28 +0,0 @@ -本轮只做回应核验,未重复独立审计;无 shell,未计算哈希、未运行任何测试。 - -# R7 v1 第二轮确认(Opus 5) - -## 1. 版本与哈希 - -- 冻结方案:`docs/investigations/r7/plan-v1.md`,任务声明 SHA-256 **`7af5705ebbfb0a375956d38dba059095dc16e24558290b1bd35a6ce85b9e1f96`**。 -- 处置附录:`docs/investigations/r7/plan-v1.dispositions.md`,任务声明 SHA-256 **`d32d30f8a420f904da6ee039f0461d6281e7da44bd805d416b4970d071f0af32`**。 -- 两个哈希均按任务声明采纳,本会话无法计算,校验仍属调用方责任。产品基线 `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`,工作区确认未改产品代码(`docs/investigations/r7/` 为 untracked)。生产补丁仍读 `pr187.diff`,作为提议代码而非我运行的证据。 - -## 2. 三个争议点的裁定 - -**N6:我错了,撤回。** `cmd/erasure-server-pool-tags_test.go:258-264` 确有 `type tagTestCapacityDisk struct{ StorageAPI }`,其 `DiskInfo` 把 `Total=Free, Used=0`,并已在同文件 `:131` 被 `TestReplicaWritesPreserveTagOrdering` 使用。我上轮按 “adapter” 字面 grep 漏掉了该类型,所以 plan 第 56 行 “repository already used 的临时容量适配器” 属实。撤回 N6 的事实判断,保留其被接受的部分:临时 `capacity-test-utils_test.go` 不进交付 diff,原始 507 与适配过程需记录。补充一句非阻塞:若正式回归仍需容量适配,直接复用同包内已有类型即可(不算临时旁路),产品容量策略不得改动。 - -**N1:接受纠正。** `object-handlers.go:2740` 的 `rawReplica` 是整包级判定,`:2788-2791` 对任何缺 `ReplicateObject` 的条目直接 `ErrAccessDenied` 并中止该条目;因此我建议的“同一 REPLICA 包内 trusted 与 untrusted 条目对比”在机制上不可能成立。Codex 的做法正确:同一 tar 分别发 ordinary 与 replica 两次请求,比较条目元数据(排除 replica 状态/时间戳/ETag)。实质结论不变且已被证据坐实——补丁前 `:2835-2838` 的恢复分支会把外层 `supportedHeaders` + 用户元数据整体灌进仅含 storage class 的 `metadata`(`:2802-2804`),补丁后只剩六个映射;`http-baseline-v2.log:195-201` 的 `Erasure/no-pax` 正是外层 `aws-chunked` 泄漏。可选增补(非必须):再加一条 untrusted-marker 归档对照。 - -**N5:接受收窄措辞。** 源码只能支撑到:`bucket-replication.go:987-997` 的逐字符串比较 → `replicationActionForTarget:1131` → 仅在 `replicateObjectToTarget:1598` 的复制任务里求值,且成功后置 `Completed`、不自我重排队。所以“每次 heal/resync/重放对账都会再次选中 `replicateMetadata`” 成立,“不间断热循环” 我上轮说过头,撤回该措辞。补救顺序“先确认并修权威源版本、再协调副本”,以及把重复元数据复制/不一致记为已知影响,均予保留。 - -## 3. 其余处置确认 - -- **N2 已用实测兑现**:`http-baseline-v2.log` 是 handler 级证据,单盘 `ErasureSD` 与 16 盘 `Erasure` 均覆盖 put/copy-replace/multipart,失败精确落在 `replica/bare`(persisted/GET/HEAD 均为 `aws-chunked`,期望空)与 `replica/mixed`(`aws-chunked,gzip` vs `gzip`),ordinary、untrusted-marker、gzip、unauthorized-replica 全通过;连同 Snowball 8 例,64 叶 = 44 通过 / 20 失败,与附录计数一致。护栏诉求已满足(该日志由本会话之外产生,我只读未跑)。 -- **N3 / N7 / N8 / N9**:接受无异议。N3 额外要求断言 XML 码为 `AccessDenied` 以区分签名失败,正确。 -- **N4**:接受,且两条期望值与代码一致——`handler-utils.go:357-368` 按 `,` 精确等值比较,故 `"aws-chunked, gzip" → " gzip"`(保留前导空格)、`"gzip, aws-chunked" → "gzip, aws-chunked"`。仅作现状记录,不得宣传为本次修复。 -- **最终验收范围补充**:同意。无双站点调度器/重启/网络故障验收时只报告本地接收链路与既有 SSE 结论;存量修复只出设计文件,不授权扫描或改写现网对象。 - -## 4. 结论 - -**APPROVE。阻塞问题 0。** plan-v1 + 本处置附录构成的 v1 组合可直接进入实现,无需 v2;N1/N5 采用 Codex 的修正表述,N6 以我撤回告结。实现时请把 N1(无 PAX 行为变化)、N4(空格现状)、N5(收敛风险与补救顺序)落为断言或文档,并确保临时容量适配文件不出现在交付 diff 中。 diff --git a/docs/investigations/r7/review/opus-v1-confirmation.metadata.json b/docs/investigations/r7/review/opus-v1-confirmation.metadata.json deleted file mode 100644 index 31e3833b5..000000000 --- a/docs/investigations/r7/review/opus-v1-confirmation.metadata.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "requested_model": "claude-opus-5", - "requested_effort": "max", - "cli_version": "2.1.270", - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "plan": "plan-v1.md", - "plan_sha256": "7af5705ebbfb0a375956d38dba059095dc16e24558290b1bd35a6ce85b9e1f96", - "addendum": "plan-v1.dispositions.md", - "addendum_sha256": "d32d30f8a420f904da6ee039f0461d6281e7da44bd805d416b4970d071f0af32", - "status": "completed", - "started_at": "2026-09-15T15:51:25.514239+00:00", - "raw_output": "/Users/vonng/tmp/silo-r7-20260915-ad51/opus-v1-confirmation.jsonl", - "stderr": "/Users/vonng/tmp/silo-r7-20260915-ad51/opus-v1-confirmation.stderr.log", - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --output-format stream-json --verbose --add-dir /Users/vonng/tmp/silo-r7-20260915-ad51", - "actual_assistant_models": [ - "claude-opus-5" - ], - "subtype": "success", - "is_error": false, - "duration_ms": 57910, - "num_turns": 18, - "session_id": "9b4140a8-140f-4d94-9a59-9983346709fd", - "raw_sha256": "2e2db44411a0b367607b73a2f7fe38c5125f49294f496d603c8ed4975c549bba", - "review_sha256": "a471ca03b6e68d77d01dede8c6d1a8f989bceed9f7eeb99fc36634c025eb3541", - "verdict": "APPROVE", - "blocking_findings": 0, - "completed_at": "2026-09-15T15:53:09.806502+00:00", - "auxiliary_model_ids": [ - "claude-haiku-4-5-20251001", - "claude-opus-5" - ], - "observed_tool_attempts": [ - "Grep", - "Read" - ] -} diff --git a/docs/investigations/r7/review/opus-v1-confirmation.prompt.md b/docs/investigations/r7/review/opus-v1-confirmation.prompt.md deleted file mode 100644 index b4844afa9..000000000 --- a/docs/investigations/r7/review/opus-v1-confirmation.prompt.md +++ /dev/null @@ -1,7 +0,0 @@ -This is round 2 of the actual R7 Opus review discussion. Product baseline remains 9ebe81c1b3611f9cc73e676b5b741c2be62c467a and NO product code has been changed. Your first actual review is /Users/vonng/.codex/worktrees/ad51/silo/docs/investigations/r7/review/opus-v1.md. It approved v1 with 9 nonblocking notes and zero blockers. - -Please read the SAME frozen plan /Users/vonng/.codex/worktrees/ad51/silo/docs/investigations/r7/plan-v1.md (SHA-256 7af5705ebbfb0a375956d38dba059095dc16e24558290b1bd35a6ce85b9e1f96) and Codex's numbered replies / binding acceptance addendum /Users/vonng/.codex/worktrees/ad51/silo/docs/investigations/r7/plan-v1.dispositions.md (SHA-256 d32d30f8a420f904da6ee039f0461d6281e7da44bd805d416b4970d071f0af32). The plan+addendum is the exact v1 consensus bundle; production patch remains /Users/vonng/tmp/silo-r7-20260915-ad51/pr187.diff. - -Focus this round on replies, not repeating the whole independent audit. Check the factual disagreement N6 by reading cmd/erasure-server-pool-tags_test.go lines 258-265 (tagTestCapacityDisk does exist; Grep for adapter missed it). Check N1's parity testing correction: a single REPLICA archive with an unauthorized entry rejects that entry, so equal ordinary vs replica archive uploads is the correct comparison. Check N5's narrower statement about repeated reconciliation opportunities rather than claiming an automatic continuous hot loop. N2 is now backed by /Users/vonng/tmp/silo-r7-20260915-ad51/http-baseline-v2.log. The other notes are accepted and will become assertions/docs. - -Return a concise Chinese confirmation that (a) explicitly names both hashes, (b) agrees/disagrees with each disputed point N1/N5/N6 and remaining dispositions, and (c) states APPROVE or REQUEST_CHANGES and whether there are any blockers to implementing this exact v1 bundle. Do not simulate tests. No shell is available: hash checking remains the caller's responsibility. Do not attempt Write or write a Claude plan file; tools are restricted to Read/Grep/Glob and your review is the final text. diff --git a/docs/investigations/r7/review/opus-v1.md b/docs/investigations/r7/review/opus-v1.md deleted file mode 100644 index 6405d8aa4..000000000 --- a/docs/investigations/r7/review/opus-v1.md +++ /dev/null @@ -1,51 +0,0 @@ -Write 工具在本会话被禁用,因此评审直接以回复形式交付(本次任务本就是只读评审,未改动任何文件、未运行任何测试)。 - ---- - -# R7 修复方案独立评审 - -## 1. 审阅版本与哈希 - -- **审阅对象**:`/Users/vonng/.codex/worktrees/ad51/silo/docs/investigations/r7/plan-v1.md`,72 行,标题 `R7 plan v1: preserve normalized replica object metadata`(`docs/investigations/r7/` 在 git status 中为 untracked)。 -- **任务给定 SHA-256**:`7af5705ebbfb0a375956d38dba059095dc16e24558290b1bd35a6ce85b9e1f96`。本会话只读且无 shell,**我未重新计算该哈希**;逐字审阅的是该路径当前内容。 -- 基线 `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`、引入提交 `56fa63bf…`、PR #187 的 OPEN/head 状态均按任务声明采纳,**未用 git/gh 复核**。 -- `pr187.diff` 被当作“提议的代码”阅读,不视为已运行的测试;`baseline.log` 由本会话之外产生,我只读未跑。 -- **性质**:源码与方案验证,非我执行的测试。 - -## 2. 结论 - -**APPROVE_WITH_NONBLOCKING_NOTES** - -## 3. 阻塞问题 - -**无阻塞问题。** 以下为逐项核验依据。 - -**最小补丁充分性(充分)**:恢复辅助函数是 replica 路径上唯一能把未归一化的 ordinary 头写进对象元数据的入口。`putOptsFromHeaders`/`getDefaultOpts`(`cmd/object-api-options.go:388-477`)只读 SSE 与 source-* 时间戳;`completeMultipartOpts:542-548` 只取 actual-object-size 与 ssec-crc。五处调用点(`object-handlers.go:1149/2282/2836/2869`、`object-multipart-handlers.go:246`)全部被覆盖。 - -**信任边界(未改变)**:`evaluateReplicationTrust`(`cmd/replication-trust.go:78-90`)要求已认证主体 + 精确单值 `true` 标记 + `s3:ReplicateObject`,`REPLICA` 声明无权限直接 403;Snowball 走等价的 per-entry 内联判定(`object-handlers.go:2784-2793`)。补丁不前移恢复点、不让头部本身产生信任。 - -**调用链(方案描述与代码一致)**:PUT 在签名校验后评估信任、元数据在 `:2199` 已归一化;COPY REPLACE 用 `extractMetadataFromReq`、COPY 保留源语义(`:1143-1156`、`:1411`、`:1801`);多段初始化在 sanitization 之后提取(`object-multipart-handlers.go:179/233`);**分片与完成确实不需要恢复**——分片从 `mi.UserDefined` 取加密状态(`:885-886`、`:957-987`),完成从 `completeMultipartOpts` 取两个字段。 - -**六个映射与空标记**:补丁遍历 `replicationToInternalHeaders`(`handler-utils.go:106-114`),与基线遍历 `supportedHeaders` 的键集完全相同,且六→六为单射,故 map 迭代顺序无关;空值 multipart 标记按 key 存在性消费(`internal/crypto/metadata.go:27`),`strings.Join([]string{""}, ",")==""` 行为与基线一致。 - -**归一化与冗余用户元数据**:基线恢复分支会把刚被 `extractMetadata`(`:218-241`)删除的 `X-Amz-Meta-X-Amz-Unencrypted-Content-Length/-Md5`(`internal/http/headers.go:138-139`,GHSA-76wf-9vgp-pj7w)按原始大小写写回,补丁一并消除。 - -**额外独立验证(支持方案的关键事实)**:本仓库固定的 minio-go(`go.mod:71` → `pkg/signer/utils.go:70-87 setAwsChunkedContentEncoding`)**保留调用方已设编码**并生成 `aws-chunked` 或 `aws-chunked,gzip`(无空格)。因此方案声明的 `aws-chunked→无`、`aws-chunked,gzip→gzip` 与真实复制线路一致,修复后目标端存储值将等于源端 `objInfo.ContentEncoding`(`bucket-replication.go:838`),读路径 `erasure-metadata.go:138` → `api-headers.go:129-131` 也成立。 - -## 4. 非阻塞意见 - -1. **Snowball 无 PAX 条目的行为变化必须显式承认并加断言**。证据:`object-handlers.go:2802-2842` 的 `metadata` 只有 storage class 与压缩键,基线恢复会把外层 tar 请求的 content-type / `x-amz-meta-*` / cache-control 复制进每个 entry;补丁后不再复制,与普通 Snowball(`:2874-2877` 分支从不做 `extractMetadata`)一致。我同意这个选择,但它超出“只去掉 aws-chunked”。最小修正:在 §Proposed patch 第 6 条写明“trusted replica 无 PAX 条目不再继承外层归档 ordinary 元数据”,并在 §Verification 4 增加断言:同一 tar 中 trusted 与 untrusted 无 PAX 条目的 UserDefined(除 replica 状态/时间戳/ETag 外)相等;同时注明“外层请求的六个字段仍套用到所有条目”是既有且有意保留的行为。 -2. **回归护栏应绑定 handler 级用例**。`baseline_test.go` 只覆盖 helper、绕过信任门;真正会退化的是调用点。最小修正:§Verification 7 的“基线必须失败”至少绑定一条 HTTP 用例(trusted replica streaming PUT → `GetObjectInfo().ContentEncoding`)。 -3. **流式签名测试必须在签名前注入 replication 头**。`newTestStreamingSignedCustomEncodingRequest`(`test-utils_test.go:817-834`)先 Set 编码再签名;若签名后再加 `x-amz-bucket-replication-status`,得到的是 403 SignatureDoesNotMatch,容易被误读成“未恢复元数据”。最小修正:在 §Verification 2/3 补一句,并要求区分签名失败与权限拒绝。 -4. **精确 token 裁剪的空格限制未被测试固定**。`handler-utils.go:357-368` 按 `,` 分割做精确等值比较,`"gzip, aws-chunked"` 不会被裁剪。同意不改语法;最小修正:§Verification 1 增加两条“记录现状”的断言用例。 -5. **已污染对象的收敛性风险应进入补救段**。`bucket-replication.go:987-997` 用源端 `ContentEncoding` 与目标端元数据比较,而 minio-go 在已含 `aws-chunked` token 时直接早退(`utils.go:78-80`):源端仍为 `aws-chunked` 的历史对象在目标端修复后将永久 mismatch 并反复触发 metadata 复制。最小修正:补救顺序明确为“先修源端版本、再 reconcile 副本”,并把该 churn 列为已知影响与监控项。 -6. **“仓库已有的容量适配器”与现状不符**:`cmd/` 下不存在此类测试旁路(`object-api-utils.go:1302 hasSpaceFor` 无测试开关;`cmd` 内 grep `adapter` 仅命中 `object-api-interface.go:325` 注释)。最小修正:改写为“显式记录所用临时本地改动并保证其不进入交付 diff”。 -7. **map 迭代等价性建议写入方案**(第 3 条补一句“顺序无关性来自单射映射”),避免未来新增映射时被误改;`nv` 对重复大小写头仍是“最后写入者胜”,与基线相同,不是本次引入。 -8. **GHSA 冗余键回写的修复建议在根因段显式成句**;并注意 `putReplicationOpts`(`bucket-replication.go:797-813`)不像 `getCopyObjMetadata`(`:733-735`)那样过滤这两个键,故历史对象可能确实携带它们,修复后目标端会与普通 PUT 一样丢弃。 -9. **POST 表单路径的不对称**:`bucket-handlers.go:1260-1266` 直接调用 `extractMetadataFromMime`,本就不做裁剪/默认 content-type/冗余键删除;补丁未改变它,建议在方案中补半句,避免未来“顺手统一”而改变 POST 行为。 - -## 5. 是否同意同一 v1 方案进入实现 - -**同意。** 该 v1 方案的根因、信任边界、调用链与补丁范围均与当前基线源码相符,不存在阻塞缺陷;上述 9 条均为记录/测试/文档层面的处置,不需要改动补丁本体。在 Codex 逐条记录处置(尤其第 1、2、5 条)后,**同一 v1 方案可直接进入实现,无需 v2**。 - -最后重申两点事实边界:PR #187 diff 中的测试改动是提议代码而非已运行证据;`baseline.log` 的失败断言是既有缺陷的证据,非实现验证——两者都不是我在本会话中运行的测试。 diff --git a/docs/investigations/r7/review/opus-v1.metadata.json b/docs/investigations/r7/review/opus-v1.metadata.json deleted file mode 100644 index 3e7282d16..000000000 --- a/docs/investigations/r7/review/opus-v1.metadata.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "requested_model": "claude-opus-5", - "requested_effort": "max", - "cli_version": "2.1.270", - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "plan": "plan-v1.md", - "plan_sha256": "7af5705ebbfb0a375956d38dba059095dc16e24558290b1bd35a6ce85b9e1f96", - "status": "completed", - "started_at": "2026-09-15T15:44:49.861232+00:00", - "raw_output": "/Users/vonng/tmp/silo-r7-20260915-ad51/opus-v1.jsonl", - "stderr": "/Users/vonng/tmp/silo-r7-20260915-ad51/opus-v1.stderr.log", - "command": "claude --print --model claude-opus-5 --effort max --safe-mode --permission-mode plan --tools Read,Grep,Glob --strict-mcp-config --no-session-persistence --output-format stream-json --verbose --add-dir /Users/vonng/tmp/silo-r7-20260915-ad51", - "actual_assistant_models": [ - "claude-opus-5" - ], - "observed_tool_attempts": [ - "Glob", - "Grep", - "Read", - "Write" - ], - "subtype": "success", - "is_error": false, - "duration_ms": 309465, - "num_turns": 62, - "session_id": "10f1e9b3-7120-4a1f-94d9-e53745849cd1", - "raw_sha256": "b749be2acbec45b978907849c741e1f71994b0ad4e13d5b5204f429c9ba408b3", - "review_sha256": "d832e461291f0ec32455ade97c9bf9cd02d31812642a4b9254dd5e06ab1cc82b", - "verdict": "APPROVE_WITH_NONBLOCKING_NOTES", - "blocking_findings": 0, - "completed_at": "2026-09-15T15:51:03.390714+00:00", - "auxiliary_model_ids": [ - "claude-haiku-4-5-20251001", - "claude-opus-5" - ], - "note": "Actual reviewer assistant messages all identify claude-opus-5. CLI plan mode attempted Write to a Claude plan artifact, but Write was disabled; final review was returned as text. No product changes." -} diff --git a/docs/investigations/r7/review/opus-v1.prompt.md b/docs/investigations/r7/review/opus-v1.prompt.md deleted file mode 100644 index b79ffb6c3..000000000 --- a/docs/investigations/r7/review/opus-v1.prompt.md +++ /dev/null @@ -1,12 +0,0 @@ -Independently review the R7 repair plan. Do not edit files or run implementation. The user requires real Opus 5.0 discussion and explicit consensus before product code changes. Disagree when evidence warrants it; do not assume PR author claims are tests we ran. - -Repository baseline: 9ebe81c1b3611f9cc73e676b5b741c2be62c467a. -Plan v1: /Users/vonng/.codex/worktrees/ad51/silo/docs/investigations/r7/plan-v1.md -Plan SHA-256: 7af5705ebbfb0a375956d38dba059095dc16e24558290b1bd35a6ce85b9e1f96 -Current upstream PR snapshot and proposed production patch: /Users/vonng/tmp/silo-r7-20260915-ad51/pr187.json and /Users/vonng/tmp/silo-r7-20260915-ad51/pr187.diff -Direct current-baseline helper reproduction: /Users/vonng/tmp/silo-r7-20260915-ad51/baseline_test.go, /Users/vonng/tmp/silo-r7-20260915-ad51/baseline.log (expected assertions fail). -Read the full plan and then independently inspect relevant current code including cmd/handler-utils.go, cmd/replication-trust.go, all five restoration call sites in cmd/object-handlers.go and cmd/object-multipart-handlers.go, existing test fixtures and SSE replication consumers. The Snowball no-PAX caller has NOT already performed generic ordinary metadata extraction; explicitly evaluate the proposed behavior there. - -Check: minimal patch sufficiency; ordinary and trusted/replica trust boundary; PUT/COPY/multipart/parts/completion/Snowball call chains; six SSE-only mappings and empty multipart marker/checksum; normalization and removed unsafe ordinary user metadata; correct actual HTTP tests; stored-object remedial design risks. Do not expand this into independent R4/R5 issues unless this proposed fix depends on them. - -Return a review in Chinese with (1) the reviewed version and exact hash, (2) verdict APPROVE / APPROVE_WITH_NONBLOCKING_NOTES / REQUEST_CHANGES, (3) each blocking issue with severity, exact code/plan evidence and smallest correction, (4) separately numbered nonblocking notes, (5) explicit whether you agree this SAME v1 plan can proceed to implementation after Codex records its dispositions. If no blocking issues, say so. Your review is source/plan validation, not actual tests run by you. diff --git a/docs/investigations/r7/stored-metadata-remediation.md b/docs/investigations/r7/stored-metadata-remediation.md deleted file mode 100644 index c2d6d6a5e..000000000 --- a/docs/investigations/r7/stored-metadata-remediation.md +++ /dev/null @@ -1,31 +0,0 @@ -# R7 存量错误元数据处理提案(待单独批准) - -本次代码修复只阻止可信复制再次从传输头写入 `aws-chunked`。升级不会扫描或改写旧对象;普通 COPY 继续保留来源对象的既有元数据。本文件是后续操作的设计,不是已执行的迁移。 - -## 1. 只读清单 - -按桶、键和精确 version ID 记录候选;包含非当前版本,不能只检查最新版本。条件是 Content-Encoding 的逗号分隔 token 中包含 `aws-chunked`。若大小写、空格或重复值异常,单列人工核验,不能凭字符串子串匹配改写。 - -清单至少保存:来源及目标站点、bucket/key/version ID、完整原始 Content-Encoding、拟保留编码、ETag、对象大小、可用内容校验值、修改时间、完整普通及用户元数据、标签、Object Lock/保留期/法律保留、SSE 模式及必要密钥的可用性、复制状态。清单不保存 SSE-C 密钥或凭据。读取响应时禁用客户端自动 gzip 解码,以便核对原始字节。 - -仅凭错误响应头不能判断字节是什么。比对可信来源版本或独立的原始内容校验值;有 gzip 的对象确认其字节确为 gzip 并保持原字节,不重新压缩。来源也受污染、来源版本不存在或校验依据不足时,标记为需调查,不自动修复。 - -## 2. 制作具体变更清单 - -优先确认并处理权威来源的精确版本,再协调副本。若来源仍保存错误编码,复制比较器会把规范化后的目标判断为不一致;在后续 heal/resync/比较时可能反复选择元数据复制。多向复制须核对整组来源和副本,记录持续不一致及重复元数据复制。这个风险不等于已证明存在不间断重试热循环。 - -原则上只删除被证实属于传输层的 `aws-chunked` token:仅有该 token 时移除 Content-Encoding,有其他编码时保留顺序和值。每条候选给出前后值和可回滚的元数据快照,其余内容、元数据和对象标识的保持条件逐项列出。 - -普通 S3 自 COPY 可能创建新版本、更新修改时间和复制排序;它不能被当作通用的原版本元数据修复 API。先在本地同配置克隆中验证可用的受支持管理/元数据操作,再选择方案。如果必须创建替代版本,要在清单中明确 version ID、当前版本关系和调用方影响。如果没有受支持的安全路径,停止该项,不编辑 `xl.meta` 或内部盘文件。 - -## 3. 审批与小批执行条件 - -批准的是具体清单和已经验证过的写入方式。执行前再次核对 version ID、ETag、大小、原元数据和时间等并发保护;ETag 单独不足以检测元数据更新。明确写入协调或维护窗口,发生冲突则跳过。Object Lock、SSE-C、生命周期和双向复制等条件分别验证;不得为了修元数据绕过保留限制。 - -先在可回滚的小批次验证:精确版本 HEAD 的 Content-Encoding 正确、GET 原始字节/校验值一致、对象锁和其他元数据没有被丢弃、目标站点版本与复制状态最终一致。留下逐项结果、冲突、跳过和失败日志,再决定扩大批次。 - -## 4. 回滚边界 - -保留不可变清单和完整元数据备份;为选用的具体操作验证回滚步骤。若写入创建了新版本,回滚必须考虑 version ID 和当前版本关系,不能用“再 COPY 一次”代替证明。代码回滚会重新开放新污染入口,并不会自动还原任何已修过的元数据。 - -本任务未对现网执行清单扫描、对象写入、版本调整、部署或存量修复。 diff --git a/docs/investigations/r7/verification.json b/docs/investigations/r7/verification.json deleted file mode 100644 index 3ebffb50f..000000000 --- a/docs/investigations/r7/verification.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "recorded_at": "2026-09-15T15:59:49.705292+00:00", - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "branch": "codex/r7-replication-content-encoding", - "source_sha256": { - "cmd/handler-utils.go": "76d6f98a8c9b04fcf1ae7c79d5832396bbf234bd5b5efc32a7f80dec64179b1a", - "cmd/handler-utils_test.go": "f8a53dd29170280eb007c4e43773187710f027e2aa49aadda91d2777bd0e034e", - "cmd/replication-content-encoding_test.go": "c71c4cf79a3ea6336febaf14bcb8ddfd68fcbef7625ff18830c14e9143749ee7", - "cmd/replication-metadata_test.go": "2cd3c45687d7333a9466906a278d7953fe4eeb008e07e2c7e2d167480c8b64bb" - }, - "plan_sha256": { - "plan-v1.md": "7af5705ebbfb0a375956d38dba059095dc16e24558290b1bd35a6ce85b9e1f96", - "plan-v1.dispositions.md": "d32d30f8a420f904da6ee039f0461d6281e7da44bd805d416b4970d071f0af32" - }, - "validation": { - "fixed-targeted": { - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "files": { - "cmd/handler-utils.go": "76d6f98a8c9b04fcf1ae7c79d5832396bbf234bd5b5efc32a7f80dec64179b1a", - "cmd/handler-utils_test.go": "f8a53dd29170280eb007c4e43773187710f027e2aa49aadda91d2777bd0e034e", - "cmd/replication-content-encoding_test.go": "c71c4cf79a3ea6336febaf14bcb8ddfd68fcbef7625ff18830c14e9143749ee7", - "cmd/replication-metadata_test.go": "2cd3c45687d7333a9466906a278d7953fe4eeb008e07e2c7e2d167480c8b64bb" - }, - "exact_baseline_overlay": "/Users/vonng/tmp/silo-r7-20260915-ad51/exact-baseline-overlay.json", - "recorded_at": "2026-09-15T15:55:15.454079+00:00", - "exit_code": 0, - "log": "/Users/vonng/tmp/silo-r7-20260915-ad51/fixed-targeted.log", - "log_sha256": "a30add5762881d604acffd63bca5880050b92a3ae125d208c53545ac78c9b212", - "command": [ - "go", - "test", - "-p", - "2", - "-overlay", - "/Users/vonng/tmp/silo-r7-20260915-ad51/capacity-overlay.json", - "./cmd", - "-run", - "^Test(ExtractMetadataHeaders|ExtractMetadataFromRequest.*|ExtractReplicationMetadata.*|GetCopyObjectMetadataFromHeaderReplication|APIReplicaContentEncoding|APISnowballReplicaContentEncoding)$", - "-count=1", - "-v" - ], - "GOMAXPROCS": "4", - "top_level_tests_passed": 9, - "leaf_subtests_passed": 80 - }, - "fixed-sse-trust": { - "command": [ - "go", - "test", - "-p", - "2", - "-overlay", - "/Users/vonng/tmp/silo-r7-20260915-ad51/capacity-overlay.json", - "./cmd", - "-run", - "^Test(API(SSECReplicaSkipsDestinationTransforms|SSECMultipartReplicaRoundTripWithCompression|SSECMultipartReplicationTrust|PutObjectReplicationHeaderPoisoning|CopyObjectReplicationHeaderPoisoning|PutObjectReplicationTrust|SnowballReplicationTrustIsPerEntry|SnowballInheritsBucketEncryption|StreamingTrailerWithUntrustedReplicationHeaders)|CloneRequestWithoutReplicationHeaders|PutReplicationOpts.*|ReplicationTrustControlsInternalOptionsAndEvents)$", - "-count=1", - "-v" - ], - "cwd": "/Users/vonng/.codex/worktrees/ad51/silo", - "started_at": "2026-09-15T15:54:14.556575+00:00", - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "source_sha256": { - "cmd/handler-utils.go": "76d6f98a8c9b04fcf1ae7c79d5832396bbf234bd5b5efc32a7f80dec64179b1a", - "cmd/handler-utils_test.go": "f8a53dd29170280eb007c4e43773187710f027e2aa49aadda91d2777bd0e034e", - "cmd/replication-content-encoding_test.go": "c71c4cf79a3ea6336febaf14bcb8ddfd68fcbef7625ff18830c14e9143749ee7", - "cmd/replication-metadata_test.go": "2cd3c45687d7333a9466906a278d7953fe4eeb008e07e2c7e2d167480c8b64bb" - }, - "GOMAXPROCS": "4", - "GOFLAGS": null, - "status": "completed", - "log": "/Users/vonng/tmp/silo-r7-20260915-ad51/fixed-sse-trust.log", - "exit_code": 0, - "duration_seconds": 11.364, - "completed_at": "2026-09-15T15:54:25.939958+00:00", - "log_sha256": "09222aaea6f0504dce7a801d5f392df77728ebd9875f4fef9dcdb28f71ce7b5a" - }, - "fixed-race": { - "command": [ - "go", - "test", - "-race", - "-p", - "2", - "-overlay", - "/Users/vonng/tmp/silo-r7-20260915-ad51/capacity-overlay.json", - "./cmd", - "-run", - "^Test(ExtractReplicationMetadata.*|APIReplicaContentEncoding|APISnowballReplicaContentEncoding|APISnowballReplicationTrustIsPerEntry|APISSECReplicaSkipsDestinationTransforms|APISSECMultipartReplicaRoundTripWithCompression)$", - "-count=1", - "-v" - ], - "cwd": "/Users/vonng/.codex/worktrees/ad51/silo", - "started_at": "2026-09-15T15:55:27.787466+00:00", - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "source_sha256": { - "cmd/handler-utils.go": "76d6f98a8c9b04fcf1ae7c79d5832396bbf234bd5b5efc32a7f80dec64179b1a", - "cmd/handler-utils_test.go": "f8a53dd29170280eb007c4e43773187710f027e2aa49aadda91d2777bd0e034e", - "cmd/replication-content-encoding_test.go": "c71c4cf79a3ea6336febaf14bcb8ddfd68fcbef7625ff18830c14e9143749ee7", - "cmd/replication-metadata_test.go": "2cd3c45687d7333a9466906a278d7953fe4eeb008e07e2c7e2d167480c8b64bb" - }, - "GOMAXPROCS": "4", - "GOFLAGS": null, - "status": "completed", - "log": "/Users/vonng/tmp/silo-r7-20260915-ad51/fixed-race.log", - "exit_code": 0, - "duration_seconds": 48.566, - "completed_at": "2026-09-15T15:56:16.372187+00:00", - "log_sha256": "b68af50412a8c1c03ea91aa40e72314ba9026798ac5dafb0d1580bc9824526b2" - }, - "verifiers": { - "command": [ - "make", - "verifiers", - "GOLANGCI=/Users/vonng/pgsty/silo/.bin/golangci/v2.13.1/golangci-lint" - ], - "cwd": "/Users/vonng/.codex/worktrees/ad51/silo", - "started_at": "2026-09-15T15:56:40.045654+00:00", - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "source_sha256": { - "cmd/handler-utils.go": "76d6f98a8c9b04fcf1ae7c79d5832396bbf234bd5b5efc32a7f80dec64179b1a", - "cmd/handler-utils_test.go": "f8a53dd29170280eb007c4e43773187710f027e2aa49aadda91d2777bd0e034e", - "cmd/replication-content-encoding_test.go": "c71c4cf79a3ea6336febaf14bcb8ddfd68fcbef7625ff18830c14e9143749ee7", - "cmd/replication-metadata_test.go": "2cd3c45687d7333a9466906a278d7953fe4eeb008e07e2c7e2d167480c8b64bb" - }, - "GOMAXPROCS": "4", - "GOFLAGS": "-p=2", - "status": "completed", - "log": "/Users/vonng/tmp/silo-r7-20260915-ad51/verifiers.log", - "exit_code": 0, - "duration_seconds": 102.527, - "completed_at": "2026-09-15T15:58:22.585543+00:00", - "log_sha256": "e42a5bb55f5c1ebfcf02cebebf6d82cf1ec5a2d74590cdf838deba16dd80bfdf" - }, - "build": { - "command": [ - "make", - "build" - ], - "cwd": "/Users/vonng/.codex/worktrees/ad51/silo", - "started_at": "2026-09-15T15:58:33.660305+00:00", - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "source_sha256": { - "cmd/handler-utils.go": "76d6f98a8c9b04fcf1ae7c79d5832396bbf234bd5b5efc32a7f80dec64179b1a", - "cmd/handler-utils_test.go": "f8a53dd29170280eb007c4e43773187710f027e2aa49aadda91d2777bd0e034e", - "cmd/replication-content-encoding_test.go": "c71c4cf79a3ea6336febaf14bcb8ddfd68fcbef7625ff18830c14e9143749ee7", - "cmd/replication-metadata_test.go": "2cd3c45687d7333a9466906a278d7953fe4eeb008e07e2c7e2d167480c8b64bb" - }, - "GOMAXPROCS": "4", - "GOFLAGS": "-p=2", - "status": "completed", - "log": "/Users/vonng/tmp/silo-r7-20260915-ad51/build.log", - "exit_code": 0, - "duration_seconds": 24.007, - "completed_at": "2026-09-15T15:58:57.722704+00:00", - "log_sha256": "6ba9b545236be964861749c72e7609edf12b8f470df30d1ede8fd62f497e629b" - } - }, - "counterfactual": { - "command": [ - "go", - "test", - "-p", - "2", - "-overlay", - "/Users/vonng/tmp/silo-r7-20260915-ad51/exact-baseline-overlay.json", - "./cmd", - "-run", - "^Test(ExtractReplicationMetadataPreservesNormalizedMetadata|APIReplicaContentEncoding|APISnowballReplicaContentEncoding)$", - "-count=1", - "-v" - ], - "cwd": "/Users/vonng/.codex/worktrees/ad51/silo", - "started_at": "2026-09-15T15:54:49.754711+00:00", - "baseline": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "source_sha256": { - "cmd/handler-utils.go": "76d6f98a8c9b04fcf1ae7c79d5832396bbf234bd5b5efc32a7f80dec64179b1a", - "cmd/handler-utils_test.go": "f8a53dd29170280eb007c4e43773187710f027e2aa49aadda91d2777bd0e034e", - "cmd/replication-content-encoding_test.go": "c71c4cf79a3ea6336febaf14bcb8ddfd68fcbef7625ff18830c14e9143749ee7", - "cmd/replication-metadata_test.go": "2cd3c45687d7333a9466906a278d7953fe4eeb008e07e2c7e2d167480c8b64bb" - }, - "GOMAXPROCS": "4", - "GOFLAGS": null, - "status": "completed", - "log": "/Users/vonng/tmp/silo-r7-20260915-ad51/exact-baseline-regression.log", - "exit_code": 1, - "duration_seconds": 27.026, - "completed_at": "2026-09-15T15:55:16.823655+00:00", - "log_sha256": "75d42b2a711c6d9a9f6d277449d1428e768e04ab10a2a331103b554d75ab17f8", - "expected_negative": true, - "reason": "Only production handler-utils.go reverted to baseline through an overlay; final regression tests and capacity fixture unchanged", - "passed_leaf_count": 44, - "failed_leaf_count": 36, - "effective_production_override_sha256": { - "cmd/handler-utils.go": "e647b53e5288d57ddbaacc5549d76ced0123b5e0cb630450cf071ffc78818398" - } - }, - "binary": { - "path": "/Users/vonng/.codex/worktrees/ad51/silo/silo", - "sha256": "c346ee4edf575bf6232689d25a7e38caa44ddd69dfa919df96c05aba4bb04c77", - "size": 93054290, - "version_output": "silo version DEVELOPMENT.2026-09-15T15-14-22Z (commit-id=9ebe81c1b3611f9cc73e676b5b741c2be62c467a)\nRuntime: go1.27.1 darwin/arm64\nLicense: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html\nCopyright: 2015-2025 MinIO, Inc.\nModifications: Copyright 2025-2026 PGSTY\nSource compatibility: based on MinIO technology\n", - "note": "Built and validated from the recorded uncommitted candidate file hashes; embedded commit ID is the baseline. This is not a release artifact." - }, - "capacity_overlay": { - "path": "/Users/vonng/tmp/silo-r7-20260915-ad51/capacity-overlay.json", - "sha256": "aa20dd65d323f09542d2317fc1c7e24fff67cf45e831057ed0e2b0be0a4628dd", - "fixture_path": "/Users/vonng/tmp/silo-r7-20260915-ad51/capacity-test-utils_test.go", - "fixture_sha256": "d6bf8cc58b651c86ca02063fecab14c59c950b345acf739d979e553c6c2a0897", - "tracked": false - }, - "remote_pr_state": { - "headRefOid": "b8f2fdde41dff3dc3b8db669c1d42d30ca5c1d3d", - "mergedAt": null, - "reviews": [], - "state": "OPEN", - "statusCheckRollup": [], - "updatedAt": "2026-09-15T07:07:26Z" - }, - "remote_main": { - "message": "Merge pull request #192 from pgsty/codex/iam-revision-tombstones\n\nfix(iam): retain revocation versions through replay and recovery", - "sha": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a" - }, - "limits": [ - "HTTP/SSE/race tests use a temporary capacity overlay, with real disk I/O.", - "typos was not installed and Makefile skipped it.", - "No full distributed two-site scheduler/restart/network-fault acceptance.", - "No merge, remote push, release, deployment or live stored-object rewrite." - ] -} diff --git a/docs/investigations/r8/.gitattributes b/docs/investigations/r8/.gitattributes deleted file mode 100644 index d4a2e2072..000000000 --- a/docs/investigations/r8/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Keep captured output and unified-diff context verbatim. -*.log -whitespace -*.patch -whitespace diff --git a/docs/investigations/r8/README.md b/docs/investigations/r8/README.md deleted file mode 100644 index 3907155e3..000000000 --- a/docs/investigations/r8/README.md +++ /dev/null @@ -1,100 +0,0 @@ -# R8:HTTP 请求头绝对超时 - -## 当前状态 - -两条缺陷链均已修复。完整 v2 已与真实 Opus 5.0(max)明确达成同版共识,零阻断分歧;配置与连接两条直接验证链均已闭合。额外 cmd race 曾因主机 ENOSPC 在链接阶段中止,补充旧 S3 脚本受存储最小空闲阈值阻挡;父任务已确认二者为环境未完成的补充检查,不阻断本地交付。交付保存在本地分支提交中,未推送、合并、发布或部署。 - -- 基线:`9ebe81c1b3611f9cc73e676b5b741c2be62c467a`(开工时本地及实时 origin/main 一致)。 -- 分支:`codex/r8-http-header-deadline`。 -- 方案:[v1](plan-v1.md)、[完整 v2](plan-v2.md)。 -- 最终源码绑定:[final-source-manifest.json](final-source-manifest.json),包含 5 个生产文件、4 个测试文件、计划与生产 diff 的 SHA256;[最终生产补丁](review/final-production.patch)。go.mod/go.sum 与基线一致。 -- 共识与意见处置:[consensus.md](consensus.md)。实际模型、显式 effort、计划/原始输出哈希保存在 [review](review/)。 - -## 两条缺陷链 - -### 1. 连接层覆盖绝对截止 - -`DeadlineConn.Read` 在读之前把 socket 截止更新为“现在 + idle + 250ms”,覆盖 Go 设置的绝对读头截止。直接 TCP 对照中,头部限制 100ms、idle=2s、400ms 才完成请求头,标准 Go 拒绝而旧 SILO listener 返回 204。 - -修复让 HTTP/1 请求头、keep-alive 等待和 TLS 握手读取阶段保留显式绝对上限;读头结束进入 `StateActive` 后恢复原有滚动读取,避免把 `ReadTimeout=IdleTimeout` 变成上传总时长上限。显式零值仍关闭超时,过去时间仍取消读取。写侧与默认 DeadlineConn 调用者保持原行为。现有 ConnState 回调得到保留。 - -### 2. CLI/环境配置没有传入服务器 - -`buildServerCtxt` 复制了 IdleTimeout,遗漏 ReadHeaderTimeout。真实 CLI 已读到默认 30s / 参数 100ms / 环境变量 170ms,context 仍为零。因此,即使连接层已经修复,实际 SILO 进程仍回退到 ReadTimeout=idle。 - -v2 已在相邻位置补一行赋值,未增加新选项或 YAML 字段。相同进程探针显示:v1 参数和环境变量各设置 100ms 时,400ms 慢头均返回 200;v2 两种入口均拒绝该请求,随后的健康检查仍返回 200。 - -## 已完成的连接层验证 - -| 验证 | 直接证据 | -|---|---| -| 原始 TCP/HTTP 100ms/400ms 对照,修复前失败、修复后两者均拒绝 | [baseline.log](evidence/baseline.log)、[original-reproducer-fixed.log](evidence/original-reproducer-fixed.log) | -| 头部持续滴入超过多个 250ms 更新周期、首个/后续请求,明文及 TLS | [darwin-race-final.log](evidence/darwin-race-final.log) | -| Content-Length/chunked/100-continue 持续上传、空闲 body、提前关闭及下一请求、读完 body 后长期处理 | [darwin-race-final.log](evidence/darwin-race-final.log) | -| keep-alive、pipelined 缓冲请求、用户 ConnState、hijack/unwrap、TLS 握手读取 | [darwin-race-final.log](evidence/darwin-race-final.log) | -| 强制仅协商 h2,断言 HTTP/2.0;原生流超时、并发健康流及连接复用 | [darwin-race-final.log](evidence/darwin-race-final.log) | -| macOS/arm64 Go1.27.1:完整两个修改包的 race | [darwin-race-final.log](evidence/darwin-race-final.log) | -| Linux/arm64 Docker Go1.27.1:完整两个修改包的 race,包含可选 DriveOPTimeout 拨号路径 | [linux-race-final.log](evidence/linux-race-final.log) | -| 默认 idle=30s,明文/TLS 的持续上传、下载均用时约 33s 并完成 | [default-30s-transfers.log](evidence/default-30s-transfers.log) | -| grid 实际 roundtrip/disconnect,go vet | [grid.log](evidence/grid.log)、[vet-final.log](evidence/vet-final.log) | - -Linux 使用已有本地 `golang:1.27.1-bookworm` arm64 镜像 `sha256:648f440f42a0958804efb24df176f806f9d353b41f1c0627f666428e40310f6b`。临时容器仅只读挂载本工作区与 Go module cache;没有发布端口,结束即删除。 - -## 配置与真实进程验证 - -- [config-baseline.log](evidence/config-baseline.log):实际 CLI → buildServerCtxt;默认、参数、环境、参数优先级、YAML 合并、零、负值。 -- [config-fixed.log](evidence/config-fixed.log):10 个常驻配置回归全部通过,既有 YAML 配置测试也通过。包含 idle=0/负值时默认 header=30s、fmt-gen 未注册该选项时仍安全返回零。 -- [runtime-v1.json](evidence/runtime-v1.json):v1 真实编译进程在参数/环境两种配置下均错误接受 400ms 慢头;包含二进制 SHA256。 -- [runtime-v2.json](evidence/runtime-v2.json):修复后同一探针,两种入口均拒绝慢头且服务健康。最终测试二进制 SHA256:`6b982de3262c25e326280c739b4275444cf80c3eacae48f0942aa18fbb7cd654`。 -- [quality-checks.json](evidence/quality-checks.json):执行命令、退出码、平台与日志哈希。`go mod tidy -diff`、`go vet`、scoped golangci-lint 与 diff 空白检查均通过,go.mod/go.sum 未改变。 -- [runtime_probe.py](evidence/runtime_probe.py):可复现的进程探针。只启动回环地址上的临时单盘服务器,使用临时测试凭据和数据,结束时终止自己的子进程。 - -## 补充验收的环境限制 - -- 额外 `go test -race ./cmd -run TestServerReadHeaderTimeoutConfig` 未进入测试:Darwin 链接器报 `errno=28 (No space left on device)`。这与已经通过的两个网络包 macOS/Linux race、普通 cmd 配置测试不同,不能混为通过。 -- 为检验已有 `buildscripts/test-timeout.sh`,制作了临时隔离副本:将全局 pkill 换为只终止自己的 PID、监听地址限于回环、测试末尾清理;使用单独 mcli 配置目录与 BSD nc 的私有 netcat 名称,原三组请求/断言未改。脚本在正常 PUT 阶段未形成对象,整体退出 255,**未通过**。 -- 后续立即上传 30 字节的独立诊断明确返回 **HTTP 507 / XMinioStorageFull**,消息为已达到最小空闲空间阈值:[s3-capacity-probe.json](evidence/s3-capacity-probe.json)。这条补充脚本不能在当前宿主容量条件下用于证明 S3 持久化验收,也不是 deadline 回归证据。 -- 原脚本与隔离改动、命令、客户端版本、运行时长和退出码见 [isolation patch](evidence/legacy-timeout-isolation.patch)、[metadata](evidence/legacy-timeout.metadata.json)、[log](evidence/legacy-timeout.log)。v2 编译进程的健康端点慢头验证已独立通过。 -- 按父任务确认,无需为这两项非阻断补充检查继续大编译或等待;已清理本任务已结束的 v1 旧二进制约 126MiB;保留原哈希、源码方案、日志与 v2 二进制。未清理共享缓存或其他任务数据:[cleanup.json](evidence/cleanup.json)。 - -## 配置语义与兼容边界 - -| 配置 | 完整修复后的含义 | -|---|---| -| 默认 idle=30s / header=30s | 有效时限仍为 30s,持续滴入头部现在也受绝对上限约束 | -| 只设 idle=2s | header 独立采用其默认 30s;相对只做 v1 的 2s 回退上限有所放宽,相对原先无限续期则建立了正确上限 | -| header 正值 | 按该值限制 HTTP/1 头部;该值还参与 Go 的最小正 TLS 握手时限,包括 h2 握手 | -| header=0 | 按 Go 规则回退到 ReadTimeout(此服务设为 idle) | -| header<0 | 显式取消 Go 的 header 绝对上限;首个明文请求仍保留原有 socket idle 续期,TLS/keep-alive 因 Go 显式清零而无 header 上限 | -| idle=0/负值,header 未设置 | 独立的默认 header=30s 现在正确生效 | - -## 交付说明 - -- 请求头和 TLS 握手的读取现在遵守绝对上限,即使连接持续有少量字节到达也会到期;这是修复后的预期可见变化。[Go Server 定义](https://pkg.go.dev/net/http#Server) -- HTTP/1 长上传、长下载保留滚动 idle;idle 仍有原有最多约 250ms 的更新时间余量,绝对 header 上限没有这项余量。 -- HTTP/2 保留现有原生 per-stream 超时行为;其原有总时长限制不属于本次修复。 -- TLS 握手**写**侧仍沿用滚动截止,这个既有边界单列保留,不能将本报告称为完整 TLS 握手资源限制修复。 -- 未改磁盘格式、对象元数据、存量状态、依赖或协议。回滚为撤销本次源代码改动并重新构建;本地证据不是生产部署验收。 -- 大范围仓库 CI、主干合并、版本发布、镜像和部署是后续独立交付步骤。本任务只做本地修复与针对性验证。 - -## 已纠正的测试夹具问题 - -初始 H2 客户端按服务端 ALPN 顺序落回 H1,协议断言正确使测试失败;改用仅 h2 的 TLS 拨号。之后 H2 同时启动同期限读写定时器,谁先到期会改变 body 错误包装;最终夹具在 PUT 内清除写定时器以独立验证读超时,并验证其他流与同一连接存活。无数据竞争报告。 - -配置夹具最初用了错误的变量名,随后发现“环境变量不存在”与“存在但为空”的 CLI 语义不同;修正夹具后才把非零 context 丢失作为缺陷证据。相关初始失败日志保留,不作为产品回归或通过证据。 - -新增 H2 测试曾直接引用 x/net/http2,Opus 实现评审因此提出 tidy 门禁阻断项。最终改用标准库 HTTP/2-only Protocols,不引入新直接依赖;v2 Opus 明确认定该阻断项已经解决,实际 tidy 检查也通过。重复 clamp 与 h2 宽裕 keep-alive 的改进在两平台最终 race 中通过。 - -## 最终交付状态 - -| 环节 | 状态 | -|---|---| -| 研究与最小兼容方案 | 完成;同时确认连接续期覆盖和配置传递遗漏 | -| 真实 Opus 5.0 max 共识 | 完成;完整 v2 同一 SHA256,零阻断分歧 | -| 实现评审链 | v1 REQUEST_CHANGES 的测试依赖 B1 已修复,v2 实际评审明确认定已解决 | -| 本地实现 | 完成,绑定此提交内源文件哈希 | -| 必要验证 | 配置、真实进程 CLI/env、TCP/TLS、长传输、keep-alive、h2、共享调用方及两平台网络包 race 完成 | -| 补充 cmd race / 旧 S3 脚本 | 环境未完成,分别为链接 ENOSPC 与 HTTP 507 最小空闲阈值 | -| 远端推送 / PR / 合并 / 发布 / 部署 | 未执行 | - -工作量估算仍为原计划的 1–3 工程师日级别;本轮实际完成方案、两次方案共识、一次实现复核和上述本地验证。未宣称全仓 CI 或生产验收通过。 diff --git a/docs/investigations/r8/consensus.md b/docs/investigations/r8/consensus.md deleted file mode 100644 index 5f78a8d20..000000000 --- a/docs/investigations/r8/consensus.md +++ /dev/null @@ -1,51 +0,0 @@ -# R8 consensus and dispositions - -Recorded UTC: 2026-09-15T15:50:55.911352+00:00 - -## Same-version agreement, before implementation - -- Baseline: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`. -- Accepted plan: `plan-v1.md`, SHA256 `7cb609e37e3199ecd93c992f08d123968ba8082682a108f20490e0644d735366`. The plan is retained verbatim. -- Codex recommends option 4. Real Claude Code assistant messages identify `claude-opus-5`; invocation explicitly supplied `--effort max`. -- Opus verdict: **CONSENSUS**, explicitly names v1 and this SHA256; zero blocking disagreements. Successful result, no rate limit or substitute reviewer. See `review/opus-v1.md` and metadata. Opus independently read code, did not run tests or recalculate the supplied hash. Codex independently recalculated the hash here and ran the baseline. -- Codex accepts the conclusion and the following dispositions. This record authorizes the local implementation stage under the user's WORKFLOW.md. No production files have been edited at the time this record is created. -- Raw session: `/Users/vonng/tmp/silo-r8-01a0a5b9/opus-v1.jsonl`; stderr adjacent. The reviewer tried an unavailable Write tool and then delivered its review in text; no file edit was granted or used as evidence. - -## Nonblocking opinions, individually resolved - -| ID | Codex disposition | -|---|---| -| N1 | Accept precise Go source ordering: header deadlines are set in `serve` at 2038/2177; whole-request deadline at 1103 is unconditional. v1 already describes header reading separately from the latter; no algorithm change needed. | -| N2 | Accept: add pipelined requests. StateActive follows successful parsing even when all bytes were buffered, because readRequest resets the read limit. | -| N3 | Retain explicit negotiated-h2 skip as v1 permits. StateNew still enables strict before the handshake; h2 detection only governs later hooks. No connection-level stream timeout added. | -| N4 | Retain listener initial strictness and Init's phase hook, as specified. Document that the private listener and Server.Init cooperate; direct raw-listener users without the hook get strict behavior for the entire request. There is no such production caller today. | -| N5 | Accept, independently reproduced: the initial H2 smoke fell back to HTTP/1.1 and failed its protocol assertion. Fix the fixture to dial TLS advertising only h2; assert negotiated h2 and response HTTP/2.0. This fixture failure is retained in baseline-additional.log and is not a product defect. | -| N6 | Accept: add >30s continuously progressing downloads through plaintext and TLS H1; retain write logic. Unit tests assert strict reads do not change rolling writes. | -| N7 | Accept: delivery notes will state that trickling headers and read-side TLS handshakes now stop at the configured absolute cap, even when socket idle is not exceeded. | -| N8 | Accept: current globalTCPOptions leaves DriveOPTimeout commented out; the optional Linux dial path remains a shared API caller and will be tested explicitly in Linux, without changing production config. | -| N9 | Accept as a known remaining boundary: TLS handshake writes retain rolling behavior. Fixing this requires separate write/handshake phase design and is outside the R8 read-header defect. | - -No substantive plan revision is required. Added tests are acceptance refinements consistent with v1, not changes to the agreed production behavior. - -## Complete v2 consensus — before configuration implementation - -Recorded UTC: 2026-09-15T16:07:48.799416+00:00 - -- Accepted **complete plan v2**, SHA256 `426127ed9fb08aeddf8259ebdc4b1c24ebec8cda751a970ed99338a44b065f4c`, independently recalculated here. V1 remains historical agreement only. -- Actual assistant model: `claude-opus-5`; CLI explicitly `--effort max`; successful response, **CONSENSUS**, zero blocking disagreements. Opus explicitly named the complete v2 hash and the new assignment. Codex agrees with the complete v2 plan and the dispositions below. Configuration production code is still unmodified at this record's creation. -- Opus explicitly confirms the implementation-review B1 dependency blocker is resolved by the standard-library-only HTTP/2 fixture. `go mod tidy -diff` independently returned exit 0. -- Reviewer source inspection is separate from execution; all actual tests and binary hashes are Codex-produced evidence. See `review/opus-v2.md` and metadata for the original opinion and raw log identity. - -| V2 note | Codex disposition | -|---|---| -| N1 | Accept: document that a positive ReadHeaderTimeout also participates in the standard-library minimum TLS handshake window, including h2's handshake. | -| N2 | Accept: with only idle=2s customized, v1's fallback cap was 2s; complete v2 honors default header=30s. The independent header knob is intentional. Socket inactivity is still constrained under the retained rules. | -| N3 | Accept: explicitly document negative header values and the inherited first-plaintext vs TLS/keep-alive zero-deadline distinction. This is an explicit opt-out of the header cap; no unagreed policy change. | -| N4 | Accept: add parser cases for disabled idle with independent default header=30s, and include disabled-idle coverage in process probing if practical. | -| N5 | Accept evaluation: inspect the existing buildscripts/test-timeout.sh for safe isolated execution. It complements, but does not replace, the new trickle and renewal cases. Record whether run and its exact scope. | -| N6 | Accept: land configuration assertions as a permanent cmd test; zero case remains a compatibility control, not defect-discriminating evidence. | -| N7 | Accept: fmt-gen's absent duration flag returns zero and does not launch HTTP; no extra flag or production change. | -| N8 | Accept: leave the duplicate existing UserTimeout assignment untouched. | -| N9 | Accept: final quality records include actual commands/exit codes; add repaired compiled-process output with binary SHA256. The old runtime-v1 output remains explicitly pre-binding evidence. | - -Implementation and verification now continue within v2. Merge, release and deployment remain outside automatic delivery. diff --git a/docs/investigations/r8/evidence/baseline-additional.log b/docs/investigations/r8/evidence/baseline-additional.log deleted file mode 100644 index 5586d820a..000000000 --- a/docs/investigations/r8/evidence/baseline-additional.log +++ /dev/null @@ -1,23 +0,0 @@ -=== RUN TestServerConnStateHook -=== RUN TestServerConnStateHook/tls=false -=== RUN TestServerConnStateHook/tls=true ---- PASS: TestServerConnStateHook (0.00s) - --- PASS: TestServerConnStateHook/tls=false (0.00s) - --- PASS: TestServerConnStateHook/tls=true (0.00s) -=== RUN TestServerHTTP2Deadlines - r8_compatibility_test.go:278: expected HTTP/2, got HTTP/1.1 - r8_compatibility_test.go:293: unexpected HTTP/1.1 204 No Content ---- FAIL: TestServerHTTP2Deadlines (0.00s) -=== RUN TestServerEarlyBodyClose -=== RUN TestServerEarlyBodyClose/tls=false -=== PAUSE TestServerEarlyBodyClose/tls=false -=== RUN TestServerEarlyBodyClose/tls=true -=== PAUSE TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=false -=== CONT TestServerEarlyBodyClose/tls=true ---- PASS: TestServerEarlyBodyClose (0.00s) - --- PASS: TestServerEarlyBodyClose/tls=false (0.30s) - --- PASS: TestServerEarlyBodyClose/tls=true (0.30s) -FAIL -FAIL github.com/minio/minio/internal/http 0.959s -FAIL diff --git a/docs/investigations/r8/evidence/baseline-compatibility.log b/docs/investigations/r8/evidence/baseline-compatibility.log deleted file mode 100644 index 08fd2fa2c..000000000 --- a/docs/investigations/r8/evidence/baseline-compatibility.log +++ /dev/null @@ -1,80 +0,0 @@ -=== RUN TestServerContinuousUpload -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=false - r8_compatibility_test.go:181: continuous upload 1.293813708s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=false - r8_compatibility_test.go:181: continuous upload 1.293742875s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=false - r8_compatibility_test.go:181: continuous upload 1.297742459s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=true - r8_compatibility_test.go:181: continuous upload 1.293691583s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=true - r8_compatibility_test.go:181: continuous upload 1.296975334s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=true - r8_compatibility_test.go:181: continuous upload 1.297091375s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=true - r8_compatibility_test.go:181: continuous upload 1.293960709s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=false - r8_compatibility_test.go:181: continuous upload 1.297880292s > idle 300ms ---- PASS: TestServerContinuousUpload (0.00s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=true (1.30s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=true (1.30s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=true (1.30s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=true (1.30s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=false (1.30s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=false (1.30s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=false (1.30s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=false (1.30s) -=== RUN TestServerIdleBodyDeadline -=== RUN TestServerIdleBodyDeadline/tls=false -=== PAUSE TestServerIdleBodyDeadline/tls=false -=== RUN TestServerIdleBodyDeadline/tls=true -=== PAUSE TestServerIdleBodyDeadline/tls=true -=== CONT TestServerIdleBodyDeadline/tls=false -=== CONT TestServerIdleBodyDeadline/tls=true ---- PASS: TestServerIdleBodyDeadline (0.00s) - --- PASS: TestServerIdleBodyDeadline/tls=false (0.45s) - --- PASS: TestServerIdleBodyDeadline/tls=true (0.45s) -=== RUN TestServerBackgroundReadNoDeadline -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=true -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=true ---- PASS: TestServerBackgroundReadNoDeadline (0.00s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=false (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=true (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=false (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=true (0.70s) -PASS -ok github.com/minio/minio/internal/http 2.983s diff --git a/docs/investigations/r8/evidence/baseline.log b/docs/investigations/r8/evidence/baseline.log deleted file mode 100644 index 96af0f017..000000000 --- a/docs/investigations/r8/evidence/baseline.log +++ /dev/null @@ -1,11 +0,0 @@ -=== RUN TestReviewR8AbsoluteHeaderTimeout -=== RUN TestReviewR8AbsoluteHeaderTimeout/standard-net-http - r8_baseline_test.go:56: request rejected after header timeout: unexpected EOF -=== RUN TestReviewR8AbsoluteHeaderTimeout/silo-listener - r8_baseline_test.go:60: 100ms request-header deadline accepted header completed after 400ms: HTTP 204 ---- FAIL: TestReviewR8AbsoluteHeaderTimeout (0.80s) - --- PASS: TestReviewR8AbsoluteHeaderTimeout/standard-net-http (0.40s) - --- FAIL: TestReviewR8AbsoluteHeaderTimeout/silo-listener (0.40s) -FAIL -FAIL github.com/minio/minio/internal/http 1.477s -FAIL diff --git a/docs/investigations/r8/evidence/baseline_test.go.txt b/docs/investigations/r8/evidence/baseline_test.go.txt deleted file mode 100644 index 67b65a7dd..000000000 --- a/docs/investigations/r8/evidence/baseline_test.go.txt +++ /dev/null @@ -1,63 +0,0 @@ -package http - -import ( - "bufio" - "context" - "io" - "net" - stdhttp "net/http" - "testing" - "time" -) - -func TestReviewR8AbsoluteHeaderTimeout(t *testing.T) { - for _, wrapped := range []bool{false, true} { - name := "standard-net-http" - if wrapped { - name = "silo-listener" - } - t.Run(name, func(t *testing.T) { - handler := stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) { w.WriteHeader(204) }) - var addr string - if wrapped { - srv := NewServer([]string{"127.0.0.1:0"}).UseHandler(handler). - UseTCPOptions(TCPOptions{IdleTimeout: 2 * time.Second}). - UseReadHeaderTimeout(100 * time.Millisecond).UseReadTimeout(2 * time.Second).UseWriteTimeout(2 * time.Second) - serve, err := srv.Init(context.Background(), func(_ string, err error) { t.Error(err) }) - if err != nil { - t.Fatal(err) - } - addr = srv.listener.Addr().String() - go serve() - defer srv.Server.Close() - } else { - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - srv := &stdhttp.Server{Handler: handler, ReadHeaderTimeout: 100 * time.Millisecond, ReadTimeout: 2 * time.Second, WriteTimeout: 2 * time.Second} - addr = ln.Addr().String() - go srv.Serve(ln) - defer srv.Close() - } - conn, err := net.DialTimeout("tcp", addr, time.Second) - if err != nil { - t.Fatal(err) - } - defer conn.Close() - conn.SetDeadline(time.Now().Add(3 * time.Second)) - if _, err := io.WriteString(conn, "GET / HTTP/1.1\r\nHost: localhost\r\nX-Slow: "); err != nil { - t.Fatal(err) - } - time.Sleep(400 * time.Millisecond) - io.WriteString(conn, "done\r\nConnection: close\r\n\r\n") - resp, err := stdhttp.ReadResponse(bufio.NewReader(conn), nil) - if err != nil { - t.Logf("request rejected after header timeout: %v", err) - return - } - defer resp.Body.Close() - t.Errorf("100ms request-header deadline accepted header completed after 400ms: HTTP %d", resp.StatusCode) - }) - } -} diff --git a/docs/investigations/r8/evidence/build-v1.log b/docs/investigations/r8/evidence/build-v1.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r8/evidence/build-v2.log b/docs/investigations/r8/evidence/build-v2.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r8/evidence/cleanup.json b/docs/investigations/r8/evidence/cleanup.json deleted file mode 100644 index 4e9a2db84..000000000 --- a/docs/investigations/r8/evidence/cleanup.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "timestamp_utc": "2026-09-15T16:14:25.042607+00:00", - "reason": "parent requested cleanup of completed task-owned disposable artifacts; baseline runtime evidence and hash retained", - "deleted": [ - { - "path": "/Users/vonng/tmp/silo-r8-01a0a5b9/silo-v1", - "size": 132390258, - "sha256": "ed8d30cb40832f854bd82b083b9d1ec3ee16a35598416a54ef2d48bde8384ffc" - } - ] -} diff --git a/docs/investigations/r8/evidence/config-baseline-env-fixture-failure.log b/docs/investigations/r8/evidence/config-baseline-env-fixture-failure.log deleted file mode 100644 index dba96eb2f..000000000 --- a/docs/investigations/r8/evidence/config-baseline-env-fixture-failure.log +++ /dev/null @@ -1,28 +0,0 @@ -=== RUN TestServerReadHeaderTimeoutConfig -=== RUN TestServerReadHeaderTimeoutConfig/default - r8_config_test.go:31: could not parse as duration for flag read-header-timeout: time: invalid duration "" -=== RUN TestServerReadHeaderTimeoutConfig/flag - r8_config_test.go:31: could not parse as duration for flag read-header-timeout: time: invalid duration "" -=== RUN TestServerReadHeaderTimeoutConfig/environment - r8_config_test.go:28: CLI read-header-timeout=170ms, expected=170ms - r8_config_test.go:33: parsed ReadHeaderTimeout = 0s, want 170ms -=== RUN TestServerReadHeaderTimeoutConfig/flag-over-environment - r8_config_test.go:28: CLI read-header-timeout=80ms, expected=80ms - r8_config_test.go:33: parsed ReadHeaderTimeout = 0s, want 80ms -=== RUN TestServerReadHeaderTimeoutConfig/yaml-retains-flag - r8_config_test.go:31: could not parse as duration for flag read-header-timeout: time: invalid duration "" -=== RUN TestServerReadHeaderTimeoutConfig/zero-fallback - r8_config_test.go:31: could not parse as duration for flag read-header-timeout: time: invalid duration "" -=== RUN TestServerReadHeaderTimeoutConfig/negative-disabled - r8_config_test.go:31: could not parse as duration for flag read-header-timeout: time: invalid duration "" ---- FAIL: TestServerReadHeaderTimeoutConfig (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/default (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/flag (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/environment (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/flag-over-environment (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/yaml-retains-flag (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/zero-fallback (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/negative-disabled (0.00s) -FAIL -FAIL github.com/minio/minio/cmd 1.629s -FAIL diff --git a/docs/investigations/r8/evidence/config-baseline-initial-fixture-failure.log b/docs/investigations/r8/evidence/config-baseline-initial-fixture-failure.log deleted file mode 100644 index f9b8f2cb5..000000000 --- a/docs/investigations/r8/evidence/config-baseline-initial-fixture-failure.log +++ /dev/null @@ -1,4 +0,0 @@ -# github.com/minio/minio/cmd [github.com/minio/minio/cmd.test] -../../../../tmp/silo-r8-01a0a5b9/config_deadline_test.go:26:52: undefined: serverFlags -FAIL github.com/minio/minio/cmd [build failed] -FAIL diff --git a/docs/investigations/r8/evidence/config-baseline.log b/docs/investigations/r8/evidence/config-baseline.log deleted file mode 100644 index 5348bc1c7..000000000 --- a/docs/investigations/r8/evidence/config-baseline.log +++ /dev/null @@ -1,32 +0,0 @@ -=== RUN TestServerReadHeaderTimeoutConfig -=== RUN TestServerReadHeaderTimeoutConfig/default - r8_config_test.go:30: CLI read-header-timeout=30s, expected=30s - r8_config_test.go:35: parsed ReadHeaderTimeout = 0s, want 30s -=== RUN TestServerReadHeaderTimeoutConfig/flag - r8_config_test.go:30: CLI read-header-timeout=100ms, expected=100ms - r8_config_test.go:35: parsed ReadHeaderTimeout = 0s, want 100ms -=== RUN TestServerReadHeaderTimeoutConfig/environment - r8_config_test.go:30: CLI read-header-timeout=170ms, expected=170ms - r8_config_test.go:35: parsed ReadHeaderTimeout = 0s, want 170ms -=== RUN TestServerReadHeaderTimeoutConfig/flag-over-environment - r8_config_test.go:30: CLI read-header-timeout=80ms, expected=80ms - r8_config_test.go:35: parsed ReadHeaderTimeout = 0s, want 80ms -=== RUN TestServerReadHeaderTimeoutConfig/yaml-retains-flag - r8_config_test.go:30: CLI read-header-timeout=100ms, expected=100ms - r8_config_test.go:35: parsed ReadHeaderTimeout = 0s, want 100ms -=== RUN TestServerReadHeaderTimeoutConfig/zero-fallback - r8_config_test.go:30: CLI read-header-timeout=0s, expected=0s -=== RUN TestServerReadHeaderTimeoutConfig/negative-disabled - r8_config_test.go:30: CLI read-header-timeout=-1s, expected=-1s - r8_config_test.go:35: parsed ReadHeaderTimeout = 0s, want -1s ---- FAIL: TestServerReadHeaderTimeoutConfig (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/default (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/flag (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/environment (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/flag-over-environment (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/yaml-retains-flag (0.00s) - --- PASS: TestServerReadHeaderTimeoutConfig/zero-fallback (0.00s) - --- FAIL: TestServerReadHeaderTimeoutConfig/negative-disabled (0.00s) -FAIL -FAIL github.com/minio/minio/cmd 1.500s -FAIL diff --git a/docs/investigations/r8/evidence/config-fixed.log b/docs/investigations/r8/evidence/config-fixed.log deleted file mode 100644 index b224d59b8..000000000 --- a/docs/investigations/r8/evidence/config-fixed.log +++ /dev/null @@ -1,36 +0,0 @@ -=== RUN TestServerConfigFile -=== RUN TestServerConfigFile/testdata/config/1.yaml -=== RUN TestServerConfigFile/testdata/config/2.yaml -=== RUN TestServerConfigFile/testdata/config/invalid.yaml -=== RUN TestServerConfigFile/testdata/config/invalid-types.yaml -=== RUN TestServerConfigFile/testdata/config/invalid-disks.yaml ---- PASS: TestServerConfigFile (0.00s) - --- PASS: TestServerConfigFile/testdata/config/1.yaml (0.00s) - --- PASS: TestServerConfigFile/testdata/config/2.yaml (0.00s) - --- PASS: TestServerConfigFile/testdata/config/invalid.yaml (0.00s) - --- PASS: TestServerConfigFile/testdata/config/invalid-types.yaml (0.00s) - --- PASS: TestServerConfigFile/testdata/config/invalid-disks.yaml (0.00s) -=== RUN TestServerReadHeaderTimeoutConfig -=== RUN TestServerReadHeaderTimeoutConfig/default -=== RUN TestServerReadHeaderTimeoutConfig/flag -=== RUN TestServerReadHeaderTimeoutConfig/environment -=== RUN TestServerReadHeaderTimeoutConfig/flag-over-environment -=== RUN TestServerReadHeaderTimeoutConfig/yaml-retains-flag -=== RUN TestServerReadHeaderTimeoutConfig/zero-fallback -=== RUN TestServerReadHeaderTimeoutConfig/zero-idle-default-header -=== RUN TestServerReadHeaderTimeoutConfig/negative-idle-default-header -=== RUN TestServerReadHeaderTimeoutConfig/fmt-gen-unregistered-duration -=== RUN TestServerReadHeaderTimeoutConfig/negative-disabled ---- PASS: TestServerReadHeaderTimeoutConfig (0.06s) - --- PASS: TestServerReadHeaderTimeoutConfig/default (0.01s) - --- PASS: TestServerReadHeaderTimeoutConfig/flag (0.00s) - --- PASS: TestServerReadHeaderTimeoutConfig/environment (0.00s) - --- PASS: TestServerReadHeaderTimeoutConfig/flag-over-environment (0.01s) - --- PASS: TestServerReadHeaderTimeoutConfig/yaml-retains-flag (0.00s) - --- PASS: TestServerReadHeaderTimeoutConfig/zero-fallback (0.04s) - --- PASS: TestServerReadHeaderTimeoutConfig/zero-idle-default-header (0.00s) - --- PASS: TestServerReadHeaderTimeoutConfig/negative-idle-default-header (0.00s) - --- PASS: TestServerReadHeaderTimeoutConfig/fmt-gen-unregistered-duration (0.00s) - --- PASS: TestServerReadHeaderTimeoutConfig/negative-disabled (0.00s) -PASS -ok github.com/minio/minio/cmd 2.462s diff --git a/docs/investigations/r8/evidence/config-race.log b/docs/investigations/r8/evidence/config-race.log deleted file mode 100644 index d1264b4b4..000000000 --- a/docs/investigations/r8/evidence/config-race.log +++ /dev/null @@ -1,8 +0,0 @@ -# github.com/minio/minio/cmd.test -/opt/homebrew/Cellar/go/1.27.1/libexec/pkg/tool/darwin_arm64/link: running cc failed: exit status 1 -/usr/bin/cc -arch arm64 -Wl,-S -Wl,-x -Wl,-U,__dyld_get_dyld_header -o $WORK/b001/cmd.test -Qunused-arguments /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/go.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000000.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000001.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000002.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000003.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000004.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000005.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000006.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000007.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000008.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000009.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000010.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000011.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000012.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000013.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000014.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000015.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000016.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000017.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000018.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000019.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000020.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000021.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000022.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000023.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000024.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000025.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000026.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000027.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000028.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000029.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000030.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000031.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000032.o /var/folders/df/bfm8q07d7bv3kpjf1fjchq4m0000gn/T/go-link-2408312831/000033.o -lresolv -O2 -g -O2 -g -O2 -g -O2 -g -framework CoreFoundation -framework IOKit -O2 -g -framework IOKit -O2 -g -framework CoreFoundation -framework Security -O2 -g -framework CoreServices -O2 -g -O2 -g -framework CoreFoundation -framework IOKit -O2 -g -framework CoreFoundation -framework CFNetwork -framework CoreFoundation -framework CFNetwork -ld: write() failed, errno=28 (No space left on device) -clang: error: linker command failed with exit code 1 (use -v to see invocation) - -FAIL github.com/minio/minio/cmd [build failed] -FAIL diff --git a/docs/investigations/r8/evidence/config_baseline_test.go.txt b/docs/investigations/r8/evidence/config_baseline_test.go.txt deleted file mode 100644 index 6d8aa847e..000000000 --- a/docs/investigations/r8/evidence/config_baseline_test.go.txt +++ /dev/null @@ -1,39 +0,0 @@ -package cmd - -import ( - "os" - "testing" - "time" - - "github.com/minio/cli" - xhttp "github.com/minio/minio/internal/http" -) - -func TestServerReadHeaderTimeoutConfig(t *testing.T) { - for _,tc:=range []struct{name,env string;args []string;want time.Duration}{ - {name:"default",want:xhttp.DefaultReadHeaderTimeout}, - {name:"flag",args:[]string{"--read-header-timeout=100ms"},want:100*time.Millisecond}, - {name:"environment",env:"170ms",want:170*time.Millisecond}, - {name:"flag-over-environment",env:"170ms",args:[]string{"--read-header-timeout=80ms"},want:80*time.Millisecond}, - {name:"yaml-retains-flag",args:[]string{"--config=testdata/config/1.yaml","--read-header-timeout=100ms"},want:100*time.Millisecond}, - {name:"zero-fallback",args:[]string{"--read-header-timeout=0s"}}, - {name:"negative-disabled",args:[]string{"--read-header-timeout=-1s"},want:-time.Second}, - }{ - t.Run(tc.name,func(t *testing.T){ - for _,key:=range []string{"MINIO_ARGS","MINIO_VOLUMES","MINIO_ENDPOINTS","MINIO_CONFIG","MINIO_ERASURE_SET_DRIVE_COUNT"}{t.Setenv(key,"")} - t.Setenv("MINIO_READ_HEADER_TIMEOUT",tc.env) - if tc.env==""{if err:=os.Unsetenv("MINIO_READ_HEADER_TIMEOUT");err!=nil{t.Fatal(err)}} - t.Setenv("MINIO_IDLE_TIMEOUT","2s") - var got serverCtxt - called:=false - app:=cli.NewApp() - app.Commands=[]cli.Command{{Name:"server",Flags:serverCmd.Flags,Action:func(ctx *cli.Context)error{called=true;t.Logf("CLI read-header-timeout=%s, expected=%s",ctx.Duration("read-header-timeout"),tc.want);return buildServerCtxt(ctx,&got)}}} - args:=append([]string{"silo","server"},tc.args...) - args=append(args,t.TempDir()) - if err:=app.Run(args);err!=nil{t.Fatal(err)} - if !called{t.Fatal("server action did not run")} - if got.ReadHeaderTimeout!=tc.want{t.Errorf("parsed ReadHeaderTimeout = %s, want %s",got.ReadHeaderTimeout,tc.want)} - if got.IdleTimeout!=2*time.Second{t.Errorf("parsed IdleTimeout = %s, want 2s",got.IdleTimeout)} - }) - } -} diff --git a/docs/investigations/r8/evidence/darwin-race-final.log b/docs/investigations/r8/evidence/darwin-race-final.log deleted file mode 100644 index 0bf4196f6..000000000 --- a/docs/investigations/r8/evidence/darwin-race-final.log +++ /dev/null @@ -1,213 +0,0 @@ -=== RUN TestStrictReadDeadline -=== RUN TestStrictReadDeadline/SetReadDeadline -=== RUN TestStrictReadDeadline/SetDeadline ---- PASS: TestStrictReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetDeadline (0.00s) -=== RUN TestDefaultReadDeadlineStillRenews ---- PASS: TestDefaultReadDeadlineStillRenews (0.00s) -=== RUN TestStrictExpiredFutureReadDeadline ---- PASS: TestStrictExpiredFutureReadDeadline (0.06s) -=== RUN TestConcurrentStrictReadDeadline ---- PASS: TestConcurrentStrictReadDeadline (0.00s) -=== RUN TestStrictReadDeadlineRepeatedRenewal ---- PASS: TestStrictReadDeadlineRepeatedRenewal (0.91s) -=== RUN TestBuffConnReadTimeout ---- PASS: TestBuffConnReadTimeout (3.04s) -=== RUN TestBuffConnReadCheckTimeout ---- PASS: TestBuffConnReadCheckTimeout (0.50s) -PASS -ok github.com/minio/minio/internal/deadlineconn 5.927s -=== RUN TestCheckPortAvailability - check_port_test.go:31: ---- SKIP: TestCheckPortAvailability (0.00s) -=== RUN TestNewHTTPListener ---- PASS: TestNewHTTPListener (0.04s) -=== RUN TestHTTPListenerStartClose ---- PASS: TestHTTPListenerStartClose (0.03s) -=== RUN TestHTTPListenerAddr ---- PASS: TestHTTPListenerAddr (0.01s) -=== RUN TestHTTPListenerAddrs ---- PASS: TestHTTPListenerAddrs (0.02s) -=== RUN TestServerReadHeaderDeadline -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=true ---- PASS: TestServerReadHeaderDeadline (0.00s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=true (0.82s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=true (0.85s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=false (0.96s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=false (0.98s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=true (1.57s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=true (1.59s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=false (1.71s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=false (1.73s) -=== RUN TestServerKeepAliveDeadline -=== RUN TestServerKeepAliveDeadline/tls=false -=== PAUSE TestServerKeepAliveDeadline/tls=false -=== RUN TestServerKeepAliveDeadline/tls=true -=== PAUSE TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=false -=== CONT TestServerKeepAliveDeadline/tls=true ---- PASS: TestServerKeepAliveDeadline (0.00s) - --- PASS: TestServerKeepAliveDeadline/tls=false (1.52s) - --- PASS: TestServerKeepAliveDeadline/tls=true (1.53s) -=== RUN TestServerContinuousUpload -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=false - server_deadline_test.go:261: continuous upload 1.312965334s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=true - server_deadline_test.go:261: continuous upload 1.30790825s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=false - server_deadline_test.go:261: continuous upload 1.32285075s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=true - server_deadline_test.go:261: continuous upload 1.323004708s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=true - server_deadline_test.go:261: continuous upload 1.322070417s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=false - server_deadline_test.go:261: continuous upload 1.323585625s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=false - server_deadline_test.go:261: continuous upload 1.323022875s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=true - server_deadline_test.go:261: continuous upload 1.323094875s > idle 300ms ---- PASS: TestServerContinuousUpload (0.00s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=false (1.31s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=true (1.31s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=true (1.33s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=false (1.33s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=true (1.34s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=false (1.34s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=false (1.34s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=true (1.34s) -=== RUN TestServerDefaultIdleLongUpload - server_deadline_test.go:270: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle regression ---- SKIP: TestServerDefaultIdleLongUpload (0.00s) -=== RUN TestServerIdleBodyDeadline -=== RUN TestServerIdleBodyDeadline/tls=false -=== PAUSE TestServerIdleBodyDeadline/tls=false -=== RUN TestServerIdleBodyDeadline/tls=true -=== PAUSE TestServerIdleBodyDeadline/tls=true -=== CONT TestServerIdleBodyDeadline/tls=false -=== CONT TestServerIdleBodyDeadline/tls=true ---- PASS: TestServerIdleBodyDeadline (0.00s) - --- PASS: TestServerIdleBodyDeadline/tls=false (0.45s) - --- PASS: TestServerIdleBodyDeadline/tls=true (0.47s) -=== RUN TestServerBackgroundReadNoDeadline -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=true -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=false ---- PASS: TestServerBackgroundReadNoDeadline (0.00s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=true (0.71s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=false (0.71s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=false (0.71s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=true (0.72s) -=== RUN TestServerTLSHandshakeReadDeadline -2026/09/16 00:03:51 http: TLS handshake error from 127.0.0.1:57963: read tcp 127.0.0.1:57962->127.0.0.1:57963: i/o timeout ---- PASS: TestServerTLSHandshakeReadDeadline (0.20s) -=== RUN TestServerConnStateHook -=== RUN TestServerConnStateHook/tls=false -=== RUN TestServerConnStateHook/tls=true ---- PASS: TestServerConnStateHook (0.02s) - --- PASS: TestServerConnStateHook/tls=false (0.00s) - --- PASS: TestServerConnStateHook/tls=true (0.02s) -=== RUN TestServerHTTP2Deadlines ---- PASS: TestServerHTTP2Deadlines (0.43s) -=== RUN TestServerEarlyBodyClose -=== RUN TestServerEarlyBodyClose/tls=false -=== PAUSE TestServerEarlyBodyClose/tls=false -=== RUN TestServerEarlyBodyClose/tls=true -=== PAUSE TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=false -=== CONT TestServerEarlyBodyClose/tls=true ---- PASS: TestServerEarlyBodyClose (0.00s) - --- PASS: TestServerEarlyBodyClose/tls=false (0.30s) - --- PASS: TestServerEarlyBodyClose/tls=true (0.31s) -=== RUN TestServerPipelinedDeadline -=== RUN TestServerPipelinedDeadline/tls=false -=== RUN TestServerPipelinedDeadline/tls=true ---- PASS: TestServerPipelinedDeadline (1.31s) - --- PASS: TestServerPipelinedDeadline/tls=false (0.65s) - --- PASS: TestServerPipelinedDeadline/tls=true (0.66s) -=== RUN TestServerHijackedDeadline -=== RUN TestServerHijackedDeadline/tls=false -=== PAUSE TestServerHijackedDeadline/tls=false -=== RUN TestServerHijackedDeadline/tls=true -=== PAUSE TestServerHijackedDeadline/tls=true -=== CONT TestServerHijackedDeadline/tls=true -=== CONT TestServerHijackedDeadline/tls=false ---- PASS: TestServerHijackedDeadline (0.00s) - --- PASS: TestServerHijackedDeadline/tls=false (0.70s) - --- PASS: TestServerHijackedDeadline/tls=true (0.71s) -=== RUN TestServerContinuousDownload -=== RUN TestServerContinuousDownload/tls=false -=== PAUSE TestServerContinuousDownload/tls=false -=== RUN TestServerContinuousDownload/tls=true -=== PAUSE TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=false - server_deadline_test.go:619: continuous download 1.2963195s > idle 300ms -=== NAME TestServerContinuousDownload/tls=true - server_deadline_test.go:619: continuous download 1.295400042s > idle 300ms ---- PASS: TestServerContinuousDownload (0.00s) - --- PASS: TestServerContinuousDownload/tls=false (1.30s) - --- PASS: TestServerContinuousDownload/tls=true (1.31s) -=== RUN TestServerDefaultIdleLongDownload - server_deadline_test.go:626: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle transfer regressions ---- SKIP: TestServerDefaultIdleLongDownload (0.00s) -=== RUN TestNewServer ---- PASS: TestNewServer (0.00s) -PASS -ok github.com/minio/minio/internal/http 11.654s diff --git a/docs/investigations/r8/evidence/darwin-race-initial-fixture-failure.log b/docs/investigations/r8/evidence/darwin-race-initial-fixture-failure.log deleted file mode 100644 index 0ecdc3200..000000000 --- a/docs/investigations/r8/evidence/darwin-race-initial-fixture-failure.log +++ /dev/null @@ -1,213 +0,0 @@ -=== RUN TestStrictReadDeadline -=== RUN TestStrictReadDeadline/SetReadDeadline -=== RUN TestStrictReadDeadline/SetDeadline ---- PASS: TestStrictReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetDeadline (0.00s) -=== RUN TestDefaultReadDeadlineStillRenews ---- PASS: TestDefaultReadDeadlineStillRenews (0.00s) -=== RUN TestStrictExpiredFutureReadDeadline ---- PASS: TestStrictExpiredFutureReadDeadline (0.06s) -=== RUN TestConcurrentStrictReadDeadline ---- PASS: TestConcurrentStrictReadDeadline (0.00s) -=== RUN TestBuffConnReadTimeout ---- PASS: TestBuffConnReadTimeout (3.02s) -=== RUN TestBuffConnReadCheckTimeout ---- PASS: TestBuffConnReadCheckTimeout (0.50s) -PASS -ok github.com/minio/minio/internal/deadlineconn 5.330s -=== RUN TestCheckPortAvailability - check_port_test.go:31: ---- SKIP: TestCheckPortAvailability (0.00s) -=== RUN TestNewHTTPListener ---- PASS: TestNewHTTPListener (0.06s) -=== RUN TestHTTPListenerStartClose ---- PASS: TestHTTPListenerStartClose (0.05s) -=== RUN TestHTTPListenerAddr ---- PASS: TestHTTPListenerAddr (0.01s) -=== RUN TestHTTPListenerAddrs ---- PASS: TestHTTPListenerAddrs (0.00s) -=== RUN TestServerReadHeaderDeadline -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=false ---- PASS: TestServerReadHeaderDeadline (0.00s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=true (0.41s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=true (0.43s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=false (0.60s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=false (0.62s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=true (0.82s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=true (0.82s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=false (1.01s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=false (1.02s) -=== RUN TestServerKeepAliveDeadline -=== RUN TestServerKeepAliveDeadline/tls=false -=== PAUSE TestServerKeepAliveDeadline/tls=false -=== RUN TestServerKeepAliveDeadline/tls=true -=== PAUSE TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=false ---- PASS: TestServerKeepAliveDeadline (0.00s) - --- PASS: TestServerKeepAliveDeadline/tls=false (1.51s) - --- PASS: TestServerKeepAliveDeadline/tls=true (1.52s) -=== RUN TestServerContinuousUpload -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=false - server_deadline_test.go:259: continuous upload 1.299239125s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=false - server_deadline_test.go:259: continuous upload 1.297586417s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=true - server_deadline_test.go:259: continuous upload 1.308398125s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=true - server_deadline_test.go:259: continuous upload 1.300855208s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=false - server_deadline_test.go:259: continuous upload 1.30259925s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=true - server_deadline_test.go:259: continuous upload 1.301167166s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=true - server_deadline_test.go:259: continuous upload 1.309196541s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=false - server_deadline_test.go:259: continuous upload 1.302837125s > idle 300ms ---- PASS: TestServerContinuousUpload (0.00s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=false (1.30s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=false (1.30s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=true (1.32s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=true (1.32s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=false (1.32s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=true (1.32s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=true (1.32s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=false (1.32s) -=== RUN TestServerDefaultIdleLongUpload - server_deadline_test.go:268: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle regression ---- SKIP: TestServerDefaultIdleLongUpload (0.00s) -=== RUN TestServerIdleBodyDeadline -=== RUN TestServerIdleBodyDeadline/tls=false -=== PAUSE TestServerIdleBodyDeadline/tls=false -=== RUN TestServerIdleBodyDeadline/tls=true -=== PAUSE TestServerIdleBodyDeadline/tls=true -=== CONT TestServerIdleBodyDeadline/tls=true -=== CONT TestServerIdleBodyDeadline/tls=false ---- PASS: TestServerIdleBodyDeadline (0.00s) - --- PASS: TestServerIdleBodyDeadline/tls=false (0.45s) - --- PASS: TestServerIdleBodyDeadline/tls=true (0.46s) -=== RUN TestServerBackgroundReadNoDeadline -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=true -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=true ---- PASS: TestServerBackgroundReadNoDeadline (0.00s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=false (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=true (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=false (0.71s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=true (0.71s) -=== RUN TestServerTLSHandshakeReadDeadline -2026/09/15 23:52:51 http: TLS handshake error from 127.0.0.1:54831: read tcp 127.0.0.1:54830->127.0.0.1:54831: i/o timeout ---- PASS: TestServerTLSHandshakeReadDeadline (0.20s) -=== RUN TestServerConnStateHook -=== RUN TestServerConnStateHook/tls=false -=== RUN TestServerConnStateHook/tls=true ---- PASS: TestServerConnStateHook (0.01s) - --- PASS: TestServerConnStateHook/tls=false (0.00s) - --- PASS: TestServerConnStateHook/tls=true (0.01s) -=== RUN TestServerHTTP2Deadlines - server_deadline_test.go:435: expected native HTTP/2 read timeout, got stream error: stream ID 5; INTERNAL_ERROR; i/o timeout ---- FAIL: TestServerHTTP2Deadlines (0.41s) -=== RUN TestServerEarlyBodyClose -=== RUN TestServerEarlyBodyClose/tls=false -=== PAUSE TestServerEarlyBodyClose/tls=false -=== RUN TestServerEarlyBodyClose/tls=true -=== PAUSE TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=false ---- PASS: TestServerEarlyBodyClose (0.00s) - --- PASS: TestServerEarlyBodyClose/tls=false (0.30s) - --- PASS: TestServerEarlyBodyClose/tls=true (0.31s) -=== RUN TestServerPipelinedDeadline -=== RUN TestServerPipelinedDeadline/tls=false -=== RUN TestServerPipelinedDeadline/tls=true ---- PASS: TestServerPipelinedDeadline (1.31s) - --- PASS: TestServerPipelinedDeadline/tls=false (0.65s) - --- PASS: TestServerPipelinedDeadline/tls=true (0.66s) -=== RUN TestServerHijackedDeadline -=== RUN TestServerHijackedDeadline/tls=false -=== PAUSE TestServerHijackedDeadline/tls=false -=== RUN TestServerHijackedDeadline/tls=true -=== PAUSE TestServerHijackedDeadline/tls=true -=== CONT TestServerHijackedDeadline/tls=false -=== CONT TestServerHijackedDeadline/tls=true ---- PASS: TestServerHijackedDeadline (0.00s) - --- PASS: TestServerHijackedDeadline/tls=false (0.70s) - --- PASS: TestServerHijackedDeadline/tls=true (0.71s) -=== RUN TestServerContinuousDownload -=== RUN TestServerContinuousDownload/tls=false -=== PAUSE TestServerContinuousDownload/tls=false -=== RUN TestServerContinuousDownload/tls=true -=== PAUSE TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=false - server_deadline_test.go:588: continuous download 1.298471834s > idle 300ms -=== NAME TestServerContinuousDownload/tls=true - server_deadline_test.go:588: continuous download 1.29483875s > idle 300ms ---- PASS: TestServerContinuousDownload (0.00s) - --- PASS: TestServerContinuousDownload/tls=false (1.30s) - --- PASS: TestServerContinuousDownload/tls=true (1.30s) -=== RUN TestServerDefaultIdleLongDownload - server_deadline_test.go:595: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle transfer regressions ---- SKIP: TestServerDefaultIdleLongDownload (0.00s) -=== RUN TestNewServer ---- PASS: TestNewServer (0.00s) -FAIL -FAIL github.com/minio/minio/internal/http 10.023s -FAIL diff --git a/docs/investigations/r8/evidence/darwin-race.log b/docs/investigations/r8/evidence/darwin-race.log deleted file mode 100644 index 489c08e78..000000000 --- a/docs/investigations/r8/evidence/darwin-race.log +++ /dev/null @@ -1,211 +0,0 @@ -=== RUN TestStrictReadDeadline -=== RUN TestStrictReadDeadline/SetReadDeadline -=== RUN TestStrictReadDeadline/SetDeadline ---- PASS: TestStrictReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetDeadline (0.00s) -=== RUN TestDefaultReadDeadlineStillRenews ---- PASS: TestDefaultReadDeadlineStillRenews (0.00s) -=== RUN TestStrictExpiredFutureReadDeadline ---- PASS: TestStrictExpiredFutureReadDeadline (0.06s) -=== RUN TestConcurrentStrictReadDeadline ---- PASS: TestConcurrentStrictReadDeadline (0.00s) -=== RUN TestBuffConnReadTimeout ---- PASS: TestBuffConnReadTimeout (3.00s) -=== RUN TestBuffConnReadCheckTimeout ---- PASS: TestBuffConnReadCheckTimeout (0.50s) -PASS -ok github.com/minio/minio/internal/deadlineconn 5.069s -=== RUN TestCheckPortAvailability - check_port_test.go:31: ---- SKIP: TestCheckPortAvailability (0.00s) -=== RUN TestNewHTTPListener ---- PASS: TestNewHTTPListener (0.01s) -=== RUN TestHTTPListenerStartClose ---- PASS: TestHTTPListenerStartClose (0.00s) -=== RUN TestHTTPListenerAddr ---- PASS: TestHTTPListenerAddr (0.00s) -=== RUN TestHTTPListenerAddrs ---- PASS: TestHTTPListenerAddrs (0.00s) -=== RUN TestServerReadHeaderDeadline -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=false ---- PASS: TestServerReadHeaderDeadline (0.00s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=true (0.81s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=true (0.83s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=false (0.95s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=false (0.96s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=true (1.56s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=true (1.57s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=false (1.70s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=false (1.71s) -=== RUN TestServerKeepAliveDeadline -=== RUN TestServerKeepAliveDeadline/tls=false -=== PAUSE TestServerKeepAliveDeadline/tls=false -=== RUN TestServerKeepAliveDeadline/tls=true -=== PAUSE TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=false ---- PASS: TestServerKeepAliveDeadline (0.00s) - --- PASS: TestServerKeepAliveDeadline/tls=false (1.50s) - --- PASS: TestServerKeepAliveDeadline/tls=true (1.51s) -=== RUN TestServerContinuousUpload -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=false - server_deadline_test.go:259: continuous upload 1.293135s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=true - server_deadline_test.go:259: continuous upload 1.286289708s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=true - server_deadline_test.go:259: continuous upload 1.286156s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=false - server_deadline_test.go:259: continuous upload 1.288825375s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=false - server_deadline_test.go:259: continuous upload 1.293748959s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=false - server_deadline_test.go:259: continuous upload 1.293017291s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=true - server_deadline_test.go:259: continuous upload 1.293105875s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=true - server_deadline_test.go:259: continuous upload 1.292306625s > idle 300ms ---- PASS: TestServerContinuousUpload (0.00s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=false (1.29s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=true (1.29s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=false (1.29s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=true (1.29s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=false (1.31s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=true (1.31s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=false (1.31s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=true (1.31s) -=== RUN TestServerDefaultIdleLongUpload - server_deadline_test.go:268: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle regression ---- SKIP: TestServerDefaultIdleLongUpload (0.00s) -=== RUN TestServerIdleBodyDeadline -=== RUN TestServerIdleBodyDeadline/tls=false -=== PAUSE TestServerIdleBodyDeadline/tls=false -=== RUN TestServerIdleBodyDeadline/tls=true -=== PAUSE TestServerIdleBodyDeadline/tls=true -=== CONT TestServerIdleBodyDeadline/tls=false -=== CONT TestServerIdleBodyDeadline/tls=true ---- PASS: TestServerIdleBodyDeadline (0.00s) - --- PASS: TestServerIdleBodyDeadline/tls=false (0.45s) - --- PASS: TestServerIdleBodyDeadline/tls=true (0.46s) -=== RUN TestServerBackgroundReadNoDeadline -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=true -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=true ---- PASS: TestServerBackgroundReadNoDeadline (0.00s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=false (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=true (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=false (0.71s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=true (0.71s) -=== RUN TestServerTLSHandshakeReadDeadline -2026/09/15 23:53:59 http: TLS handshake error from 127.0.0.1:55169: read tcp 127.0.0.1:55168->127.0.0.1:55169: i/o timeout ---- PASS: TestServerTLSHandshakeReadDeadline (0.20s) -=== RUN TestServerConnStateHook -=== RUN TestServerConnStateHook/tls=false -=== RUN TestServerConnStateHook/tls=true ---- PASS: TestServerConnStateHook (0.01s) - --- PASS: TestServerConnStateHook/tls=false (0.00s) - --- PASS: TestServerConnStateHook/tls=true (0.01s) -=== RUN TestServerHTTP2Deadlines ---- PASS: TestServerHTTP2Deadlines (0.41s) -=== RUN TestServerEarlyBodyClose -=== RUN TestServerEarlyBodyClose/tls=false -=== PAUSE TestServerEarlyBodyClose/tls=false -=== RUN TestServerEarlyBodyClose/tls=true -=== PAUSE TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=false ---- PASS: TestServerEarlyBodyClose (0.00s) - --- PASS: TestServerEarlyBodyClose/tls=false (0.30s) - --- PASS: TestServerEarlyBodyClose/tls=true (0.31s) -=== RUN TestServerPipelinedDeadline -=== RUN TestServerPipelinedDeadline/tls=false -=== RUN TestServerPipelinedDeadline/tls=true ---- PASS: TestServerPipelinedDeadline (1.31s) - --- PASS: TestServerPipelinedDeadline/tls=false (0.65s) - --- PASS: TestServerPipelinedDeadline/tls=true (0.66s) -=== RUN TestServerHijackedDeadline -=== RUN TestServerHijackedDeadline/tls=false -=== PAUSE TestServerHijackedDeadline/tls=false -=== RUN TestServerHijackedDeadline/tls=true -=== PAUSE TestServerHijackedDeadline/tls=true -=== CONT TestServerHijackedDeadline/tls=false -=== CONT TestServerHijackedDeadline/tls=true ---- PASS: TestServerHijackedDeadline (0.00s) - --- PASS: TestServerHijackedDeadline/tls=false (0.70s) - --- PASS: TestServerHijackedDeadline/tls=true (0.71s) -=== RUN TestServerContinuousDownload -=== RUN TestServerContinuousDownload/tls=false -=== PAUSE TestServerContinuousDownload/tls=false -=== RUN TestServerContinuousDownload/tls=true -=== PAUSE TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=false - server_deadline_test.go:624: continuous download 1.297514833s > idle 300ms -=== NAME TestServerContinuousDownload/tls=true - server_deadline_test.go:624: continuous download 1.29253225s > idle 300ms ---- PASS: TestServerContinuousDownload (0.00s) - --- PASS: TestServerContinuousDownload/tls=false (1.30s) - --- PASS: TestServerContinuousDownload/tls=true (1.30s) -=== RUN TestServerDefaultIdleLongDownload - server_deadline_test.go:631: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle transfer regressions ---- SKIP: TestServerDefaultIdleLongDownload (0.00s) -=== RUN TestNewServer ---- PASS: TestNewServer (0.00s) -PASS -ok github.com/minio/minio/internal/http 11.525s diff --git a/docs/investigations/r8/evidence/default-30s-transfers.log b/docs/investigations/r8/evidence/default-30s-transfers.log deleted file mode 100644 index c1d6295ae..000000000 --- a/docs/investigations/r8/evidence/default-30s-transfers.log +++ /dev/null @@ -1,34 +0,0 @@ -=== RUN TestServerDefaultIdleLongUpload -=== PAUSE TestServerDefaultIdleLongUpload -=== RUN TestServerDefaultIdleLongDownload -=== PAUSE TestServerDefaultIdleLongDownload -=== CONT TestServerDefaultIdleLongUpload -=== RUN TestServerDefaultIdleLongUpload/tls=false -=== PAUSE TestServerDefaultIdleLongUpload/tls=false -=== RUN TestServerDefaultIdleLongUpload/tls=true -=== PAUSE TestServerDefaultIdleLongUpload/tls=true -=== CONT TestServerDefaultIdleLongUpload/tls=false -=== CONT TestServerDefaultIdleLongDownload -=== RUN TestServerDefaultIdleLongDownload/tls=false -=== PAUSE TestServerDefaultIdleLongDownload/tls=false -=== RUN TestServerDefaultIdleLongDownload/tls=true -=== PAUSE TestServerDefaultIdleLongDownload/tls=true -=== CONT TestServerDefaultIdleLongDownload/tls=false -=== CONT TestServerDefaultIdleLongDownload/tls=true -=== CONT TestServerDefaultIdleLongUpload/tls=true -=== NAME TestServerDefaultIdleLongDownload/tls=true - server_deadline_test.go:601: continuous download 33.028469208s > idle 30s -=== NAME TestServerDefaultIdleLongDownload/tls=false - server_deadline_test.go:601: continuous download 33.033171416s > idle 30s -=== NAME TestServerDefaultIdleLongUpload/tls=true - server_deadline_test.go:274: continuous upload 33.028635792s > idle 30s -=== NAME TestServerDefaultIdleLongUpload/tls=false - server_deadline_test.go:274: continuous upload 33.033198583s > idle 30s ---- PASS: TestServerDefaultIdleLongDownload (0.00s) - --- PASS: TestServerDefaultIdleLongDownload/tls=true (33.03s) - --- PASS: TestServerDefaultIdleLongDownload/tls=false (33.03s) ---- PASS: TestServerDefaultIdleLongUpload (0.00s) - --- PASS: TestServerDefaultIdleLongUpload/tls=false (33.03s) - --- PASS: TestServerDefaultIdleLongUpload/tls=true (33.03s) -PASS -ok github.com/minio/minio/internal/http 34.193s diff --git a/docs/investigations/r8/evidence/delivery-whitespace.log b/docs/investigations/r8/evidence/delivery-whitespace.log deleted file mode 100644 index 6f28be029..000000000 --- a/docs/investigations/r8/evidence/delivery-whitespace.log +++ /dev/null @@ -1,3 +0,0 @@ -$ git diff --check 9ebe81c1b3611f9cc73e676b5b741c2be62c467a -- - -exit_code=0 diff --git a/docs/investigations/r8/evidence/grid.log b/docs/investigations/r8/evidence/grid.log deleted file mode 100644 index 46e5f7d0e..000000000 --- a/docs/investigations/r8/evidence/grid.log +++ /dev/null @@ -1,39 +0,0 @@ -=== RUN TestDisconnect - connection_test.go:90: Started server on 127.0.0.1:54773 URL: http://127.0.0.1:54773 - connection_test.go:91: Started server on 127.0.0.1:54774 URL: http://127.0.0.1:54774 - connection_test.go:46: Got a GET request for: /minio/grid/v1 - connection_test.go:140: Roundtrip: sending request - connection_test.go:142: Roundtrip: 246.625µs [] remote disconnected - connection_test.go:46: Got a GET request for: /minio/grid/v1 - connection_test.go:157: Resp: {[] remote disconnected} - connection_test.go:46: Got a GET request for: /minio/grid/v1 ---- PASS: TestDisconnect (0.01s) -=== RUN TestSingleRoundtrip -=== RUN TestSingleRoundtrip/localToRemote -=== NAME TestSingleRoundtrip - grid_test.go:66: 1: server payload: 17 bytes. -=== NAME TestSingleRoundtrip/localToRemote - grid_test.go:90: Roundtrip: 141.625µs -=== RUN TestSingleRoundtrip/localToRemoteErr -=== NAME TestSingleRoundtrip - grid_test.go:71: 2: server payload: 17 bytes. -=== NAME TestSingleRoundtrip/localToRemoteErr - grid_test.go:97: Roundtrip: 187.625µs - grid_test.go:104: Roundtrip: 195.958µs -=== RUN TestSingleRoundtrip/localToRemoteHuge -=== NAME TestSingleRoundtrip - grid_test.go:66: 1: server payload: 1048576 bytes. -=== NAME TestSingleRoundtrip/localToRemoteHuge - grid_test.go:116: Roundtrip: 3.062792ms -=== RUN TestSingleRoundtrip/localToRemoteErrHuge -=== NAME TestSingleRoundtrip - grid_test.go:71: 2: server payload: 1024 bytes. -=== NAME TestSingleRoundtrip/localToRemoteErrHuge - grid_test.go:130: Roundtrip: 232.958µs ---- PASS: TestSingleRoundtrip (0.00s) - --- PASS: TestSingleRoundtrip/localToRemote (0.00s) - --- PASS: TestSingleRoundtrip/localToRemoteErr (0.00s) - --- PASS: TestSingleRoundtrip/localToRemoteHuge (0.00s) - --- PASS: TestSingleRoundtrip/localToRemoteErrHuge (0.00s) -PASS -ok github.com/minio/minio/internal/grid 0.622s diff --git a/docs/investigations/r8/evidence/implementation-focused.log b/docs/investigations/r8/evidence/implementation-focused.log deleted file mode 100644 index 6cd38adcd..000000000 --- a/docs/investigations/r8/evidence/implementation-focused.log +++ /dev/null @@ -1,195 +0,0 @@ -=== RUN TestStrictReadDeadline -=== RUN TestStrictReadDeadline/SetReadDeadline -=== RUN TestStrictReadDeadline/SetDeadline ---- PASS: TestStrictReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetDeadline (0.00s) -=== RUN TestDefaultReadDeadlineStillRenews ---- PASS: TestDefaultReadDeadlineStillRenews (0.00s) -=== RUN TestStrictExpiredFutureReadDeadline ---- PASS: TestStrictExpiredFutureReadDeadline (0.06s) -=== RUN TestConcurrentStrictReadDeadline ---- PASS: TestConcurrentStrictReadDeadline (0.00s) -PASS -ok github.com/minio/minio/internal/deadlineconn 0.652s -=== RUN TestServerReadHeaderDeadline -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=true ---- PASS: TestServerReadHeaderDeadline (0.00s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=true (0.40s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=true (0.41s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=false (0.60s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=false (0.61s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=true (0.80s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=true (0.81s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=false (1.00s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=false (1.01s) -=== RUN TestServerKeepAliveDeadline -=== RUN TestServerKeepAliveDeadline/tls=false -=== PAUSE TestServerKeepAliveDeadline/tls=false -=== RUN TestServerKeepAliveDeadline/tls=true -=== PAUSE TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=false -=== CONT TestServerKeepAliveDeadline/tls=true ---- PASS: TestServerKeepAliveDeadline (0.00s) - --- PASS: TestServerKeepAliveDeadline/tls=true (1.50s) - --- PASS: TestServerKeepAliveDeadline/tls=false (1.50s) -=== RUN TestServerContinuousUpload -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=true - server_deadline_test.go:259: continuous upload 1.296547458s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=false - server_deadline_test.go:259: continuous upload 1.296906542s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=false - server_deadline_test.go:259: continuous upload 1.293365791s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=true - server_deadline_test.go:259: continuous upload 1.293554459s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=false - server_deadline_test.go:259: continuous upload 1.296912417s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=true - server_deadline_test.go:259: continuous upload 1.293226958s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=true - server_deadline_test.go:259: continuous upload 1.295960125s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=false - server_deadline_test.go:259: continuous upload 1.29373425s > idle 300ms ---- PASS: TestServerContinuousUpload (0.00s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=false (1.30s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=true (1.30s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=false (1.30s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=false (1.30s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=false (1.30s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=true (1.30s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=true (1.30s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=true (1.30s) -=== RUN TestServerDefaultIdleLongUpload - server_deadline_test.go:268: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle regression ---- SKIP: TestServerDefaultIdleLongUpload (0.00s) -=== RUN TestServerIdleBodyDeadline -=== RUN TestServerIdleBodyDeadline/tls=false -=== PAUSE TestServerIdleBodyDeadline/tls=false -=== RUN TestServerIdleBodyDeadline/tls=true -=== PAUSE TestServerIdleBodyDeadline/tls=true -=== CONT TestServerIdleBodyDeadline/tls=false -=== CONT TestServerIdleBodyDeadline/tls=true ---- PASS: TestServerIdleBodyDeadline (0.00s) - --- PASS: TestServerIdleBodyDeadline/tls=false (0.45s) - --- PASS: TestServerIdleBodyDeadline/tls=true (0.45s) -=== RUN TestServerBackgroundReadNoDeadline -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=true -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=true ---- PASS: TestServerBackgroundReadNoDeadline (0.00s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=true (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=false (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=true (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=false (0.70s) -=== RUN TestServerTLSHandshakeReadDeadline -2026/09/15 23:51:45 http: TLS handshake error from 127.0.0.1:54459: read tcp 127.0.0.1:54458->127.0.0.1:54459: i/o timeout ---- PASS: TestServerTLSHandshakeReadDeadline (0.20s) -=== RUN TestServerConnStateHook -=== RUN TestServerConnStateHook/tls=false -=== RUN TestServerConnStateHook/tls=true ---- PASS: TestServerConnStateHook (0.00s) - --- PASS: TestServerConnStateHook/tls=false (0.00s) - --- PASS: TestServerConnStateHook/tls=true (0.00s) -=== RUN TestServerHTTP2Deadlines ---- PASS: TestServerHTTP2Deadlines (0.40s) -=== RUN TestServerEarlyBodyClose -=== RUN TestServerEarlyBodyClose/tls=false -=== PAUSE TestServerEarlyBodyClose/tls=false -=== RUN TestServerEarlyBodyClose/tls=true -=== PAUSE TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=false -=== CONT TestServerEarlyBodyClose/tls=true ---- PASS: TestServerEarlyBodyClose (0.00s) - --- PASS: TestServerEarlyBodyClose/tls=false (0.30s) - --- PASS: TestServerEarlyBodyClose/tls=true (0.30s) -=== RUN TestServerPipelinedDeadline -=== RUN TestServerPipelinedDeadline/tls=false -=== RUN TestServerPipelinedDeadline/tls=true ---- PASS: TestServerPipelinedDeadline (1.30s) - --- PASS: TestServerPipelinedDeadline/tls=false (0.65s) - --- PASS: TestServerPipelinedDeadline/tls=true (0.65s) -=== RUN TestServerHijackedDeadline -=== RUN TestServerHijackedDeadline/tls=false -=== PAUSE TestServerHijackedDeadline/tls=false -=== RUN TestServerHijackedDeadline/tls=true -=== PAUSE TestServerHijackedDeadline/tls=true -=== CONT TestServerHijackedDeadline/tls=false -=== CONT TestServerHijackedDeadline/tls=true ---- PASS: TestServerHijackedDeadline (0.00s) - --- PASS: TestServerHijackedDeadline/tls=false (0.70s) - --- PASS: TestServerHijackedDeadline/tls=true (0.70s) -=== RUN TestServerContinuousDownload -=== RUN TestServerContinuousDownload/tls=false -=== PAUSE TestServerContinuousDownload/tls=false -=== RUN TestServerContinuousDownload/tls=true -=== PAUSE TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=false -=== CONT TestServerContinuousDownload/tls=true -=== NAME TestServerContinuousDownload/tls=false - server_deadline_test.go:587: continuous download 1.296415709s > idle 300ms -=== NAME TestServerContinuousDownload/tls=true - server_deadline_test.go:587: continuous download 1.294823375s > idle 300ms ---- PASS: TestServerContinuousDownload (0.00s) - --- PASS: TestServerContinuousDownload/tls=false (1.30s) - --- PASS: TestServerContinuousDownload/tls=true (1.30s) -=== RUN TestServerDefaultIdleLongDownload - server_deadline_test.go:594: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle transfer regressions ---- SKIP: TestServerDefaultIdleLongDownload (0.00s) -PASS -ok github.com/minio/minio/internal/http 9.965s diff --git a/docs/investigations/r8/evidence/legacy-timeout-isolation.patch b/docs/investigations/r8/evidence/legacy-timeout-isolation.patch deleted file mode 100644 index 398c3e6c5..000000000 --- a/docs/investigations/r8/evidence/legacy-timeout-isolation.patch +++ /dev/null @@ -1,31 +0,0 @@ ---- buildscripts/test-timeout.sh -+++ temporary/test-timeout-isolated.sh -@@ -31,8 +31,11 @@ - fi - - echo "Cleaning up instances of Silo" -- pkill silo || true -- pkill -9 silo || true -+ if [ -n "${pid:-}" ]; then -+ kill "$pid" 2>/dev/null || true -+ wait "$pid" 2>/dev/null || true -+ fi -+ if [ -f "$WORK_DIR/server1.log" ]; then cp "$WORK_DIR/server1.log" "$PWD/timeout-server.log"; fi - purge "$WORK_DIR" - if [ $# -ne 0 ]; then - exit $# -@@ -83,7 +86,7 @@ - "$(git rev-parse --show-toplevel)/buildscripts/install-mcli.sh" "$PWD/mc" - fi - -- "${SILO[@]}" --address ":$start_port" --read-header-timeout ${srv_hdr_timeout}s --idle-timeout ${srv_idle_timeout}s "${WORK_DIR}/disk/" >"${WORK_DIR}/server1.log" 2>&1 & -+ "${SILO[@]}" --address "127.0.0.1:$start_port" --read-header-timeout ${srv_hdr_timeout}s --idle-timeout ${srv_idle_timeout}s "${WORK_DIR}/disk/" >"${WORK_DIR}/server1.log" 2>&1 & - pid=$! - disown $pid - sleep 1 -@@ -124,3 +127,5 @@ - } - - main "$@" -+ -+catch diff --git a/docs/investigations/r8/evidence/legacy-timeout.log b/docs/investigations/r8/evidence/legacy-timeout.log deleted file mode 100644 index 4ce8aaa8f..000000000 --- a/docs/investigations/r8/evidence/legacy-timeout.log +++ /dev/null @@ -1,6 +0,0 @@ -Cleaning up instances of Silo -Bucket created successfully `silo/testbucket`. -Access permission for `silo/testbucket` is set to `public` -mcli: Unable to stat `silo/testbucket/testobject`. Object does not exist. -mcli: Unable to stat `silo/testbucket/testobject`. Object does not exist. -mcli: Unable to stat `silo/testbucket/testobject`. Object does not exist. diff --git a/docs/investigations/r8/evidence/legacy-timeout.metadata.json b/docs/investigations/r8/evidence/legacy-timeout.metadata.json deleted file mode 100644 index 8449bd601..000000000 --- a/docs/investigations/r8/evidence/legacy-timeout.metadata.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "original_script": "buildscripts/test-timeout.sh", - "isolation_patch": "legacy-timeout-isolation.patch", - "working_directory": "/Users/vonng/tmp/silo-r8-01a0a5b9/legacy-timeout", - "binary_sha256": "6b982de3262c25e326280c739b4275444cf80c3eacae48f0942aa18fbb7cd654", - "client": "/opt/homebrew/bin/mcli RELEASE.2026-08-26T17-15-27Z (70a2950478e18e38eb68162317e4b5d34c6eb6d5)", - "netcat": "/usr/bin/nc via private netcat symlink", - "command": [ - "/bin/bash", - "/Users/vonng/tmp/silo-r8-01a0a5b9/legacy-timeout/test-timeout-isolated.sh" - ], - "exit_code": 255, - "elapsed_seconds": 67.029 -} diff --git a/docs/investigations/r8/evidence/lint-final.log b/docs/investigations/r8/evidence/lint-final.log deleted file mode 100644 index 6a3ebaa7e..000000000 --- a/docs/investigations/r8/evidence/lint-final.log +++ /dev/null @@ -1 +0,0 @@ -0 issues. diff --git a/docs/investigations/r8/evidence/lint-initial-lock.log b/docs/investigations/r8/evidence/lint-initial-lock.log deleted file mode 100644 index fd1117a98..000000000 --- a/docs/investigations/r8/evidence/lint-initial-lock.log +++ /dev/null @@ -1,2 +0,0 @@ -Error: parallel golangci-lint is running -The command is terminated due to an error: parallel golangci-lint is running diff --git a/docs/investigations/r8/evidence/lint.log b/docs/investigations/r8/evidence/lint.log deleted file mode 100644 index db69c561d..000000000 --- a/docs/investigations/r8/evidence/lint.log +++ /dev/null @@ -1,5 +0,0 @@ -internal/http/server_deadline_test.go:61:7: QF1008: could remove embedded field "Server" from selector (staticcheck) - srv.Server.Close() - ^ -1 issues: -* staticcheck: 1 diff --git a/docs/investigations/r8/evidence/linux-race-final.log b/docs/investigations/r8/evidence/linux-race-final.log deleted file mode 100644 index 43f623734..000000000 --- a/docs/investigations/r8/evidence/linux-race-final.log +++ /dev/null @@ -1,214 +0,0 @@ -=== RUN TestStrictReadDeadline -=== RUN TestStrictReadDeadline/SetReadDeadline -=== RUN TestStrictReadDeadline/SetDeadline ---- PASS: TestStrictReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetDeadline (0.00s) -=== RUN TestDefaultReadDeadlineStillRenews ---- PASS: TestDefaultReadDeadlineStillRenews (0.00s) -=== RUN TestStrictExpiredFutureReadDeadline ---- PASS: TestStrictExpiredFutureReadDeadline (0.06s) -=== RUN TestConcurrentStrictReadDeadline ---- PASS: TestConcurrentStrictReadDeadline (0.00s) -=== RUN TestStrictReadDeadlineRepeatedRenewal ---- PASS: TestStrictReadDeadlineRepeatedRenewal (0.91s) -=== RUN TestBuffConnReadTimeout ---- PASS: TestBuffConnReadTimeout (3.01s) -=== RUN TestBuffConnReadCheckTimeout ---- PASS: TestBuffConnReadCheckTimeout (0.50s) -PASS -ok github.com/minio/minio/internal/deadlineconn 5.490s -=== RUN TestCheckPortAvailability ---- PASS: TestCheckPortAvailability (0.00s) -=== RUN TestInternodeDialReadDeadline ---- PASS: TestInternodeDialReadDeadline (1.31s) -=== RUN TestNewHTTPListener ---- PASS: TestNewHTTPListener (0.01s) -=== RUN TestHTTPListenerStartClose ---- PASS: TestHTTPListenerStartClose (0.00s) -=== RUN TestHTTPListenerAddr ---- PASS: TestHTTPListenerAddr (0.00s) -=== RUN TestHTTPListenerAddrs ---- PASS: TestHTTPListenerAddrs (0.00s) -=== RUN TestServerReadHeaderDeadline -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=true ---- PASS: TestServerReadHeaderDeadline (0.00s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=true (0.82s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=false (0.96s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=true (1.58s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=false (1.71s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=false (0.95s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=true (0.83s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=false (1.72s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=true (1.59s) -=== RUN TestServerKeepAliveDeadline -=== RUN TestServerKeepAliveDeadline/tls=false -=== PAUSE TestServerKeepAliveDeadline/tls=false -=== RUN TestServerKeepAliveDeadline/tls=true -=== PAUSE TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=false ---- PASS: TestServerKeepAliveDeadline (0.00s) - --- PASS: TestServerKeepAliveDeadline/tls=false (1.52s) - --- PASS: TestServerKeepAliveDeadline/tls=true (1.52s) -=== RUN TestServerContinuousUpload -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=true - server_deadline_test.go:261: continuous upload 1.320011834s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=true - server_deadline_test.go:261: continuous upload 1.33052725s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=false - server_deadline_test.go:261: continuous upload 1.330741292s > idle 300ms -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=true - server_deadline_test.go:261: continuous upload 1.321011251s > idle 300ms -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=true - server_deadline_test.go:261: continuous upload 1.325489459s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=false - server_deadline_test.go:261: continuous upload 1.315352084s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=false - server_deadline_test.go:261: continuous upload 1.325666792s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=false - server_deadline_test.go:261: continuous upload 1.315434542s > idle 300ms ---- PASS: TestServerContinuousUpload (0.00s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=true (1.33s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=true (1.33s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=false (1.33s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=true (1.33s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=true (1.33s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=false (1.33s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=false (1.33s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=false (1.33s) -=== RUN TestServerDefaultIdleLongUpload - server_deadline_test.go:270: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle regression ---- SKIP: TestServerDefaultIdleLongUpload (0.00s) -=== RUN TestServerIdleBodyDeadline -=== RUN TestServerIdleBodyDeadline/tls=false -=== PAUSE TestServerIdleBodyDeadline/tls=false -=== RUN TestServerIdleBodyDeadline/tls=true -=== PAUSE TestServerIdleBodyDeadline/tls=true -=== CONT TestServerIdleBodyDeadline/tls=false -=== CONT TestServerIdleBodyDeadline/tls=true ---- PASS: TestServerIdleBodyDeadline (0.00s) - --- PASS: TestServerIdleBodyDeadline/tls=false (0.45s) - --- PASS: TestServerIdleBodyDeadline/tls=true (0.46s) -=== RUN TestServerBackgroundReadNoDeadline -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=true -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=false ---- PASS: TestServerBackgroundReadNoDeadline (0.00s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=false (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=true (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=true (0.71s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=false (0.71s) -=== RUN TestServerTLSHandshakeReadDeadline -2026/09/15 16:04:28 http: TLS handshake error from 127.0.0.1:60010: read tcp 127.0.0.1:44841->127.0.0.1:60010: i/o timeout ---- PASS: TestServerTLSHandshakeReadDeadline (0.21s) -=== RUN TestServerConnStateHook -=== RUN TestServerConnStateHook/tls=false -=== RUN TestServerConnStateHook/tls=true ---- PASS: TestServerConnStateHook (0.01s) - --- PASS: TestServerConnStateHook/tls=false (0.00s) - --- PASS: TestServerConnStateHook/tls=true (0.01s) -=== RUN TestServerHTTP2Deadlines ---- PASS: TestServerHTTP2Deadlines (0.42s) -=== RUN TestServerEarlyBodyClose -=== RUN TestServerEarlyBodyClose/tls=false -=== PAUSE TestServerEarlyBodyClose/tls=false -=== RUN TestServerEarlyBodyClose/tls=true -=== PAUSE TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=false -=== CONT TestServerEarlyBodyClose/tls=true ---- PASS: TestServerEarlyBodyClose (0.00s) - --- PASS: TestServerEarlyBodyClose/tls=false (0.31s) - --- PASS: TestServerEarlyBodyClose/tls=true (0.31s) -=== RUN TestServerPipelinedDeadline -=== RUN TestServerPipelinedDeadline/tls=false -=== RUN TestServerPipelinedDeadline/tls=true ---- PASS: TestServerPipelinedDeadline (1.34s) - --- PASS: TestServerPipelinedDeadline/tls=false (0.66s) - --- PASS: TestServerPipelinedDeadline/tls=true (0.68s) -=== RUN TestServerHijackedDeadline -=== RUN TestServerHijackedDeadline/tls=false -=== PAUSE TestServerHijackedDeadline/tls=false -=== RUN TestServerHijackedDeadline/tls=true -=== PAUSE TestServerHijackedDeadline/tls=true -=== CONT TestServerHijackedDeadline/tls=true -=== CONT TestServerHijackedDeadline/tls=false ---- PASS: TestServerHijackedDeadline (0.00s) - --- PASS: TestServerHijackedDeadline/tls=false (0.70s) - --- PASS: TestServerHijackedDeadline/tls=true (0.71s) -=== RUN TestServerContinuousDownload -=== RUN TestServerContinuousDownload/tls=false -=== PAUSE TestServerContinuousDownload/tls=false -=== RUN TestServerContinuousDownload/tls=true -=== PAUSE TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=false -=== NAME TestServerContinuousDownload/tls=true - server_deadline_test.go:619: continuous download 1.306085084s > idle 300ms -=== NAME TestServerContinuousDownload/tls=false - server_deadline_test.go:619: continuous download 1.319082334s > idle 300ms ---- PASS: TestServerContinuousDownload (0.00s) - --- PASS: TestServerContinuousDownload/tls=true (1.32s) - --- PASS: TestServerContinuousDownload/tls=false (1.32s) -=== RUN TestServerDefaultIdleLongDownload - server_deadline_test.go:626: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle transfer regressions ---- SKIP: TestServerDefaultIdleLongDownload (0.00s) -=== RUN TestNewServer ---- PASS: TestNewServer (0.00s) -PASS -ok github.com/minio/minio/internal/http 15.196s diff --git a/docs/investigations/r8/evidence/linux-race-initial.log b/docs/investigations/r8/evidence/linux-race-initial.log deleted file mode 100644 index 964d89c6f..000000000 --- a/docs/investigations/r8/evidence/linux-race-initial.log +++ /dev/null @@ -1,212 +0,0 @@ -=== RUN TestStrictReadDeadline -=== RUN TestStrictReadDeadline/SetReadDeadline -=== RUN TestStrictReadDeadline/SetDeadline ---- PASS: TestStrictReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetDeadline (0.00s) -=== RUN TestDefaultReadDeadlineStillRenews ---- PASS: TestDefaultReadDeadlineStillRenews (0.00s) -=== RUN TestStrictExpiredFutureReadDeadline ---- PASS: TestStrictExpiredFutureReadDeadline (0.06s) -=== RUN TestConcurrentStrictReadDeadline ---- PASS: TestConcurrentStrictReadDeadline (0.00s) -=== RUN TestBuffConnReadTimeout ---- PASS: TestBuffConnReadTimeout (3.01s) -=== RUN TestBuffConnReadCheckTimeout ---- PASS: TestBuffConnReadCheckTimeout (0.50s) -PASS -ok github.com/minio/minio/internal/deadlineconn 4.580s -=== RUN TestCheckPortAvailability ---- PASS: TestCheckPortAvailability (0.00s) -=== RUN TestInternodeDialReadDeadline ---- PASS: TestInternodeDialReadDeadline (1.31s) -=== RUN TestNewHTTPListener ---- PASS: TestNewHTTPListener (0.01s) -=== RUN TestHTTPListenerStartClose ---- PASS: TestHTTPListenerStartClose (0.00s) -=== RUN TestHTTPListenerAddr ---- PASS: TestHTTPListenerAddr (0.00s) -=== RUN TestHTTPListenerAddrs ---- PASS: TestHTTPListenerAddrs (0.00s) -=== RUN TestServerReadHeaderDeadline -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=true ---- PASS: TestServerReadHeaderDeadline (0.00s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=false (0.61s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=true (0.81s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=true (0.81s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=false (1.01s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=false (0.61s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=true (0.41s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=true (0.42s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=false (1.01s) -=== RUN TestServerKeepAliveDeadline -=== RUN TestServerKeepAliveDeadline/tls=false -=== PAUSE TestServerKeepAliveDeadline/tls=false -=== RUN TestServerKeepAliveDeadline/tls=true -=== PAUSE TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=false ---- PASS: TestServerKeepAliveDeadline (0.00s) - --- PASS: TestServerKeepAliveDeadline/tls=true (1.52s) - --- PASS: TestServerKeepAliveDeadline/tls=false (1.52s) -=== RUN TestServerContinuousUpload -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=false - server_deadline_test.go:259: continuous upload 1.317026792s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=false - server_deadline_test.go:259: continuous upload 1.327268125s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=true - server_deadline_test.go:259: continuous upload 1.327373417s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=true - server_deadline_test.go:259: continuous upload 1.316941667s > idle 300ms -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=true - server_deadline_test.go:259: continuous upload 1.299763501s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=true - server_deadline_test.go:259: continuous upload 1.313516875s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=false - server_deadline_test.go:259: continuous upload 1.314431001s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=false - server_deadline_test.go:259: continuous upload 1.300043209s > idle 300ms ---- PASS: TestServerContinuousUpload (0.00s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=false (1.33s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=true (1.33s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=false (1.33s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=true (1.33s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=true (1.31s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=false (1.32s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=false (1.32s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=true (1.32s) -=== RUN TestServerDefaultIdleLongUpload - server_deadline_test.go:268: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle regression ---- SKIP: TestServerDefaultIdleLongUpload (0.00s) -=== RUN TestServerIdleBodyDeadline -=== RUN TestServerIdleBodyDeadline/tls=false -=== PAUSE TestServerIdleBodyDeadline/tls=false -=== RUN TestServerIdleBodyDeadline/tls=true -=== PAUSE TestServerIdleBodyDeadline/tls=true -=== CONT TestServerIdleBodyDeadline/tls=false -=== CONT TestServerIdleBodyDeadline/tls=true ---- PASS: TestServerIdleBodyDeadline (0.00s) - --- PASS: TestServerIdleBodyDeadline/tls=false (0.46s) - --- PASS: TestServerIdleBodyDeadline/tls=true (0.46s) -=== RUN TestServerBackgroundReadNoDeadline -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=true -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=true ---- PASS: TestServerBackgroundReadNoDeadline (0.00s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=false (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=true (0.70s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=false (0.71s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=true (0.71s) -=== RUN TestServerTLSHandshakeReadDeadline -2026/09/15 15:53:23 http: TLS handshake error from 127.0.0.1:45990: read tcp 127.0.0.1:34171->127.0.0.1:45990: i/o timeout ---- PASS: TestServerTLSHandshakeReadDeadline (0.20s) -=== RUN TestServerConnStateHook -=== RUN TestServerConnStateHook/tls=false -=== RUN TestServerConnStateHook/tls=true ---- PASS: TestServerConnStateHook (0.01s) - --- PASS: TestServerConnStateHook/tls=false (0.00s) - --- PASS: TestServerConnStateHook/tls=true (0.01s) -=== RUN TestServerHTTP2Deadlines ---- PASS: TestServerHTTP2Deadlines (0.42s) -=== RUN TestServerEarlyBodyClose -=== RUN TestServerEarlyBodyClose/tls=false -=== PAUSE TestServerEarlyBodyClose/tls=false -=== RUN TestServerEarlyBodyClose/tls=true -=== PAUSE TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=false ---- PASS: TestServerEarlyBodyClose (0.00s) - --- PASS: TestServerEarlyBodyClose/tls=false (0.31s) - --- PASS: TestServerEarlyBodyClose/tls=true (0.31s) -=== RUN TestServerPipelinedDeadline -=== RUN TestServerPipelinedDeadline/tls=false -=== RUN TestServerPipelinedDeadline/tls=true ---- PASS: TestServerPipelinedDeadline (1.33s) - --- PASS: TestServerPipelinedDeadline/tls=false (0.66s) - --- PASS: TestServerPipelinedDeadline/tls=true (0.67s) -=== RUN TestServerHijackedDeadline -=== RUN TestServerHijackedDeadline/tls=false -=== PAUSE TestServerHijackedDeadline/tls=false -=== RUN TestServerHijackedDeadline/tls=true -=== PAUSE TestServerHijackedDeadline/tls=true -=== CONT TestServerHijackedDeadline/tls=false -=== CONT TestServerHijackedDeadline/tls=true ---- PASS: TestServerHijackedDeadline (0.00s) - --- PASS: TestServerHijackedDeadline/tls=false (0.70s) - --- PASS: TestServerHijackedDeadline/tls=true (0.71s) -=== RUN TestServerContinuousDownload -=== RUN TestServerContinuousDownload/tls=false -=== PAUSE TestServerContinuousDownload/tls=false -=== RUN TestServerContinuousDownload/tls=true -=== PAUSE TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=false -=== CONT TestServerContinuousDownload/tls=true - server_deadline_test.go:588: continuous download 1.315636125s > idle 300ms -=== NAME TestServerContinuousDownload/tls=false - server_deadline_test.go:588: continuous download 1.324163209s > idle 300ms ---- PASS: TestServerContinuousDownload (0.00s) - --- PASS: TestServerContinuousDownload/tls=false (1.32s) - --- PASS: TestServerContinuousDownload/tls=true (1.32s) -=== RUN TestServerDefaultIdleLongDownload - server_deadline_test.go:595: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle transfer regressions ---- SKIP: TestServerDefaultIdleLongDownload (0.00s) -=== RUN TestNewServer ---- PASS: TestNewServer (0.00s) -PASS -ok github.com/minio/minio/internal/http 13.825s diff --git a/docs/investigations/r8/evidence/linux-race.log b/docs/investigations/r8/evidence/linux-race.log deleted file mode 100644 index 4814a2dc2..000000000 --- a/docs/investigations/r8/evidence/linux-race.log +++ /dev/null @@ -1,213 +0,0 @@ -=== RUN TestStrictReadDeadline -=== RUN TestStrictReadDeadline/SetReadDeadline -=== RUN TestStrictReadDeadline/SetDeadline ---- PASS: TestStrictReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetReadDeadline (0.00s) - --- PASS: TestStrictReadDeadline/SetDeadline (0.00s) -=== RUN TestDefaultReadDeadlineStillRenews ---- PASS: TestDefaultReadDeadlineStillRenews (0.00s) -=== RUN TestStrictExpiredFutureReadDeadline ---- PASS: TestStrictExpiredFutureReadDeadline (0.06s) -=== RUN TestConcurrentStrictReadDeadline ---- PASS: TestConcurrentStrictReadDeadline (0.00s) -=== RUN TestBuffConnReadTimeout ---- PASS: TestBuffConnReadTimeout (3.01s) -=== RUN TestBuffConnReadCheckTimeout ---- PASS: TestBuffConnReadCheckTimeout (0.51s) -PASS -ok github.com/minio/minio/internal/deadlineconn 4.583s -=== RUN TestCheckPortAvailability ---- PASS: TestCheckPortAvailability (0.01s) -=== RUN TestInternodeDialReadDeadline ---- PASS: TestInternodeDialReadDeadline (1.31s) -=== RUN TestNewHTTPListener ---- PASS: TestNewHTTPListener (0.01s) -=== RUN TestHTTPListenerStartClose ---- PASS: TestHTTPListenerStartClose (0.00s) -=== RUN TestHTTPListenerAddr ---- PASS: TestHTTPListenerAddr (0.00s) -=== RUN TestHTTPListenerAddrs ---- PASS: TestHTTPListenerAddrs (0.00s) -=== RUN TestServerReadHeaderDeadline -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== RUN TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== PAUSE TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=false/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=false/second=false/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=true/trickle=false -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=true -=== CONT TestServerReadHeaderDeadline/tls=true/second=false/trickle=false ---- PASS: TestServerReadHeaderDeadline (0.00s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=true (0.83s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=false/trickle=false (0.95s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=true (1.57s) - --- PASS: TestServerReadHeaderDeadline/tls=false/second=true/trickle=false (1.71s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=true (0.83s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=true (1.58s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=true/trickle=false (1.71s) - --- PASS: TestServerReadHeaderDeadline/tls=true/second=false/trickle=false (0.96s) -=== RUN TestServerKeepAliveDeadline -=== RUN TestServerKeepAliveDeadline/tls=false -=== PAUSE TestServerKeepAliveDeadline/tls=false -=== RUN TestServerKeepAliveDeadline/tls=true -=== PAUSE TestServerKeepAliveDeadline/tls=true -=== CONT TestServerKeepAliveDeadline/tls=false -=== CONT TestServerKeepAliveDeadline/tls=true ---- PASS: TestServerKeepAliveDeadline (0.00s) - --- PASS: TestServerKeepAliveDeadline/tls=false (1.51s) - --- PASS: TestServerKeepAliveDeadline/tls=true (1.51s) -=== RUN TestServerContinuousUpload -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== RUN TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== PAUSE TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=true -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=false/expect=true -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=false - server_deadline_test.go:259: continuous upload 1.314443501s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=true - server_deadline_test.go:259: continuous upload 1.313693959s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=true - server_deadline_test.go:259: continuous upload 1.326107334s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=false/expect=false - server_deadline_test.go:259: continuous upload 1.314532709s > idle 300ms -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=true -=== CONT TestServerContinuousUpload/tls=false/chunked=false/expect=false -=== CONT TestServerContinuousUpload/tls=false/chunked=true/expect=false -=== CONT TestServerContinuousUpload/tls=true/chunked=true/expect=true -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=true - server_deadline_test.go:259: continuous upload 1.308128834s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=true/expect=false - server_deadline_test.go:259: continuous upload 1.308309709s > idle 300ms -=== NAME TestServerContinuousUpload/tls=false/chunked=false/expect=false - server_deadline_test.go:259: continuous upload 1.30837825s > idle 300ms -=== NAME TestServerContinuousUpload/tls=true/chunked=true/expect=true - server_deadline_test.go:259: continuous upload 1.297138459s > idle 300ms ---- PASS: TestServerContinuousUpload (0.00s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=false (1.33s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=true (1.33s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=true (1.33s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=false/expect=false (1.33s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=true (1.31s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=true/expect=false (1.31s) - --- PASS: TestServerContinuousUpload/tls=false/chunked=false/expect=false (1.31s) - --- PASS: TestServerContinuousUpload/tls=true/chunked=true/expect=true (1.31s) -=== RUN TestServerDefaultIdleLongUpload - server_deadline_test.go:268: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle regression ---- SKIP: TestServerDefaultIdleLongUpload (0.00s) -=== RUN TestServerIdleBodyDeadline -=== RUN TestServerIdleBodyDeadline/tls=false -=== PAUSE TestServerIdleBodyDeadline/tls=false -=== RUN TestServerIdleBodyDeadline/tls=true -=== PAUSE TestServerIdleBodyDeadline/tls=true -=== CONT TestServerIdleBodyDeadline/tls=false -=== CONT TestServerIdleBodyDeadline/tls=true ---- PASS: TestServerIdleBodyDeadline (0.00s) - --- PASS: TestServerIdleBodyDeadline/tls=false (0.45s) - --- PASS: TestServerIdleBodyDeadline/tls=true (0.46s) -=== RUN TestServerBackgroundReadNoDeadline -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=false/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=false/body=true -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=false -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=false -=== RUN TestServerBackgroundReadNoDeadline/tls=true/body=true -=== PAUSE TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=false -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=false/body=true -=== CONT TestServerBackgroundReadNoDeadline/tls=true/body=false ---- PASS: TestServerBackgroundReadNoDeadline (0.00s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=false (0.71s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=false/body=true (0.71s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=false (0.71s) - --- PASS: TestServerBackgroundReadNoDeadline/tls=true/body=true (0.71s) -=== RUN TestServerTLSHandshakeReadDeadline -2026/09/15 15:55:04 http: TLS handshake error from 127.0.0.1:33250: read tcp 127.0.0.1:42477->127.0.0.1:33250: i/o timeout ---- PASS: TestServerTLSHandshakeReadDeadline (0.21s) -=== RUN TestServerConnStateHook -=== RUN TestServerConnStateHook/tls=false -=== RUN TestServerConnStateHook/tls=true ---- PASS: TestServerConnStateHook (0.01s) - --- PASS: TestServerConnStateHook/tls=false (0.00s) - --- PASS: TestServerConnStateHook/tls=true (0.01s) -=== RUN TestServerHTTP2Deadlines ---- PASS: TestServerHTTP2Deadlines (0.42s) -=== RUN TestServerEarlyBodyClose -=== RUN TestServerEarlyBodyClose/tls=false -=== PAUSE TestServerEarlyBodyClose/tls=false -=== RUN TestServerEarlyBodyClose/tls=true -=== PAUSE TestServerEarlyBodyClose/tls=true -=== CONT TestServerEarlyBodyClose/tls=false -=== CONT TestServerEarlyBodyClose/tls=true ---- PASS: TestServerEarlyBodyClose (0.00s) - --- PASS: TestServerEarlyBodyClose/tls=false (0.31s) - --- PASS: TestServerEarlyBodyClose/tls=true (0.31s) -=== RUN TestServerPipelinedDeadline -=== RUN TestServerPipelinedDeadline/tls=false -=== RUN TestServerPipelinedDeadline/tls=true ---- PASS: TestServerPipelinedDeadline (1.35s) - --- PASS: TestServerPipelinedDeadline/tls=false (0.67s) - --- PASS: TestServerPipelinedDeadline/tls=true (0.68s) -=== RUN TestServerHijackedDeadline -=== RUN TestServerHijackedDeadline/tls=false -=== PAUSE TestServerHijackedDeadline/tls=false -=== RUN TestServerHijackedDeadline/tls=true -=== PAUSE TestServerHijackedDeadline/tls=true -=== CONT TestServerHijackedDeadline/tls=true -=== CONT TestServerHijackedDeadline/tls=false ---- PASS: TestServerHijackedDeadline (0.00s) - --- PASS: TestServerHijackedDeadline/tls=false (0.70s) - --- PASS: TestServerHijackedDeadline/tls=true (0.71s) -=== RUN TestServerContinuousDownload -=== RUN TestServerContinuousDownload/tls=false -=== PAUSE TestServerContinuousDownload/tls=false -=== RUN TestServerContinuousDownload/tls=true -=== PAUSE TestServerContinuousDownload/tls=true -=== CONT TestServerContinuousDownload/tls=false -=== CONT TestServerContinuousDownload/tls=true -=== NAME TestServerContinuousDownload/tls=false - server_deadline_test.go:624: continuous download 1.327951834s > idle 300ms -=== NAME TestServerContinuousDownload/tls=true - server_deadline_test.go:624: continuous download 1.320759709s > idle 300ms ---- PASS: TestServerContinuousDownload (0.00s) - --- PASS: TestServerContinuousDownload/tls=false (1.33s) - --- PASS: TestServerContinuousDownload/tls=true (1.33s) -=== RUN TestServerDefaultIdleLongDownload - server_deadline_test.go:631: set SILO_TEST_LONG_UPLOAD=1 for >30s default-idle transfer regressions ---- SKIP: TestServerDefaultIdleLongDownload (0.00s) -=== RUN TestNewServer ---- PASS: TestNewServer (0.00s) -PASS -ok github.com/minio/minio/internal/http 14.683s diff --git a/docs/investigations/r8/evidence/original-reproducer-fixed.log b/docs/investigations/r8/evidence/original-reproducer-fixed.log deleted file mode 100644 index 01fd37e60..000000000 --- a/docs/investigations/r8/evidence/original-reproducer-fixed.log +++ /dev/null @@ -1,10 +0,0 @@ -=== RUN TestReviewR8AbsoluteHeaderTimeout -=== RUN TestReviewR8AbsoluteHeaderTimeout/standard-net-http - r8_baseline_test.go:56: request rejected after header timeout: unexpected EOF -=== RUN TestReviewR8AbsoluteHeaderTimeout/silo-listener - r8_baseline_test.go:56: request rejected after header timeout: unexpected EOF ---- PASS: TestReviewR8AbsoluteHeaderTimeout (0.80s) - --- PASS: TestReviewR8AbsoluteHeaderTimeout/standard-net-http (0.40s) - --- PASS: TestReviewR8AbsoluteHeaderTimeout/silo-listener (0.40s) -PASS -ok github.com/minio/minio/internal/http 1.312s diff --git a/docs/investigations/r8/evidence/quality-checks.json b/docs/investigations/r8/evidence/quality-checks.json deleted file mode 100644 index 6602a8e93..000000000 --- a/docs/investigations/r8/evidence/quality-checks.json +++ /dev/null @@ -1,186 +0,0 @@ -[ - { - "name": "vet-final", - "command": [ - "go", - "vet", - "./internal/deadlineconn", - "./internal/http" - ], - "started_at_utc": "2026-09-15T16:04:26.504181+00:00", - "exit_code": 0, - "log": "docs/investigations/r8/evidence/vet-final.log", - "log_sha256": "521e3dbf0e63e910c0025f52abf592799fe3a9e9329a7fc33cb9938df5578036" - }, - { - "name": "tidy-final", - "command": [ - "go", - "mod", - "tidy", - "-diff" - ], - "started_at_utc": "2026-09-15T16:04:26.810955+00:00", - "exit_code": 0, - "log": "docs/investigations/r8/evidence/tidy-final.log", - "log_sha256": "3ad6ef5392ed3ed22d5b0fc1d5e04665208487b3fb168910457b8197ab507471" - }, - { - "name": "whitespace-final", - "command": [ - "git", - "diff", - "--check" - ], - "started_at_utc": "2026-09-15T16:04:26.979546+00:00", - "exit_code": 0, - "log": "docs/investigations/r8/evidence/whitespace-final.log", - "log_sha256": "fa77ab6dcf895a30623113e37b6ee812c6cd35b0cbb9693a0c18de550e6b1cf6" - }, - { - "name": "linux-race-final", - "command": "docker run --rm ... golang:1.27.1-bookworm go test -p 2 -race ./internal/deadlineconn ./internal/http -count=1 -v", - "exit_code": 0, - "platform": "linux/arm64", - "log": "docs/investigations/r8/evidence/linux-race-final.log", - "log_sha256": "484577f3b3820ecfc078b38dd808e048f52704a8c3ffc0dad9100bff26ead1f3" - }, - { - "name": "darwin-race-final", - "command": "go test -race ./internal/deadlineconn ./internal/http -count=1 -v", - "exit_code": 0, - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/darwin-race-final.log", - "log_sha256": "e7f0bac82e1eb346ff53422bb2426995bcb7d5d06b1d81c1bb3cc8c2dcdc7774" - }, - { - "name": "review-refinements", - "command": "go test -race ./internal/http ./internal/deadlineconn -run ^Test(ServerHTTP2Deadlines|StrictReadDeadlineRepeatedRenewal)$ -count=3 -v", - "exit_code": 0, - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/review-refinements.log", - "log_sha256": "c9b46f946cedcca88b8ed3dc7e465a4ad7836b4add338e42db3da7572be6a83b" - }, - { - "name": "original-reproducer-fixed", - "command": "go test -overlay=/Users/vonng/tmp/silo-r8-01a0a5b9/baseline-overlay.json ./internal/http -run ^TestReviewR8AbsoluteHeaderTimeout$ -count=1 -v", - "exit_code": 0, - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/original-reproducer-fixed.log", - "log_sha256": "e787f5e5ad826518a39fdebbb43d28b6ceab36ed8e32267cf3a450af31ce9844" - }, - { - "name": "default-30s-transfers", - "command": "SILO_TEST_LONG_UPLOAD=1 go test ./internal/http -run ^TestServerDefaultIdleLong -count=1 -v", - "exit_code": 0, - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/default-30s-transfers.log", - "log_sha256": "378defff78ae565e95d52830e5b11c1a01a0a11efc14c2822228ca366df58bc3" - }, - { - "name": "grid", - "command": "go test ./internal/grid -run ^(TestSingleRoundtrip|TestDisconnect)$ -count=1 -v", - "exit_code": 0, - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/grid.log", - "log_sha256": "4f677621e0c927e7a37bbd4c8e29ec9c5879ae4aaaf185fe4c3e0a4c41c6ab10" - }, - { - "name": "configuration-baseline", - "command": "go test -p 2 -overlay=/Users/vonng/tmp/silo-r8-01a0a5b9/config-overlay.json ./cmd -run ^TestServerReadHeaderTimeoutConfig$ -count=1 -v", - "exit_code": 1, - "expected_failure": true, - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/config-baseline.log", - "log_sha256": "cc7f7bf345ecfe52db414a785ac3727dc7bf88ec6493de13c1ced1d215e706d8" - }, - { - "name": "runtime-v1", - "command": "python3 docs/investigations/r8/evidence/runtime_probe.py /Users/vonng/tmp/silo-r8-01a0a5b9/silo-v1 baseline /Users/vonng/tmp/silo-r8-01a0a5b9/runtime-v1", - "exit_code": 0, - "expected_behavior": "both configured sources still incorrectly accept delayed headers before the binding fix", - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/runtime-v1.json", - "log_sha256": "64808fb172e98e81c65c324f5bb967b91a931fa4a757508aa64aaac94b663ce1" - }, - { - "name": "lint-initial", - "command": "golangci-lint run --allow-serial-runners --timeout=5m ./internal/deadlineconn ./internal/http", - "exit_code": 1, - "reason": "staticcheck requested the promoted srv.Close selector; fixed without behavior change", - "log": "docs/investigations/r8/evidence/lint.log", - "log_sha256": "3370461b3b426035790fccc8b862538f3740f3c8455277518872de6befa6990a" - }, - { - "name": "configuration-fixed", - "command": "go test -p 2 ./cmd -run ^(TestServerReadHeaderTimeoutConfig|TestServerConfigFile)$ -count=1 -v", - "exit_code": 0, - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/config-fixed.log", - "log_sha256": "bf9b7b80ecaa011bcdd4e32d902c0e03d79dfa5f9016f6b43c65678542cc74bb" - }, - { - "name": "build-v2", - "command": "go build -p 2 -o /Users/vonng/tmp/silo-r8-01a0a5b9/silo-v2 .", - "exit_code": 0, - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/build-v2.log", - "log_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - }, - { - "name": "lint-final", - "command": "golangci-lint run --allow-serial-runners --timeout=5m ./internal/deadlineconn ./internal/http", - "exit_code": 0, - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/lint-final.log", - "log_sha256": "e92606b0bf483111dff0a120c315ea165821348f31365020e2468a0059095c47" - }, - { - "name": "runtime-v2", - "command": "python3 docs/investigations/r8/evidence/runtime_probe.py /Users/vonng/tmp/silo-r8-01a0a5b9/silo-v2 fixed /Users/vonng/tmp/silo-r8-01a0a5b9/runtime-v2", - "exit_code": 0, - "platform": "darwin/arm64", - "log": "docs/investigations/r8/evidence/runtime-v2.json", - "log_sha256": "253b0c192a3267731bfc46545befe0b7fe07cee6f9c5785d10a03ff782189499" - }, - { - "name": "configuration-race-extra", - "command": "go test -race -p 2 ./cmd -run ^TestServerReadHeaderTimeoutConfig$ -count=1 -v", - "exit_code": 1, - "blocked_by_environment": "Darwin external linker failed: errno=28 No space left on device", - "log": "docs/investigations/r8/evidence/config-race.log", - "log_sha256": "4cec7bba425217027939e01cce8d931a0ade98695bfb31529e71a2d748fc6295" - }, - { - "name": "legacy-timeout", - "command": "/bin/bash /Users/vonng/tmp/silo-r8-01a0a5b9/legacy-timeout/test-timeout-isolated.sh", - "exit_code": 255, - "status": "supplementary S3 script blocked by host storage capacity; normal PUT independently returns HTTP 507 XMinioStorageFull", - "log": "docs/investigations/r8/evidence/legacy-timeout.log", - "diagnostic": "docs/investigations/r8/evidence/s3-capacity-probe.json", - "log_sha256": "64f838a6a726e5ed2145924ea6bdbfe6ccb4a05dc1977baaa9658e7024888af6" - }, - { - "name": "s3-capacity-diagnostic", - "command": "python3 /Users/vonng/tmp/silo-r8-01a0a5b9/s3_capacity_probe.py", - "exit_code": 0, - "observed_http_status": 507, - "observed_s3_code": "XMinioStorageFull", - "log": "docs/investigations/r8/evidence/s3-capacity-probe.json", - "log_sha256": "51111b9d72437089c8642ef2e2c2b02cf9c81227ec9de9857ba938ea280d077a" - }, - { - "name": "delivery-whitespace", - "command": [ - "git", - "diff", - "--check", - "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "--" - ], - "exit_code": 0, - "log": "docs/investigations/r8/evidence/delivery-whitespace.log", - "log_sha256": "3a403d687fac110e2675ee7f86a4f8b3c095b54fa602535f9805a957c67b92c2", - "note": "R8 archive .gitattributes preserves raw .log/.patch whitespace; production and test source still checked normally." - } -] diff --git a/docs/investigations/r8/evidence/review-refinements.log b/docs/investigations/r8/evidence/review-refinements.log deleted file mode 100644 index 7430df38b..000000000 --- a/docs/investigations/r8/evidence/review-refinements.log +++ /dev/null @@ -1,16 +0,0 @@ -=== RUN TestServerHTTP2Deadlines ---- PASS: TestServerHTTP2Deadlines (0.42s) -=== RUN TestServerHTTP2Deadlines ---- PASS: TestServerHTTP2Deadlines (0.41s) -=== RUN TestServerHTTP2Deadlines ---- PASS: TestServerHTTP2Deadlines (0.42s) -PASS -ok github.com/minio/minio/internal/http 3.003s -=== RUN TestStrictReadDeadlineRepeatedRenewal ---- PASS: TestStrictReadDeadlineRepeatedRenewal (0.90s) -=== RUN TestStrictReadDeadlineRepeatedRenewal ---- PASS: TestStrictReadDeadlineRepeatedRenewal (0.90s) -=== RUN TestStrictReadDeadlineRepeatedRenewal ---- PASS: TestStrictReadDeadlineRepeatedRenewal (0.90s) -PASS -ok github.com/minio/minio/internal/deadlineconn 4.915s diff --git a/docs/investigations/r8/evidence/runtime-v1.json b/docs/investigations/r8/evidence/runtime-v1.json deleted file mode 100644 index f59a47999..000000000 --- a/docs/investigations/r8/evidence/runtime-v1.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "binary": "/Users/vonng/tmp/silo-r8-01a0a5b9/silo-v1", - "binary_sha256": "ed8d30cb40832f854bd82b083b9d1ec3ee16a35598416a54ef2d48bde8384ffc", - "expected_rejection": false, - "cases": [ - { - "source": "flag", - "header_timeout_ms": 100, - "idle_timeout_ms": 2000, - "header_completion_delay_ms": 400, - "rejected": false, - "status": "HTTP/1.1 200 OK", - "elapsed_seconds": 0.401, - "still_alive": true - }, - { - "source": "environment", - "header_timeout_ms": 100, - "idle_timeout_ms": 2000, - "header_completion_delay_ms": 400, - "rejected": false, - "status": "HTTP/1.1 200 OK", - "elapsed_seconds": 0.408, - "still_alive": true - } - ] -} diff --git a/docs/investigations/r8/evidence/runtime-v1.stderr.log b/docs/investigations/r8/evidence/runtime-v1.stderr.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r8/evidence/runtime-v2.json b/docs/investigations/r8/evidence/runtime-v2.json deleted file mode 100644 index 68da68d83..000000000 --- a/docs/investigations/r8/evidence/runtime-v2.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "binary": "/Users/vonng/tmp/silo-r8-01a0a5b9/silo-v2", - "binary_sha256": "6b982de3262c25e326280c739b4275444cf80c3eacae48f0942aa18fbb7cd654", - "expected_rejection": true, - "cases": [ - { - "source": "flag", - "header_timeout_ms": 100, - "idle_timeout_ms": 2000, - "header_completion_delay_ms": 400, - "rejected": true, - "status": "", - "elapsed_seconds": 0.403, - "still_alive": true - }, - { - "source": "environment", - "header_timeout_ms": 100, - "idle_timeout_ms": 2000, - "header_completion_delay_ms": 400, - "rejected": true, - "status": "", - "elapsed_seconds": 0.401, - "still_alive": true - } - ] -} diff --git a/docs/investigations/r8/evidence/runtime-v2.stderr.log b/docs/investigations/r8/evidence/runtime-v2.stderr.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r8/evidence/runtime_probe.py b/docs/investigations/r8/evidence/runtime_probe.py deleted file mode 100644 index 8947f5cc2..000000000 --- a/docs/investigations/r8/evidence/runtime_probe.py +++ /dev/null @@ -1,79 +0,0 @@ -"""Probe the compiled SILO CLI on disposable loopback-only single-disk servers.""" -import hashlib -import json -import os -from pathlib import Path -import signal -import socket -import subprocess -import sys -import tempfile -import time -import urllib.request - -binary = Path(sys.argv[1]).resolve() -expected_rejection = sys.argv[2] == 'fixed' -output_dir = Path(sys.argv[3]).resolve() -output_dir.mkdir(parents=True, exist_ok=True) -results = [] -for source in ('flag', 'environment'): - with socket.socket() as reservation: - reservation.bind(('127.0.0.1', 0)) - port = reservation.getsockname()[1] - data_dir = tempfile.mkdtemp(prefix=f'r8-{source}-', dir=output_dir) - env = {k: v for k, v in os.environ.items() if not k.startswith(('MINIO_', 'SILO_'))} - env.update(MINIO_ROOT_USER='r8localtest', MINIO_ROOT_PASSWORD='r8-local-disposable-test-only', MINIO_BROWSER='off') - args = [str(binary), 'server', f'--address=127.0.0.1:{port}', '--console-address=127.0.0.1:0', '--idle-timeout=2s'] - if source == 'flag': - args.append('--read-header-timeout=100ms') - else: - env['MINIO_READ_HEADER_TIMEOUT'] = '100ms' - args.append(data_dir) - with (output_dir / f'{source}-server.log').open('w') as server_log: - proc = subprocess.Popen(args, env=env, stdout=server_log, stderr=subprocess.STDOUT) - try: - url = f'http://127.0.0.1:{port}/minio/health/live' - started = time.monotonic() - while True: - if proc.poll() is not None: - raise RuntimeError(f'{source}: server exited {proc.returncode}; see its log') - try: - with urllib.request.urlopen(url, timeout=1) as resp: - if resp.status == 200: - break - except Exception: - pass - if time.monotonic() - started > 30: - raise RuntimeError(f'{source}: startup timed out') - time.sleep(0.1) - with socket.create_connection(('127.0.0.1', port), timeout=2) as conn: - conn.settimeout(3) - conn.sendall(b'GET /minio/health/live HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nX-Slow: ') - before = time.monotonic() - time.sleep(0.4) - try: - conn.sendall(b'done\r\n\r\n') - data = conn.recv(4096) - except (BrokenPipeError, ConnectionResetError): - data = b'' - rejected = not data - status_line = data.split(b'\r\n', 1)[0].decode('ascii', 'replace') - elapsed = time.monotonic() - before - with urllib.request.urlopen(url, timeout=2) as resp: - alive = resp.status == 200 - result = dict(source=source, header_timeout_ms=100, idle_timeout_ms=2000, - header_completion_delay_ms=400, rejected=rejected, status=status_line, - elapsed_seconds=round(elapsed, 3), still_alive=alive) - results.append(result) - if rejected != expected_rejection or not alive: - raise AssertionError(result) - finally: - proc.send_signal(signal.SIGTERM) - try: - proc.wait(timeout=10) - except subprocess.TimeoutExpired: - proc.kill() - proc.wait(timeout=5) -report = dict(binary=str(binary), binary_sha256=hashlib.sha256(binary.read_bytes()).hexdigest(), - expected_rejection=expected_rejection, cases=results) -print(json.dumps(report, indent=2)) diff --git a/docs/investigations/r8/evidence/s3-capacity-probe.json b/docs/investigations/r8/evidence/s3-capacity-probe.json deleted file mode 100644 index 06a88115f..000000000 --- a/docs/investigations/r8/evidence/s3-capacity-probe.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": 507, - "body": "\nXMinioStorageFullStorage backend has reached its minimum free drive threshold. Please delete a few objects to proceed.testobjecttestbucket/testbucket/testobject18D58A9DC7CF766804c30fe94f97d234a29260401af8d884660258a809670077dfa5454fa24cb0df" -} diff --git a/docs/investigations/r8/evidence/s3-capacity-probe.stderr.log b/docs/investigations/r8/evidence/s3-capacity-probe.stderr.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r8/evidence/s3_capacity_probe.py b/docs/investigations/r8/evidence/s3_capacity_probe.py deleted file mode 100644 index 0f053de02..000000000 --- a/docs/investigations/r8/evidence/s3_capacity_probe.py +++ /dev/null @@ -1,27 +0,0 @@ -from pathlib import Path -import os,subprocess,socket,tempfile,time,urllib.request,urllib.error,signal,json -root=Path('/Users/vonng/tmp/silo-r8-01a0a5b9/capacity-probe');root.mkdir(exist_ok=True) -with socket.socket() as reservation: - reservation.bind(('127.0.0.1',0));port=reservation.getsockname()[1] -env={k:v for k,v in os.environ.items() if not k.startswith(('MINIO_','MC_','SILO_'))} -env.update(MINIO_ROOT_USER='silo',MINIO_ROOT_PASSWORD='silo1234',MINIO_BROWSER='off',MINIO_CI_CD='1',MC_HOST_silo=f'http://silo:silo1234@127.0.0.1:{port}/') -url=f'http://127.0.0.1:{port}' -with (root/'server.log').open('w') as log: - proc=subprocess.Popen(['/Users/vonng/tmp/silo-r8-01a0a5b9/silo-v2','server',f'--address=127.0.0.1:{port}','--read-header-timeout=5s','--idle-timeout=5s',str(root/'data')],env=env,stdout=log,stderr=subprocess.STDOUT) - try: - for _ in range(100): - try: - with urllib.request.urlopen(url+'/minio/health/live',timeout=1):break - except Exception:time.sleep(.1) - cli=['/opt/homebrew/bin/mcli','--config-dir',str(root/'mcli')] - for args in [['mb','silo/testbucket'],['anonymous','set','public','silo/testbucket']]: - r=subprocess.run(cli+args,env=env,text=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT);assert r.returncode==0,r.stdout - req=urllib.request.Request(url+'/testbucket/testobject',data=b'x'*30,method='PUT') - try: - with urllib.request.urlopen(req,timeout=10) as response:result={'status':response.status,'body':response.read().decode()} - except urllib.error.HTTPError as error:result={'status':error.code,'body':error.read().decode()} - print(json.dumps(result,indent=2)) - finally: - proc.send_signal(signal.SIGTERM) - try:proc.wait(timeout=10) - except subprocess.TimeoutExpired:proc.kill();proc.wait() diff --git a/docs/investigations/r8/evidence/tidy-diff.log b/docs/investigations/r8/evidence/tidy-diff.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r8/evidence/tidy-final.log b/docs/investigations/r8/evidence/tidy-final.log deleted file mode 100644 index 73a05a2ff..000000000 --- a/docs/investigations/r8/evidence/tidy-final.log +++ /dev/null @@ -1,3 +0,0 @@ -$ go mod tidy -diff - -exit_code=0 diff --git a/docs/investigations/r8/evidence/vet-final.log b/docs/investigations/r8/evidence/vet-final.log deleted file mode 100644 index cf7c47989..000000000 --- a/docs/investigations/r8/evidence/vet-final.log +++ /dev/null @@ -1,3 +0,0 @@ -$ go vet ./internal/deadlineconn ./internal/http - -exit_code=0 diff --git a/docs/investigations/r8/evidence/vet.log b/docs/investigations/r8/evidence/vet.log deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/investigations/r8/evidence/whitespace-final.log b/docs/investigations/r8/evidence/whitespace-final.log deleted file mode 100644 index 9decb7c54..000000000 --- a/docs/investigations/r8/evidence/whitespace-final.log +++ /dev/null @@ -1,3 +0,0 @@ -$ git diff --check - -exit_code=0 diff --git a/docs/investigations/r8/final-source-manifest.json b/docs/investigations/r8/final-source-manifest.json deleted file mode 100644 index 5cbb4ebdd..000000000 --- a/docs/investigations/r8/final-source-manifest.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "created_at_utc": "2026-09-15T16:17:15.174123+00:00", - "baseline_sha": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "branch": "codex/r8-http-header-deadline", - "binding": "This manifest is committed with the source files it hashes; final local commit ID is reported in delivery.", - "accepted_plan": "plan-v2.md", - "plan_sha256": "426127ed9fb08aeddf8259ebdc4b1c24ebec8cda751a970ed99338a44b065f4c", - "production_diff_sha256": "92d3f43aa2bb4c636da830fc00c8a2939524d69bb8ab4bda4c4b284f043d3cf3", - "production_files": { - "cmd/common-main.go": "f8777fe8a07d175aceee07b4dd13792b2384449c404c004c38a06893be997843", - "cmd/server-main.go": "04c265de211412ba0297396928096d7f2d971244a957a3126154846035263514", - "internal/deadlineconn/deadlineconn.go": "b9272ef640f1d4403b3d0af6cdbaba9186c51ad9a0226dfe449e8ef738e1ec4b", - "internal/http/listener.go": "49628575367f6ab9b6986caf594726d74d370f7d2ac4eed582903600b6eb3fa2", - "internal/http/server.go": "b7b0355f2781f8c5f7c77bc910cd4180cd3e5f22a87de41bd35ef119d36b4cdf" - }, - "test_files": { - "cmd/server_deadline_config_test.go": "1013157f83baa5f7882ec2d41c7b1fccb9e05fb418d0fa61263953037c9698c4", - "internal/deadlineconn/deadlineconn_strict_test.go": "f405690c9ff044595f48323d68f4a9b33ce695b3ad6820f54f17151067bfae5e", - "internal/http/dial_deadline_linux_test.go": "0939d05b72a09760d53fcdf249775989e3f89bca824b9961d0b2a657ebfdf41e", - "internal/http/server_deadline_test.go": "a6e687b3904a876fa92a4c5b86453159f3e5a38a4b9412dc213c7772f47fbf0b" - }, - "dependencies_unchanged": { - "go.mod": "8351bb86377a8deed95fd0bd67c1e363e11d33f8da7631cea8f68cea11259976", - "go.sum": "2287ce975cab91f92f59a3b6e164d49325f141f35d480b8c3d23f796df3772b2" - } -} diff --git a/docs/investigations/r8/plan-v1.md b/docs/investigations/r8/plan-v1.md deleted file mode 100644 index 7a1aa9e3c..000000000 --- a/docs/investigations/r8/plan-v1.md +++ /dev/null @@ -1,82 +0,0 @@ -# R8 plan v1 — phase-aware read deadline preservation - -## Baseline and authorization - -- Worktree: `/Users/vonng/.codex/worktrees/3bae/silo`; HEAD and origin/main: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a` (live checked). -- Go: go1.27.1 darwin/arm64; CLI: Claude Code 2.1.270. No production implementation has started. -- Root AGENTS.md is ignored and was absent from the generated worktree; copied the actual primary checkout guide into this worktree. Maintained PGSTY stack and inexpensive compatibility govern this repair. -- GitHub open PRs: #184 and #187, neither changes R8. No R8 implementation PR found. -- Parent workflow permits research and temporary reproductions before consensus; ordinary implementation after explicit same-version Codex/Opus consensus. Merge, publish and deployment excluded. - -## Evidence and cause - -`evidence/baseline.log` is a fresh real TCP run on this SHA with the parent's temporary Go overlay. Header limit 100ms, connection idle 2s, header finishes at 400ms. Standard net/http rejects; SILO returns HTTP 204. This is inherited DeadlineConn behavior, not a new SILO option. - -`DeadlineConn.Read` calls `setReadDeadline`, replacing an explicit future deadline by now+readIdle+250ms. Past deadlines and explicit zero are already special (abort and disable respectively). `cmd/server-main.go` sets ReadTimeout and WriteTimeout to IdleTimeout as well as setting TCPOptions.IdleTimeout. Therefore globally preserving every explicit deadline makes normal HTTP/1 uploads time out at the idle duration in total. - -Current Go source examined: `/opt/homebrew/Cellar/go/1.27.1/libexec/src/net/http/server.go` and `net/http/internal/http2/server.go`. Public semantics: https://pkg.go.dev/net/http#Server and https://pkg.go.dev/net#Conn . - -- StateNew occurs before serving; TLS read timeout is min positive header/read/write timeouts. -- HTTP/1 reads under the configured header deadline. After parsing/validation, readRequest sets wholeReqDeadline based on ReadTimeout, then the serving loop calls StateActive (also on malformed input, followed by rejection). -- With a body, net/http registers an EOF callback; without a body it immediately calls startBackgroundRead. That clears the read deadline. Body EOF also clears the deadline before launching background disconnect detection. -- StateIdle happens after finishRequest/abortPendingRead, before setting keep-alive deadline and peeking the next request. The next header deadline is then installed. Thus StateIdle can restore strictness for both waiting and reading the next header. -- TLS wrapping remains `tls.Conn -> DeadlineConn -> TCPConn`; use tls.Conn.NetConn to reach the wrapper in the state callback. -- TLS can negotiate h2 (`cmd/utils.go`). HTTP/2 uses per-stream ReadTimeout (currently an absolute body limit), clears the underlying read deadline after handshake, and has independent connection state hooks. Do not impose a connection-wide request timer on multiplexed h2. Preserve this existing behavior; test HTTP/2 smoke and record that its preexisting absolute stream timeout is not fixed by this R8 HTTP/1 change. - -## Options and decision - -1. Globally clamp all future deadlines: too broad; turns ReadTimeout=idle into a total HTTP/1 upload limit, changes Linux internode caller behavior. -2. Remove ReadTimeout and interpret zero as rolling idle: wrong; zero is also how net/http disables background read deadlines, and a long handler could be spuriously canceled. H2 loses its existing per-stream read timeout. -3. Body-reader/ResponseController timer wrappers: possible but introduce body/drain/EOF bookkeeping, affect buffered/chunked reads, require h2-specific treatment and rewrite behavior beyond the header bug. -4. Selected: opt-in preservation of explicit read deadlines during HTTP/1 header/keep-alive/TLS phases; retain legacy rolling reads during the HTTP/1 body/handler phase. Keep generic DeadlineConn default and existing production timeout configuration. - -## Concrete implementation - -### internal/deadlineconn/deadlineconn.go - -- Add mutex-protected last explicit read deadline and a `readDeadlineStrict` boolean (default false). Existing constructors and internode callers retain rolling semantics. -- Store the explicit read timestamp in SetReadDeadline and SetDeadline; preserve zero/abort flags and immediate forwarding to net.Conn. -- Expose a small `SetReadDeadlineStrict(bool)` method, documented as toggling whether automatic idle renewal may extend explicit deadlines. Lock the same mutex and reset readSetAt so the next read applies the current mode. -- In setReadDeadline, recheck abort/inf under the lock, keep existing throttling and idle slack, and when strict and explicit is nonzero take min(idleCandidate, explicit). Never add 250ms slack to the absolute limit. Deadline updates themselves are never throttled. -- Writes remain unchanged. Expired explicit times remain expired when strict. Existing explicit zero always disables read renewal, and explicit past time keeps abort semantics. - -### internal/http/listener.go - -- Keep the accepted concrete type *DeadlineConn, read/write idle durations and unwrap compatibility. -- Enable strict read mode before returning each newly accepted connection. This covers initial HTTP headers and TLS handshake reads, including a normal net/http Server using this listener. - -### internal/http/server.go - -- In Init, compose (do not drop) the caller's existing ConnState hook. -- Find the *DeadlineConn, unwrapping one *tls.Conn with NetConn when necessary. -- For HTTP/1 StateActive: turn strict mode off, retaining the current rolling ReadTimeout=idle semantics for bodies and long uploads. Do so before the caller's state hook. -- For StateNew/StateIdle: turn strict mode on. Ignore other states. -- For negotiated HTTP/2, skip per-request phase changes; after handshake raw zero deadlines stay disabled, and stream timeouts remain native net/http behavior. -- Do not remove ReadTimeout/WriteTimeout or change flags. Add a short explanatory comment around the production timeout setup if useful. - -## Failure paths and compatibility - -- Slow/incomplete headers (including byte trickles across many 250ms update intervals): explicit cap must hold. -- Continuous uploads: request duration can exceed idle; underlying socket reads renew with existing +250ms slack. A truly stalled socket body read times out. No promise of application CPU/storage wait deadlines. -- Empty and completed bodies: net/http's zero deadline must keep disconnect detection from timing out otherwise active long handlers. -- Keep-alive: caller sets idle wait, then next header absolute timeout; both remain capped after StateIdle. -- TLS: read handshake cap cannot be renewed; completed handshake transitions into fresh HTTP header cap. Write-side handshake deadline behavior is unchanged/out of this read-side defect. -- Chunked encoding/Expect 100-continue/early close: preserve existing generic read path; exercise actual requests. -- Explicit body deadlines retain historical SILO behavior (future can renew; past abort works); this patch does not promise a generic net.Conn behavior migration. -- Linux internode DriveOPTimeout caller uses default mode false; same read/write/zero/abort behavior. Grid raw upgrade still unwraps the same concrete type; TLS upgraded connections stay in legacy body mode. No data/format migration or stored-state rewriting. - -## Validation matrix - -1. Deterministic recording net.Conn tests: strict future cap, strict far-future deadline bounded by idle, no slack on cap, mode transition, default rolling behavior, SetDeadline/read direction separation, zero disable, past cancellation, explicit updates resetting throttle, expired future deadline, concurrent Read/SetReadDeadline (race). -2. Real TCP HTTP/1: initial slow header; byte trickle longer than header timeout; successful ordinary request; keep-alive idle and second slow header; continuous long body exceeding scaled idle; truly idle body; chunked body and Expect 100-continue; early body close followed by next request where supported; handler spending >idle after no body/after EOF without request context cancellation. -3. Run HTTP/1 body and header matrix through TLS (real tls.Client), plus stalled TLS ClientHello/handshake. Test existing ConnState hook chaining. -4. HTTP/2 over real TLS smoke and native timeout preservation; explicitly no cross-stream socket timeout introduced. -5. DeadlineConn existing tests and internal/http full suite under race, targeted grid roundtrips/disconnect; Linux compile for modified packages and default caller behavior fixture (Darwin does not exercise dial_linux at runtime). -6. One explicit >30s continuously progressing upload using production default idle, both cleartext and TLS HTTP/1 if feasible, to guard against an unintended total-30s cap. This may be opt-in to keep the routine suite fast, but run it for acceptance. -7. Scope tests, gofmt, git diff --check. If Linux runtime available without unrelated environment changes, run focused tests there; otherwise report compile vs runtime separately. - -## Work and delivery - -Estimate: 1–3 engineer days including design review and matrix; expected production diff tens of lines plus tests. Isolated `codex/` branch after agreement. Save raw Opus JSONL and stderr outside Git; keep prompt, exact plan SHA256, actual assistant model identity, effort, reviewer text, issue dispositions and consensus record here. No commit/push required for local review, and no merge/release/deploy is authorized in this phase. - -Codex position: recommend option 4. Opus must independently verify the phase ordering, EOF/background behavior, keep-alive, TLS/H2 and shared-caller boundaries. Any blocking disagreement requires a revised plan and another review; a failed/limited/wrong-model response is not consensus. diff --git a/docs/investigations/r8/plan-v2.md b/docs/investigations/r8/plan-v2.md deleted file mode 100644 index 43d4f25b9..000000000 --- a/docs/investigations/r8/plan-v2.md +++ /dev/null @@ -1,115 +0,0 @@ -# R8 plan v2 — configuration propagation and phase-aware read deadline preservation - -## Baseline and authorization - -- Worktree: `/Users/vonng/.codex/worktrees/3bae/silo`; HEAD and origin/main: `9ebe81c1b3611f9cc73e676b5b741c2be62c467a` (live checked). -- Go: go1.27.1 darwin/arm64; CLI: Claude Code 2.1.270. V1 connection implementation exists after v1 consensus; the additional configuration binding below is NOT implemented yet. -- Root AGENTS.md is ignored and was absent from the generated worktree; copied the actual primary checkout guide into this worktree. Maintained PGSTY stack and inexpensive compatibility govern this repair. -- GitHub open PRs: #184 and #187, neither changes R8. No R8 implementation PR found. Local branch is `codex/r8-http-header-deadline`; no commits/push/merge/release/deployment. -- Parent workflow permits research and temporary reproductions before consensus; ordinary implementation after explicit same-version Codex/Opus consensus. Merge, publish and deployment excluded. - -## V2 revision boundary and new evidence - -This v2 is the complete plan: retain the agreed v1 connection design below and add the missing CLI/env-to-server-context assignment. V1 approval does not authorize this newly discovered binding change. The parent explicitly confirmed that v2 needs its own same-version Opus agreement before the binding is edited. - -Fresh temporary CLI tests are saved in `evidence/config-baseline.log` and `evidence/config_baseline_test.go.txt`. They invoke the real `serverCmd.Flags` through a `cli.App`, then the actual `buildServerCtxt`; no production configuration code was changed to obtain the evidence. The CLI reports default=30s, flag=100ms, env=170ms, flag-over-env=80ms, YAML+flag=100ms, negative=-1s. The context remains 0 in all those cases. Explicit zero naturally remains 0. Initial fixture mistakes (wrong variable name, empty env vs absent env) are saved separately and are not product findings. - -Root cause: `cmd/common-main.go:447` transfers IdleTimeout but omits ReadHeaderTimeout. The only production reads of that field are the Server.UseReadHeaderTimeout call. Thus after the v1 connection fix, a real process still ignores the configured header limit and Go falls back from zero ReadHeaderTimeout to ReadTimeout=idle. Defaults accidentally appear correct only when the two durations match (30s). - -V2 addition is one assignment: `ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout")` next to IdleTimeout. No new option/schema, dependency, storage change, or new timeout policy. Preserve the current cli library's precedence and error checking, including the fact that an explicitly empty environment duration is invalid. - -## Evidence and cause - -`evidence/baseline.log` is a fresh real TCP run on this SHA with the parent's temporary Go overlay. Header limit 100ms, connection idle 2s, header finishes at 400ms. Standard net/http rejects; SILO returns HTTP 204. This is inherited DeadlineConn behavior, not a new SILO option. - -`DeadlineConn.Read` calls `setReadDeadline`, replacing an explicit future deadline by now+readIdle+250ms. Past deadlines and explicit zero are already special (abort and disable respectively). `cmd/server-main.go` sets ReadTimeout and WriteTimeout to IdleTimeout as well as setting TCPOptions.IdleTimeout. Therefore globally preserving every explicit deadline makes normal HTTP/1 uploads time out at the idle duration in total. - -Current Go source examined: `/opt/homebrew/Cellar/go/1.27.1/libexec/src/net/http/server.go` and `net/http/internal/http2/server.go`. Public semantics: https://pkg.go.dev/net/http#Server and https://pkg.go.dev/net#Conn . - -- StateNew occurs before serving; TLS read timeout is min positive header/read/write timeouts. -- HTTP/1 reads under the configured header deadline. After parsing/validation, readRequest sets wholeReqDeadline based on ReadTimeout, then the serving loop calls StateActive (also on malformed input, followed by rejection). -- With a body, net/http registers an EOF callback; without a body it immediately calls startBackgroundRead. That clears the read deadline. Body EOF also clears the deadline before launching background disconnect detection. -- StateIdle happens after finishRequest/abortPendingRead, before setting keep-alive deadline and peeking the next request. The next header deadline is then installed. Thus StateIdle can restore strictness for both waiting and reading the next header. -- TLS wrapping remains `tls.Conn -> DeadlineConn -> TCPConn`; use tls.Conn.NetConn to reach the wrapper in the state callback. -- TLS can negotiate h2 (`cmd/utils.go`). HTTP/2 uses per-stream ReadTimeout (currently an absolute body limit), clears the underlying read deadline after handshake, and has independent connection state hooks. Do not impose a connection-wide request timer on multiplexed h2. Preserve this existing behavior; test HTTP/2 smoke and record that its preexisting absolute stream timeout is not fixed by this R8 HTTP/1 change. - -## Options and decision - -1. Globally clamp all future deadlines: too broad; turns ReadTimeout=idle into a total HTTP/1 upload limit, changes Linux internode caller behavior. -2. Remove ReadTimeout and interpret zero as rolling idle: wrong; zero is also how net/http disables background read deadlines, and a long handler could be spuriously canceled. H2 loses its existing per-stream read timeout. -3. Body-reader/ResponseController timer wrappers: possible but introduce body/drain/EOF bookkeeping, affect buffered/chunked reads, require h2-specific treatment and rewrite behavior beyond the header bug. -4. Selected: opt-in preservation of explicit read deadlines during HTTP/1 header/keep-alive/TLS phases; retain legacy rolling reads during the HTTP/1 body/handler phase. Keep generic DeadlineConn default and existing production timeout configuration. - -## Concrete implementation - -### cmd/common-main.go (V2 addition, pending new consensus) - -- Add the single assignment shown above in buildServerCtxt beside the other TCP/HTTP duration options. -- Default 30s becomes effective independently of idle. An explicit positive header timeout is honored; flag precedence over env stays in the CLI. Zero retains Go's fallback to ReadTimeout; a negative value disables Go's header cap. Socket idle and past/zero DeadlineConn rules remain as described below. -- YAML merging does not contain or overwrite these duration fields; do not invent a YAML option. Test that CLI/env values survive the existing merge. -- This expands the user-visible behavior changes listed in v1: previously ignored defaults/overrides now take effect. With idle customized or disabled, header timeout can therefore differ from prior observed behavior, exactly as the declared option intends. No new migration or confirmation required for the ordinary code fix after consensus. - -### internal/deadlineconn/deadlineconn.go - -- Add mutex-protected last explicit read deadline and a `readDeadlineStrict` boolean (default false). Existing constructors and internode callers retain rolling semantics. -- Store the explicit read timestamp in SetReadDeadline and SetDeadline; preserve zero/abort flags and immediate forwarding to net.Conn. -- Expose a small `SetReadDeadlineStrict(bool)` method, documented as toggling whether automatic idle renewal may extend explicit deadlines. Lock the same mutex and reset readSetAt so the next read applies the current mode. -- In setReadDeadline, recheck abort/inf under the lock, keep existing throttling and idle slack, and when strict and explicit is nonzero take min(idleCandidate, explicit). Never add 250ms slack to the absolute limit. Deadline updates themselves are never throttled. -- Writes remain unchanged. Expired explicit times remain expired when strict. Existing explicit zero always disables read renewal, and explicit past time keeps abort semantics. - -### internal/http/listener.go - -- Keep the accepted concrete type *DeadlineConn, read/write idle durations and unwrap compatibility. -- Enable strict read mode before returning each newly accepted connection. This covers initial HTTP headers and TLS handshake reads, including a normal net/http Server using this listener. - -### internal/http/server.go - -- In Init, compose (do not drop) the caller's existing ConnState hook. -- Find the *DeadlineConn, unwrapping one *tls.Conn with NetConn when necessary. -- For HTTP/1 StateActive: turn strict mode off, retaining the current rolling ReadTimeout=idle semantics for bodies and long uploads. Do so before the caller's state hook. -- For StateNew/StateIdle: turn strict mode on. Ignore other states. -- For negotiated HTTP/2, skip per-request phase changes; after handshake raw zero deadlines stay disabled, and stream timeouts remain native net/http behavior. -- Do not remove ReadTimeout/WriteTimeout or change flags. Add a short explanatory comment around the production timeout setup if useful. - -## Failure paths and compatibility - -- Slow/incomplete headers (including byte trickles across many 250ms update intervals): explicit cap must hold. -- Continuous uploads: request duration can exceed idle; underlying socket reads renew with existing +250ms slack. A truly stalled socket body read times out. No promise of application CPU/storage wait deadlines. -- Empty and completed bodies: net/http's zero deadline must keep disconnect detection from timing out otherwise active long handlers. -- Keep-alive: caller sets idle wait, then next header absolute timeout; both remain capped after StateIdle. -- TLS: read handshake cap cannot be renewed; completed handshake transitions into fresh HTTP header cap. Write-side handshake deadline behavior is unchanged/out of this read-side defect. -- Chunked encoding/Expect 100-continue/early close: preserve existing generic read path; exercise actual requests. -- Explicit body deadlines retain historical SILO behavior (future can renew; past abort works); this patch does not promise a generic net.Conn behavior migration. -- Linux internode DriveOPTimeout caller uses default mode false; same read/write/zero/abort behavior. Grid raw upgrade still unwraps the same concrete type; TLS upgraded connections stay in legacy body mode. No data/format migration or stored-state rewriting. - -## Validation matrix - -V2 adds two separately evidenced chains: - -- Configuration: use the real CLI parser and actual buildServerCtxt; cover default, explicit flag, environment, flag-over-env, YAML merge, zero and negative duration. Run focused `./cmd` tests. Verify parser output and context output independently. -- Compiled process: build SILO on this exact worktree; launch disposable single-disk servers bound only to 127.0.0.1 with browser disabled and isolated test credentials/data. For both flag and env source, set header=100ms, idle=2s, finish a real health request header after 400ms. Preserve pre-binding evidence (v1 binary still accepts), and require the v2 binary to reject while a subsequent ordinary health request still succeeds. Stop only the child processes this test started. Use `evidence/runtime_probe.py`, retaining binary SHA256 and logs in the task's temp directory. This is local runtime verification, not deployment or S3 production acceptance. - -The retained connection matrix follows: - -1. Deterministic recording net.Conn tests: strict future cap, strict far-future deadline bounded by idle, no slack on cap, mode transition, default rolling behavior, SetDeadline/read direction separation, zero disable, past cancellation, explicit updates resetting throttle, expired future deadline, concurrent Read/SetReadDeadline (race). -2. Real TCP HTTP/1: initial slow header; byte trickle longer than header timeout; successful ordinary request; keep-alive idle and second slow header; continuous long body exceeding scaled idle; truly idle body; chunked body and Expect 100-continue; early body close followed by next request where supported; handler spending >idle after no body/after EOF without request context cancellation. -3. Run HTTP/1 body and header matrix through TLS (real tls.Client), plus stalled TLS ClientHello/handshake. Test existing ConnState hook chaining. -4. HTTP/2 over real TLS smoke and native timeout preservation; explicitly no cross-stream socket timeout introduced. -5. DeadlineConn existing tests and internal/http full suite under race, targeted grid roundtrips/disconnect; Linux compile for modified packages and default caller behavior fixture (Darwin does not exercise dial_linux at runtime). -6. One explicit >30s continuously progressing upload using production default idle, both cleartext and TLS HTTP/1 if feasible, to guard against an unintended total-30s cap. This may be opt-in to keep the routine suite fast, but run it for acceptance. -7. Scope tests, gofmt, git diff --check. If Linux runtime available without unrelated environment changes, run focused tests there; otherwise report compile vs runtime separately. - -## Work and delivery - -Estimate: 1–3 engineer days including design review and matrix; expected production diff tens of lines plus tests. Isolated `codex/` branch after agreement. Save raw Opus JSONL and stderr outside Git; keep prompt, exact plan SHA256, actual assistant model identity, effort, reviewer text, issue dispositions and consensus record here. No commit/push required for local review, and no merge/release/deploy is authorized in this phase. - -Codex position: recommend option 4 plus the one-line configuration binding; both are needed for the configured HTTP header deadline to work in the actual server. Opus must independently verify the phase ordering, EOF/background behavior, keep-alive, TLS/H2 and shared-caller boundaries. Any blocking disagreement requires a revised plan and another review; a failed/limited/wrong-model response is not consensus. - -## Current v1 implementation evidence and review refinements - -- Actual initial 100ms/400ms standard/SILO TCP comparison passes after the connection fix (`original-reproducer-fixed.log`). -- macOS and Linux arm64 full `internal/deadlineconn` + `internal/http` race suites pass. Linux explicitly exercises the optional DriveOPTimeout dialer. Grid roundtrip/disconnect and focused go vet pass. -- With default 30s idle, plaintext and TLS HTTP/1 uploads AND downloads continued for 33s and succeeded (`default-30s-transfers.log`). -- Nine v1 review notes are individually resolved in `consensus.md`: pipelined parsing test; explicit h2-only TLS negotiation and HTTP/2 response assertions; background EOF and hijack zero semantics; long download coverage; unchanged TLS write-handshake limitation; default internode DriveOPTimeout currently commented out. -- The H2 test initially fell back to H1, then the native read/write timers produced differing errors when racing. Both fixture problems were fixed, retained as raw evidence, and the final test isolates the read timer by clearing the stream write deadline; checks concurrent healthy streams and reuse of the same TLS connection. No production H2 behavior changed. -- V1 production diff remains exactly the previously reviewed phase-aware design. V2 will not be declared complete until both configuration and connection chains pass. Remaining boundaries: HTTP/2's preexisting absolute per-stream timeout and the legacy TLS handshake write deadline behavior remain outside R8. diff --git a/docs/investigations/r8/review/final-production.patch b/docs/investigations/r8/review/final-production.patch deleted file mode 100644 index 49b8aed0a..000000000 --- a/docs/investigations/r8/review/final-production.patch +++ /dev/null @@ -1,149 +0,0 @@ -diff --git a/cmd/common-main.go b/cmd/common-main.go -index 393ab17d0..c127759fc 100644 ---- a/cmd/common-main.go -+++ b/cmd/common-main.go -@@ -445,6 +445,7 @@ func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) { - ctxt.SendBufSize = ctx.Int("send-buf-size") - ctxt.RecvBufSize = ctx.Int("recv-buf-size") - ctxt.IdleTimeout = ctx.Duration("idle-timeout") -+ ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout") - ctxt.UserTimeout = ctx.Duration("conn-user-timeout") - - if conf := ctx.String("config"); len(conf) > 0 { -diff --git a/cmd/server-main.go b/cmd/server-main.go -index 48ed0f87d..c8a3a19ca 100644 ---- a/cmd/server-main.go -+++ b/cmd/server-main.go -@@ -901,6 +901,8 @@ func serverMain(ctx *cli.Context) { - close(globalGridStart) - close(globalLockGridStart) - -+ // The HTTP/1 listener preserves absolute header deadlines and renews the -+ // body read/write idle limits, so transfers may outlast IdleTimeout. - httpServer := xhttp.NewServer(getServerListenAddrs()). - UseHandler(setCriticalErrorHandler(corsHandler(handler))). - UseTLSConfig(newTLSConfig(getCert)). -diff --git a/internal/deadlineconn/deadlineconn.go b/internal/deadlineconn/deadlineconn.go -index 95bb43eff..5fa5a1403 100644 ---- a/internal/deadlineconn/deadlineconn.go -+++ b/internal/deadlineconn/deadlineconn.go -@@ -34,6 +34,8 @@ type DeadlineConn struct { - net.Conn - readDeadline time.Duration // sets the read deadline on a connection. - readSetAt time.Time -+ readExplicit time.Time // last deadline requested by the caller. -+ readDeadlineStrict bool // idle renewal must not extend readExplicit. - writeDeadline time.Duration // sets the write deadline on a connection. - writeSetAt time.Time - abortReads, abortWrites atomic.Bool // A deadline was set to indicate caller wanted the conn to time out. -@@ -59,17 +61,31 @@ func (c *DeadlineConn) setReadDeadline() { - - c.mu.Lock() - defer c.mu.Unlock() -- if c.abortReads.Load() { -+ if c.abortReads.Load() || c.infReads.Load() { - return - } - - now := time.Now() - if now.Sub(c.readSetAt) > updateInterval { -- c.Conn.SetReadDeadline(now.Add(c.readDeadline + updateInterval)) -+ deadline := now.Add(c.readDeadline + updateInterval) -+ if c.readDeadlineStrict && !c.readExplicit.IsZero() && c.readExplicit.Before(deadline) { -+ deadline = c.readExplicit -+ } -+ c.Conn.SetReadDeadline(deadline) - c.readSetAt = now - } - } - -+// SetReadDeadlineStrict controls whether idle renewal may extend a deadline set -+// by SetReadDeadline or SetDeadline. The default is false. Explicit zero and -+// past deadlines retain their disable/cancel semantics in either mode. -+func (c *DeadlineConn) SetReadDeadlineStrict(strict bool) { -+ c.mu.Lock() -+ defer c.mu.Unlock() -+ c.readDeadlineStrict = strict -+ c.readSetAt = time.Time{} -+} -+ - func (c *DeadlineConn) setWriteDeadline() { - // Do not set a Write deadline, if upstream wants to cancel all reads. - if c.writeDeadline <= 0 || c.abortWrites.Load() || c.infWrites.Load() { -@@ -115,6 +131,7 @@ func (c *DeadlineConn) SetDeadline(t time.Time) error { - defer c.mu.Unlock() - - c.readSetAt = time.Time{} -+ c.readExplicit = t - c.writeSetAt = time.Time{} - c.abortReads.Store(!t.IsZero() && time.Until(t) < 0) - c.abortWrites.Store(!t.IsZero() && time.Until(t) < 0) -@@ -132,6 +149,7 @@ func (c *DeadlineConn) SetReadDeadline(t time.Time) error { - c.abortReads.Store(!t.IsZero() && time.Until(t) < 0) - c.infReads.Store(t.IsZero()) - c.readSetAt = time.Time{} -+ c.readExplicit = t - return c.Conn.SetReadDeadline(t) - } - -diff --git a/internal/http/listener.go b/internal/http/listener.go -index bc6de3af9..14d34f6ea 100644 ---- a/internal/http/listener.go -+++ b/internal/http/listener.go -@@ -70,7 +70,10 @@ func (listener *httpListener) Accept() (conn net.Conn, err error) { - if result.err != nil { - return nil, result.err - } -- return deadlineconn.New(result.conn).WithReadDeadline(listener.opts.IdleTimeout).WithWriteDeadline(listener.opts.IdleTimeout), result.err -+ conn := deadlineconn.New(result.conn).WithReadDeadline(listener.opts.IdleTimeout).WithWriteDeadline(listener.opts.IdleTimeout) -+ // Server.Init switches to rolling reads only after HTTP/1 headers are read. -+ conn.SetReadDeadlineStrict(true) -+ return conn, nil - case <-listener.ctxDoneCh: - } - return nil, syscall.EINVAL -diff --git a/internal/http/server.go b/internal/http/server.go -index 2934fda6c..d9c19a33f 100644 ---- a/internal/http/server.go -+++ b/internal/http/server.go -@@ -29,6 +29,7 @@ import ( - "time" - - "github.com/dustin/go-humanize" -+ "github.com/minio/minio/internal/deadlineconn" - ) - - var ( -@@ -123,6 +124,32 @@ func (srv *Server) Init(listenCtx context.Context, listenErrCallback func(listen - srv.listener = listener - srv.listenerMutex.Unlock() - -+ connState := srv.ConnState -+ srv.ConnState = func(conn net.Conn, state http.ConnState) { -+ raw := conn -+ if tlsConn, ok := raw.(*tls.Conn); ok { -+ if tlsConn.ConnectionState().NegotiatedProtocol == "h2" { -+ // HTTP/2 owns its stream deadlines; do not change the connection. -+ raw = nil -+ } else { -+ raw = tlsConn.NetConn() -+ } -+ } -+ if dc, ok := raw.(*deadlineconn.DeadlineConn); ok { -+ switch state { -+ case http.StateNew, http.StateIdle: -+ dc.SetReadDeadlineStrict(true) -+ case http.StateActive: -+ // net/http has finished reading the headers, including buffered -+ // requests. Keep ReadTimeout as a rolling idle limit for uploads. -+ dc.SetReadDeadlineStrict(false) -+ } -+ } -+ if connState != nil { -+ connState(conn, state) -+ } -+ } -+ - var l net.Listener = listener - if tlsConfig != nil { - l = tls.NewListener(listener, tlsConfig) diff --git a/docs/investigations/r8/review/implementation-dispositions.md b/docs/investigations/r8/review/implementation-dispositions.md deleted file mode 100644 index 4aaba21f1..000000000 --- a/docs/investigations/r8/review/implementation-dispositions.md +++ /dev/null @@ -1,16 +0,0 @@ -# V1 implementation-review dispositions (carried into v2) - -The real Opus implementation review returned REQUEST_CHANGES with one test/dependency blocker and no production-behavior defect. This is not recorded as implementation approval. - -- B1 accepted: the test's direct x/net/http2 import would change the indirect annotation during tidy. Removed that import and use the Go1.27 standard-library HTTP/2 client with an explicit Protocols set containing only HTTP/2. Keep the server's HTTP/1-first ALPN order, and assert actual h2 negotiation and HTTP/2.0 response. No go.mod/go.sum change. Revalidation and dependency hygiene follow. -- N1 accepted: add repeated-renewal recording-connection test across three actual 300ms intervals, without resetting the cap. -- N2 noted: the existing concurrent test is a race probe; semantic assertions are supplied by separate deadline and HTTP tests, not inferred from that probe. -- N3 accepted for h2: give the fixture a 3s server keep-alive idle period while retaining 400ms ReadTimeout and isolating the native read timer. Small scaled streaming tests retain their prior margin, separately backed by 33s production-default runs. -- N4 retained: local conn shadowing is legal, no behavior concern; avoid unrelated cleanup. -- N5 retained: redundant deadline calls are low-cost and intentional; no performance redesign. -- N6 retained: explicit h2 exclusion documents the multiplexed boundary and is safe; zero deadlines remain the fundamental compatibility mechanism. -- N7/N8 accepted: release notes distinguish header/handshake-read tightening from unchanged h2 stream and TLS handshake-write limitations. -- N9 resolved with direct final macOS/Linux race logs, then recheck modified fixtures after this review's changes. -- N10 accepted: record explicit command and exit status for vet and final checks, not only empty stdout. - -The new common-main.go binding is still unmodified and awaits complete-plan v2 agreement. V1 approval cannot cover it. V2 review must also verify B1's chosen no-new-dependency resolution. diff --git a/docs/investigations/r8/review/implementation-files.json b/docs/investigations/r8/review/implementation-files.json deleted file mode 100644 index 74920bd2f..000000000 --- a/docs/investigations/r8/review/implementation-files.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "internal/deadlineconn/deadlineconn.go": "b9272ef640f1d4403b3d0af6cdbaba9186c51ad9a0226dfe449e8ef738e1ec4b", - "internal/http/listener.go": "49628575367f6ab9b6986caf594726d74d370f7d2ac4eed582903600b6eb3fa2", - "internal/http/server.go": "b7b0355f2781f8c5f7c77bc910cd4180cd3e5f22a87de41bd35ef119d36b4cdf", - "cmd/server-main.go": "04c265de211412ba0297396928096d7f2d971244a957a3126154846035263514", - "internal/deadlineconn/deadlineconn_strict_test.go": "fec37273f338d3cb6f56c71eb6d8d677f8d5e4c2a7c55bc5ab2e64c56ad4a50b", - "internal/http/server_deadline_test.go": "c56460fd8258a7fe8cf25d88fb2030ecfee0e142e0559fad0eb1a05d43d27c0f", - "internal/http/dial_deadline_linux_test.go": "0939d05b72a09760d53fcdf249775989e3f89bca824b9961d0b2a657ebfdf41e" -} diff --git a/docs/investigations/r8/review/implementation-opus.md b/docs/investigations/r8/review/implementation-opus.md deleted file mode 100644 index 3ecd1815a..000000000 --- a/docs/investigations/r8/review/implementation-opus.md +++ /dev/null @@ -1,78 +0,0 @@ -I'll start by reading the review artifacts and the plan documents. - -已完成只读复核。我独立读取了 Go 1.27.1 的 `net/http` 源码、全部 7 个当前实现文件、既有测试与全部 evidence 日志。 - ---- - -# R8 实现评审 — Opus 5.0(只读,实测复核) - -## 结论:**REQUEST_CHANGES** - -生产代码(`deadlineconn.go` / `listener.go` / `server.go` / `server-main.go`)我**未发现任何行为性缺陷**,与已同意的 v1(SHA256 `7cb6…6366`)逐条吻合。唯一阻塞项在**测试侧引入的依赖,会确定性地打红仓库自身的 CI 质量门**(`make check-gen`),一行 `go.mod` 即可修复,不需要改动任何生产语义。 - -> 诚实性声明:① 我**没有执行任何测试、`go vet`、`gofmt`、`go mod tidy` 或哈希计算**(本会话仅 Read/Grep/Glob);所有 SHA256(plan、production diff、implementation-files.json)均沿用你提供的值,**未独立重算**。② 我逐字节比对了 `review/implementation.patch` 与四个生产文件的当前内容,二者一致。③ 下文所有"已核验"均指**我亲自读过的源码**(含 `/opt/homebrew/Cellar/go/1.27.1/libexec/src/net/http/`);所有"日志显示"均指**我读日志得出的转述,非我运行**。④ 未使用子代理。 - ---- - -## 一、对 v1 的实现符合性(逐条,全部达成) - -| v1 条款 | 实现位置 | 结论 | -|---|---|---| -| mutex 保护的 explicit 时间 + `readDeadlineStrict`(默认 false) | `deadlineconn.go:37-38,43` | ✅ | -| `SetReadDeadline`/`SetDeadline` 记录 explicit,保留 zero/abort 与立即转发 | `deadlineconn.go:134,152` + `136-139,149-150` | ✅ | -| `SetReadDeadlineStrict(bool)`,同锁、重置 `readSetAt`、带文档 | `deadlineconn.go:79-87` | ✅ | -| 锁内复检 abort/inf;保留节流与 250ms 松弛;strict 下取 `min(idle, explicit)`;绝对上限不加松弛 | `deadlineconn.go:64-66,69-76`(`deadline = c.readExplicit`,未 `Add`) | ✅ | -| 写侧完全不动 | `deadlineconn.go:89-105,161-168` 与基线一致 | ✅ | -| listener 保留具体类型/读写 idle/Unwrap 兼容,返回前开启 strict | `listener.go:73-76` | ✅ | -| Init **组合而非丢弃** 调用方 ConnState;先切模式再调用方 hook | `server.go:127,138-151`(`connState(conn,state)` 在 switch 之后) | ✅ | -| 一层 `*tls.Conn` 用 `NetConn()` 解包;h2 跳过;其他状态忽略 | `server.go:130-137,139-147` | ✅ | -| 不删 ReadTimeout/WriteTimeout、不改 flag;生产超时处加说明注释 | `cmd/server-main.go:904-905`(其余 906-915 未变) | ✅ | - -验证矩阵 1–7 项的**内容**也已全部落地(含 N2 pipelined、N5 真 h2 断言、N6 >30s 下载三项补充)。 - ---- - -## 二、你点名的 8 项独立核验(结果) - -1. **Go 1.27.1 相位序(实读源码)**:`setState(c.rwc,StateNew,runHooks)` 在 `go c.serve()` **之前**、accept 循环内同步执行(`server.go:3580-3581`、hook 同步调用见 `1881-1883`);首个 header deadline 在 serve loop 之前(`2038-2040`);`StateActive` 在 `readRequest` **返回之后**(`2054-2059`);整请求 deadline **无条件**下发(`1103`,`ReadTimeout<=0` 时下发 zero);`StateIdle`(`2152`) → idle deadline(`2163-2167`) → `Peek(4)`(`2173`) → 第二个 header deadline(`2177-2181`) **全部落在 strict 窗口内**。结论:v1 的相位切分正确。 -2. **mutex/atomic 交互**:`readExplicit`/`readDeadlineStrict`/`readSetAt` 三者只在 `mu` 下读写;`abortReads`/`infReads` 原子量在锁内**复检**(`deadlineconn.go:64`),恰好堵住"`Read` 已过外层门 → 并发 `SetReadDeadline(zero)` → 滚动 deadline 覆盖 net/http 背景读的零值"这一 TOCTOU;锁内无阻塞 I/O(`SetReadDeadline` 只是 runtime 定时器调整),因此 accept 循环里的 hook 不会被卡住。未见数据竞争面。 -3. **>250ms 更新不得续期 header 上限**:`deadlineconn.go:69-76` 每次到期重算都会再次 clamp 回同一个绝对 `readExplicit`,绝不外推。推演 trickle 用例(header 650ms、100ms 一字节):t=0/300/600ms 三次落入更新分支,每次都 clamp 到 t0+650ms,650ms 必断。**不变量成立**。 -4. **nonzero/zero/past**:nonzero → `min()`;zero → `infReads` 在外层与锁内双重短路(`58`/`64`),背景读、hijack、h2 全部维持"永不超时";past → `abortReads` 使 `Read` 直接返回 `context.DeadlineExceeded`(实现 `net.Error.Timeout()`)。strict 下**已过期的未来时间保持过期**(clamp 出一个过去时刻)。 -5. **H1 body 滚动**:`1103` 在 `2058` 之前就把 socket deadline 改成 `t0+ReadTimeout` 并同步刷新 `readExplicit`,二者之间**不存在任何读**;`StateActive` 关 strict 且清 `readSetAt`,首个 body 读立刻续期 → 长上传不被硬顶。`ReadTimeout<=0` 时退化为 `infReads`,与补丁前一致。 -6. **buffered/pipelined 与 keep-alive**:`StateActive` 的触发条件 `c.r.remain != initialReadLimitSize()` 在**成功路径上恒成立**——`readRequest` 在 `1067` 调 `setInfiniteReadLimit()`(`remain=maxInt64`),故纯缓冲的流水线第二个请求同样会关 strict(opus-v1 N2 得到证实)。keep-alive 侧 `2163` 会立刻覆盖掉上一请求遗留的 `readExplicit`,中间窗口无读,无陈旧值风险。 -7. **TLS/h2**:握手读上限 = `min(正 RHT,RT,WT)`(`server.go:969-983,1962-1968`),成功后两侧清零(`1988-1992`)→ 再进入新的 header 上限。协商 h2 时 net/http 走 `setState(...,skipHooks)`(`2002`)且在 `ServeConn` 前把两侧 deadline 清零(`http2.go:100-101`);h2 自身只上报 Active/Idle(`internal/http2/server.go:568-572,796-800`),我们的 h2 分支跳过它们,即便不跳过也被 `infReads` 短路。h2 的 per-stream `ReadTimeout` 是 `time.AfterFunc` 计时器(`1970-1972` → `onReadTimeout` `1836-1841`,返回包装后的 `os.ErrDeadlineExceeded`,满足 `net.Error`),**未新增任何连接级读超时**。 -8. **grid/hijack 与默认调用方**:`hijackLocked` 先 `abortPendingRead` 再 `rwc.SetDeadline(zero)`,之后才 `StateHijacked`(`server.go:322-326,336`);grid 随后 `deadlineconn.Unwrap(conn)` 取回裸 `*net.TCPConn`(`internal/grid/manager.go:193`),strict 根本触达不到 grid。内节点 dialer 走默认 false(`internal/http/dial_linux.go:126-131`),且 `DriveOPTimeout` 在生产仍被注释(`cmd/server-main.go:421-422`),生产内节点连接压根不经过 DeadlineConn。 - ---- - -## 三、阻塞项(1 项,不涉及生产语义) - -**B1 — 新测试的直接依赖会打红 `make check-gen` / CI `quality` job** -- `internal/http/server_deadline_test.go:35` 直接 `import "golang.org/x/net/http2"`,而 `go.mod:297` 为 `golang.org/x/net v0.59.0 // indirect`;全仓库(Grep 确认)**只有这一个文件**直接导入 `golang.org/x/net/*`。 -- `Makefile:49-58` 的 `check-gen` 会执行 `go mod tidy -compat=1.27`,随后 `git diff --name-only -- … go.mod go.sum` 非空即 `exit 1`;`.github/workflows/go.yml:75-76` 把它作为必跑步骤。`go mod tidy` 会因"主模块的测试直接导入"而把该行提升为直接依赖(去掉 `// indirect`)→ **go.mod 产生 diff → CI 红**。 -- 注意这**不是构建失败**:`go.sum:765` 已有完整 `h1:` 哈希,所以本地 `go test` 能过(日志也显示过了),git status 里 go.mod/go.sum 也确实未变——问题只在 tidy 门禁。 -- 两种修法任选其一:(a) 把 `golang.org/x/net v0.59.0` 移入直接 require 块(不改版本、不引新模块,零风险);(b) 去掉该依赖,用标准库 h2 客户端(`stdhttp.Transport` + `TLSClientConfig.NextProtos=[]string{"h2"}`)——被测服务端本就是 net/http 内置 http2,(b) 反而更贴切。 -- 声明:我**无法执行** `go mod tidy` 验证,此结论由 `go.mod:297` + `Makefile:49-58` + `go.yml:75-76` + Grep 结果推得。 - ---- - -## 四、非阻塞项 - -- **N1(最值得补的测试缺口)** `deadlineconn_strict_test.go:40-100` 中每次 `Read` 之前都有 `SetReadDeadline*`,而它们会把 `readSetAt` 清零(`deadlineconn.go:86,151`),因此**只覆盖了"第一次更新"的 clamp,从未覆盖"跨 250ms 的第二/第三次更新仍 clamp"**——而这正是本缺陷的核心不变量。目前它只由真实 socket 的 trickle 用例(`server_deadline_test.go:145-151`)间接覆盖。建议加一个确定性用例:strict + 设上限 → `Read` → `sleep(300ms)` → `Read` → 断言 `raw.read` 仍等于上限。 -- **N2** `TestConcurrentStrictReadDeadline`(`:140-162`)无断言,且 setter 每轮立刻把 deadline 归零,**从未让 `Read` 与"正在生效的 clamp"并发**;作为 race 探针可以,但别把它当作语义回归。 -- **N3(flake 风险)** `TestServerHTTP2Deadlines`(`:376-478`)里 `IdleTimeout=400ms` 会被 net/http 映射为 h2 连接级 idle(`net/http/http2.go:57-61`);两次初始 GET 之后、以及 `<-done` 到最后一次 GET 之间,连接处于 idle,若 runner 抖动 >400ms,连接会被 h2 自身关闭 → `connections.Load()==1` 假失败。同理 `TestServerContinuousUpload` 每块只有 550ms 余量。建议 h2 fixture 单独用更大的 idle。(fixture 清写定时器的修法本身是对的:`onWriteTimeout` 产生的是 `StreamError/INTERNAL_ERROR`(`internal/http2/server.go:1846-1852`),不满足 `net.Error`,正是初版失败的原因。) -- **N4** `listener.go:73` 的局部 `conn` 遮蔽了具名返回值 `conn net.Conn`(且 `err` 也不再使用);合法但可读性差,`dc := …` 更干净。 -- **N5(微小开销)** clamp 命中时 `deadlineconn.go:74` 会把 socket 已持有的同一时刻再写一遍;加上每次相位切换清 `readSetAt`(`server.go:141,145`),每个 H1 请求多出约 2 次 deadline 重算。相对 net/http 自身每请求 3 次 `SetReadDeadline` 可忽略,无需改。 -- **N6** `server.go:131` 每次状态转换都调 `tlsConn.ConnectionState()`(持 `handshakeMutex`)。正确且安全(StateNew 发生在 `go c.serve()` 之前),但如 opus-v1 N3 所述该分支严格冗余(`http2.go:100-101` 已清零,`infReads` 必然短路)。保留可作纵深防御,建议注释点明"仅为防御,非必要条件"。 -- **N7(发布说明,已在 consensus N7 登记)** 两处用户可见收紧:TLS 握手读被 `min(RHT,RT,WT)`(生产 30s)硬顶;请求头被 ReadHeaderTimeout 硬顶(即便字节持续到达)。 -- **N8(已知边界,非本轮)** 生产 `NextProtos{"http/1.1","h2"}`(`cmd/utils.go:970`)下,只宣告 h2 的客户端仍受 h2 **绝对** per-stream `ReadTimeout`(30s)约束;plan 第 24 行/consensus N3 已声明不修,交付说明请重述。 -- **N9(证据对齐,已更正)** 我核对了日志里的 `t.Logf` 归属行(注意 `runContinuousUpload/Download` 调了 `t.Helper()`,报的是**调用点**):`implementation-focused.log:85` 的 `:259`、`default-30s-transfers.log:24` 的 `:274` 与当前文件 **完全一致**;下载区整体偏移 +36/+37 行,与"仅 H2 fixture 被重写(当前 `:376-478`,初版失败点 `:435` → 现 `:463`)+ 长用例补了 `t.Parallel()`"完全吻合。**结论:现存日志并非整体过期,H2 用例之前的部分与当前源码同版;H2 用例及其后的部分尚无对应通过记录**——与你说的"最终 race 复跑进行中"一致,我不将其计为已通过。 -- **N10** `evidence/vet.log` 为 **0 字节**。无输出多半就是干净,但空文件不自证;建议在其中记录命令行与退出码。 - ---- - -## 五、未由我执行的部分(请勿当作我背书的通过) - -`implementation-focused.log` / `darwin-race.log` / `linux-race.log` / `grid.log`(仅 `TestDisconnect`+`TestSingleRoundtrip`)/ `default-30s-transfers.log`(33.03s 明文与 TLS 上传/下载)/ `vet.log` 均为**我阅读的记录**,非我运行。Linux 的 `dial_deadline_linux_test.go`(`//go:build linux`)在本机 Darwin 上不会运行,我只静态核验了它确实断言了默认滚动语义的三种情形(50ms 显式被续期、zero 禁用、1min 显式仍被 idle 截断)。最终全量 race 复跑结果待你回填。 - -修掉 B1(或明确判定 tidy 门禁不适用)后,我这边即可转 APPROVE。 diff --git a/docs/investigations/r8/review/implementation-prompt.md b/docs/investigations/r8/review/implementation-prompt.md deleted file mode 100644 index 867b80196..000000000 --- a/docs/investigations/r8/review/implementation-prompt.md +++ /dev/null @@ -1,5 +0,0 @@ -Read-only implementation review of SILO R8 on baseline 9ebe81c1b3611f9cc73e676b5b741c2be62c467a. You previously agreed plan v1 SHA256 7cb609e37e3199ecd93c992f08d123968ba8082682a108f20490e0644d735366; this is a fresh actual Opus 5.0 review. Read docs/investigations/r8/plan-v1.md, consensus.md, review/opus-v1.md, review/implementation.patch (production diff SHA256 88716d9af433b55527308e09949b0adaab6980aca3bf1ad38b677e9c1c0ce083), and every current file in review/implementation-files.json. These are actual edits, including new untracked tests not represented in git diff. Do not edit or try to write a plan file; deliver findings in the response. Allowed tools are Read/Grep/Glob only. - -Independent checks: exact phase ordering in current Go1.27.1 net/http, mutex/atomic interaction, no header cap renewal across >250ms updates, nonzero/zero/past handling, H1 body rolling, buffered/pipelined and keep-alive transitions, TLS/h2 and grid/hijack default-caller compatibility. Detect concrete bugs and weak tests, avoid hypothetical unrelated expansions. Also see evidence/implementation-focused.log, evidence/default-30s-transfers.log (>33s plaintext and TLS H1 uploads/downloads passed), evidence/grid.log, and evidence/linux-race.log. The first Darwin race run failed only because the H2 fixture's equal read/write timers raced to supply different timeout error types (not a data race); final fixture clears the per-stream write timer to isolate native ReadTimeout and checks healthy multiplexed requests and connection reuse. Final full race reruns are in progress; do not treat pending checks as passed. - -Give Chinese verdict APPROVE or REQUEST_CHANGES, concrete file/line findings separated into blocking and nonblocking, and explicit implementation conformance to accepted v1. Clearly distinguish independently inspected source from tests you did not personally execute. If no blockers, say so. diff --git a/docs/investigations/r8/review/implementation.metadata.json b/docs/investigations/r8/review/implementation.metadata.json deleted file mode 100644 index c8e2b4b88..000000000 --- a/docs/investigations/r8/review/implementation.metadata.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "requested_model": "claude-opus-5", - "effort": "max", - "cli_version": "2.1.270", - "baseline_sha": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "plan_sha256": "7cb609e37e3199ecd93c992f08d123968ba8082682a108f20490e0644d735366", - "production_diff_sha256": "88716d9af433b55527308e09949b0adaab6980aca3bf1ad38b677e9c1c0ce083", - "status": "completed", - "created_at_utc": "2026-09-15T15:54:25.631091+00:00", - "actual_assistant_models": [ - "claude-opus-5" - ], - "result_subtype": "success", - "is_error": false, - "session_id": "541429b8-dc15-4c36-8bf9-f6117e086ae3", - "duration_ms": 382141, - "raw_path": "/Users/vonng/tmp/silo-r8-01a0a5b9/opus-implementation.jsonl", - "raw_sha256": "d5ec088b66ceed9024afc74fc5df40340dc6d208e73d99a8638beb1fde906952", - "review_sha256": "e5861d0da930fe590606722563109d2b87b03b1497abc4a758b7846f446e1eba", - "completed_at_utc": "2026-09-15T16:01:49.467166+00:00", - "scope_note": "Covers v1 connection implementation only; newly discovered config binding requires v2 agreement." -} diff --git a/docs/investigations/r8/review/implementation.patch b/docs/investigations/r8/review/implementation.patch deleted file mode 100644 index ec1c36863..000000000 --- a/docs/investigations/r8/review/implementation.patch +++ /dev/null @@ -1,137 +0,0 @@ -diff --git a/cmd/server-main.go b/cmd/server-main.go -index 48ed0f87d..c8a3a19ca 100644 ---- a/cmd/server-main.go -+++ b/cmd/server-main.go -@@ -901,6 +901,8 @@ func serverMain(ctx *cli.Context) { - close(globalGridStart) - close(globalLockGridStart) - -+ // The HTTP/1 listener preserves absolute header deadlines and renews the -+ // body read/write idle limits, so transfers may outlast IdleTimeout. - httpServer := xhttp.NewServer(getServerListenAddrs()). - UseHandler(setCriticalErrorHandler(corsHandler(handler))). - UseTLSConfig(newTLSConfig(getCert)). -diff --git a/internal/deadlineconn/deadlineconn.go b/internal/deadlineconn/deadlineconn.go -index 95bb43eff..5fa5a1403 100644 ---- a/internal/deadlineconn/deadlineconn.go -+++ b/internal/deadlineconn/deadlineconn.go -@@ -34,6 +34,8 @@ type DeadlineConn struct { - net.Conn - readDeadline time.Duration // sets the read deadline on a connection. - readSetAt time.Time -+ readExplicit time.Time // last deadline requested by the caller. -+ readDeadlineStrict bool // idle renewal must not extend readExplicit. - writeDeadline time.Duration // sets the write deadline on a connection. - writeSetAt time.Time - abortReads, abortWrites atomic.Bool // A deadline was set to indicate caller wanted the conn to time out. -@@ -59,17 +61,31 @@ func (c *DeadlineConn) setReadDeadline() { - - c.mu.Lock() - defer c.mu.Unlock() -- if c.abortReads.Load() { -+ if c.abortReads.Load() || c.infReads.Load() { - return - } - - now := time.Now() - if now.Sub(c.readSetAt) > updateInterval { -- c.Conn.SetReadDeadline(now.Add(c.readDeadline + updateInterval)) -+ deadline := now.Add(c.readDeadline + updateInterval) -+ if c.readDeadlineStrict && !c.readExplicit.IsZero() && c.readExplicit.Before(deadline) { -+ deadline = c.readExplicit -+ } -+ c.Conn.SetReadDeadline(deadline) - c.readSetAt = now - } - } - -+// SetReadDeadlineStrict controls whether idle renewal may extend a deadline set -+// by SetReadDeadline or SetDeadline. The default is false. Explicit zero and -+// past deadlines retain their disable/cancel semantics in either mode. -+func (c *DeadlineConn) SetReadDeadlineStrict(strict bool) { -+ c.mu.Lock() -+ defer c.mu.Unlock() -+ c.readDeadlineStrict = strict -+ c.readSetAt = time.Time{} -+} -+ - func (c *DeadlineConn) setWriteDeadline() { - // Do not set a Write deadline, if upstream wants to cancel all reads. - if c.writeDeadline <= 0 || c.abortWrites.Load() || c.infWrites.Load() { -@@ -115,6 +131,7 @@ func (c *DeadlineConn) SetDeadline(t time.Time) error { - defer c.mu.Unlock() - - c.readSetAt = time.Time{} -+ c.readExplicit = t - c.writeSetAt = time.Time{} - c.abortReads.Store(!t.IsZero() && time.Until(t) < 0) - c.abortWrites.Store(!t.IsZero() && time.Until(t) < 0) -@@ -132,6 +149,7 @@ func (c *DeadlineConn) SetReadDeadline(t time.Time) error { - c.abortReads.Store(!t.IsZero() && time.Until(t) < 0) - c.infReads.Store(t.IsZero()) - c.readSetAt = time.Time{} -+ c.readExplicit = t - return c.Conn.SetReadDeadline(t) - } - -diff --git a/internal/http/listener.go b/internal/http/listener.go -index bc6de3af9..14d34f6ea 100644 ---- a/internal/http/listener.go -+++ b/internal/http/listener.go -@@ -70,7 +70,10 @@ func (listener *httpListener) Accept() (conn net.Conn, err error) { - if result.err != nil { - return nil, result.err - } -- return deadlineconn.New(result.conn).WithReadDeadline(listener.opts.IdleTimeout).WithWriteDeadline(listener.opts.IdleTimeout), result.err -+ conn := deadlineconn.New(result.conn).WithReadDeadline(listener.opts.IdleTimeout).WithWriteDeadline(listener.opts.IdleTimeout) -+ // Server.Init switches to rolling reads only after HTTP/1 headers are read. -+ conn.SetReadDeadlineStrict(true) -+ return conn, nil - case <-listener.ctxDoneCh: - } - return nil, syscall.EINVAL -diff --git a/internal/http/server.go b/internal/http/server.go -index 2934fda6c..d9c19a33f 100644 ---- a/internal/http/server.go -+++ b/internal/http/server.go -@@ -29,6 +29,7 @@ import ( - "time" - - "github.com/dustin/go-humanize" -+ "github.com/minio/minio/internal/deadlineconn" - ) - - var ( -@@ -123,6 +124,32 @@ func (srv *Server) Init(listenCtx context.Context, listenErrCallback func(listen - srv.listener = listener - srv.listenerMutex.Unlock() - -+ connState := srv.ConnState -+ srv.ConnState = func(conn net.Conn, state http.ConnState) { -+ raw := conn -+ if tlsConn, ok := raw.(*tls.Conn); ok { -+ if tlsConn.ConnectionState().NegotiatedProtocol == "h2" { -+ // HTTP/2 owns its stream deadlines; do not change the connection. -+ raw = nil -+ } else { -+ raw = tlsConn.NetConn() -+ } -+ } -+ if dc, ok := raw.(*deadlineconn.DeadlineConn); ok { -+ switch state { -+ case http.StateNew, http.StateIdle: -+ dc.SetReadDeadlineStrict(true) -+ case http.StateActive: -+ // net/http has finished reading the headers, including buffered -+ // requests. Keep ReadTimeout as a rolling idle limit for uploads. -+ dc.SetReadDeadlineStrict(false) -+ } -+ } -+ if connState != nil { -+ connState(conn, state) -+ } -+ } -+ - var l net.Listener = listener - if tlsConfig != nil { - l = tls.NewListener(listener, tlsConfig) diff --git a/docs/investigations/r8/review/opus-v1.md b/docs/investigations/r8/review/opus-v1.md deleted file mode 100644 index e930f60c1..000000000 --- a/docs/investigations/r8/review/opus-v1.md +++ /dev/null @@ -1,70 +0,0 @@ -I'll start by reading the required materials to ground my review. - -Now let me verify the actual Go 1.27.1 `net/http` behavior rather than trusting the plan's summary. - -Verification complete. Writing my review record to the plan file. - -Write 工具在本会话被禁用(确为只读评审),故评审记录直接以回复形式交付。 - ---- - -# R8 独立评审 — Opus 5.0 reviewer - -**结论:CONSENSUS —— 同意 `docs/investigations/r8/plan-v1.md`(SHA256 `7cb609e37e3199ecd93c992f08d123968ba8082682a108f20490e0644d735366`),无遗留阻塞性分歧。** 共 9 条非阻塞意见。 - -> 诚实性声明(先行):① SHA256 采用你给出的值——本会话只读工具集不含哈希命令,我**未独立重算**;② `evidence/baseline.log` 对应的临时测试文件已不在 worktree(`git status` 仅 `docs/investigations/r8/` 未跟踪),我**未重跑**该日志,只是从 Go 源码独立推导出的机制与该日志完全吻合;③ 未使用子代理、未模拟工具、未声称运行过任何测试。 - -## 一、逐条核验(Go 1.27.1 源码,`/opt/homebrew/Cellar/go/1.27.1/libexec/src/net/http/`) - -| 核验项 | 事实 | 位置 | -|---|---|---| -| ConnState 相位序 | `setState(c.rwc, StateNew, runHooks)` 在 `go c.serve()` **之前**,hook 同步执行、传入 `c.rwc` | `server.go:3580`, `1865-1884` | -| 首个 header deadline | 在 serve loop **之前**设置,此时 state 仍为 StateNew | `server.go:2038-2040` | -| StateActive 时机 | `readRequest` **返回之后**才触发;成功路径必然触发(`setInfiniteReadLimit()` 把 `remain` 置 `maxInt64`) | `2054-2059`, `1067` | -| whole-request deadline | Go 1.27 **无条件** `SetReadDeadline(t0+ReadTimeout)`(旧版的 `!hdrDeadline.Equal(...)` 条件已删) | `1103`, `1038-1041` | -| TLS 握手 | 读写 deadline = `now + min(正RHT,RT,WT)` = SILO 30s;成功后两侧清零 | `1962-1968`, `964-984`, `1988-1992` | -| zero / background EOF | 有 body → EOF 回调启动 background read;无 body → 立即启动,且 `SetReadDeadline(zero)` | `2123-2127`, `731-743` | -| abort / finishRequest | `abortPendingRead` 设 `aLongTimeAgo` → 等待 → 清零;由 `finishRequest` 调用 | `785-797`, `1694-1707` | -| keep-alive 第二个 header | StateIdle hook → `SetReadDeadline(now+idle)` → `Peek(4)` → `SetReadDeadline(now+RHT)`,**全部落在 strict 窗口内** | `2152`, `2163-2181` | -| Hijack(grid/ws) | `abortPendingRead` + `rwc.SetDeadline(zero)` + StateHijacked | `318-337` | -| 写侧 | 每请求由 `readRequest` 的 defer 重新武装,请求结束清零 | `1042-1046`, `2145` | -| H2 是否启用 | SILO 设了 `TLSConfig` 且 `NextProtos` 含 `"h2"` → `shouldConfigureHTTP2ForServe()` 为真 → `s.h2` 配置,走内置 http2 | `3469-3489`, `http2.go:82` | -| H2 与 hook | h2 **会**调用用户 hook(带 nil 保护);进入 ServeConn 前把两侧 deadline 清零;除 per-stream 定时器外无任何 conn 级 `SetReadDeadline` | `http2.go:100-101,188-198`; `internal/http2/server.go:571-572, 799-800, 1567, 2100, 1970-1972` | - -SILO 侧:`cmd/server-main.go:907-913`(RT=WT=Idle=30s,RHT=30s)、`internal/http/listener.go:73`、`dial_linux.go:126-132`、`grid/manager.go:193`、`cmd/utils.go:970`。全仓库 `*.go` **无任何 `ConnState` 使用**;现有 `internal/deadlineconn`、`internal/http` 测试均不设显式 deadline,故不受 strict 影响。 - -## 二、相位推导:option 4 为何正确 - -- **strict ON 覆盖**:accept/StateNew → TLS 握手读 → 首个 header(`2038`);以及 `2152` StateIdle 到下一轮 `2058` StateActive 之间的 idle 等待(`2163`)**与第二个 header**(`2177`)。 -- **strict OFF 覆盖**:`2058` 之后到 `2152` 之前,即 body 读 + handler 全程 → 保留 rolling,长上传不被硬顶。 -- **`1103` 与 `2058` 之间**虽仍 strict 且 explicit 已变为 `t0+ReadTimeout`,但该区间**不存在任何读**(只有 `2106` 的 header 检查与 `2118-2127` 的登记)——无副作用。 -- **zero 优先于 strict**(`infReads` 提前 return)→ background read、hijack、h2 全部保持原语义;长 handler(如 `mc admin trace` 这类流式 GET)不会被误杀。 -- 节流与 `readSetAt` 重置的组合可保证:strict 上限一旦写入 socket 就不会被后续 `Read` 重新拉长;模式切换重置 `readSetAt` 使下一次读立即按新模式重算。 - -**对 baseline 的验证**:strict 下 header 上限 = `min(now+2s+250ms, now+100ms)` = `now+100ms` → 400ms 完成的 header 必被拒,与标准 `net/http` 一致。 - -## 三、非阻塞意见(N1–N9) - -1. **N1 措辞订正**:计划 20 行说 header deadline 在 `readRequest` 内设;实际在 `2038`/`2177`。且 Go 1.27 的 `1103` 是**无条件**的——这反而让 option 1 的否决理由更硬:RT=30s 会硬顶「header+body」整请求。 -2. **N2 不变量要写死**:StateActive 的触发依据应记为 `1067` 的 `setInfiniteReadLimit()`,而非「读到字节」。pipelined 请求即使 header 全来自 `bufio` 缓冲、零 socket 读也必然触发。建议补一个 pipelined 用例。 -3. **N3 可简化**:h2 特判可省——`http2.go:100-101` 已清零两侧 deadline,strict 分支恒被 `infReads` 短路。保留亦正确,只多一次 `ConnectionState()` 加锁;若保留,注意 StateNew 时握手尚未发生,不能依赖其返回值。 -4. **N4 可简化**:`Accept` 里开 strict 是冗余的(`3580` 严格 happens-before 任何读)。保留可作纵深防御,但会让「未装 hook 的 httpListener 使用者」隐式获得 strict body 语义(今天不存在,`httpListener` 未导出、仅 `Server.Init` 构造)。两者皆可,请写明这层耦合。 -5. **N5 测试有效性(最重要)**:`cmd/utils.go:970` 顺序为 `{"http/1.1","h2"}`,Go 的 ALPN 以**服务端顺序**优先 → 普通 Go HTTP/2 客户端会落回 http/1.1。H2 smoke 必须用只声明 `NextProtos: []string{"h2"}` 的 `tls.Client`,并断言 `NegotiatedProtocol == "h2"` 与 `resp.Proto == "HTTP/2.0"`,否则是假阳性。 -6. **N6 回归护栏**:写侧 rolling 是大对象 GET 的命门(`1042-1046` 每请求 `now+30s`,全靠每 250ms 续期)。计划正确地不动写侧;建议在 `setWriteDeadline` 旁注明「不可对称 strict」,并在矩阵补一个 >30s 的持续**下载**用例。 -7. **N7 发布说明**:两处用户可见收紧——TLS 握手读侧被 `min(RHT,RT,WT)=30s` 硬顶;请求头被 `ReadHeaderTimeout` 硬顶(即便字节持续到达)。这正是 stock `net/http` 语义,但极慢链路客户端可能受影响,需文档化。 -8. **N8 影响面比计划更小**:`cmd/server-main.go:421-422` 的 `DriveOPTimeout` 仍被 FIXME 注释掉 → `dial_linux.go:126` 分支不成立,**生产内节点连接根本不经过 DeadlineConn**。计划结论正确,建议据实补记。 -9. **N9 已知项**:握手写侧仍会被续期,慢读 ServerHello 的客户端仍能拉长写侧占用。既有缺陷、本轮不修,建议显式登记而非静默。 - -## 四、替代方案对比(是否存在更简单的正确解:无) - -- **全局 clamp**:`1103` 无条件设 `wholeReqDeadline`,RT=30s 直接破功大对象上传。❌ -- **去 ReadTimeout + 把 zero 解释成 rolling**:与 `731-743`(background read)、`326`(hijack)冲突,两处都用 zero 表达「永不超时」。❌ -- **body/ResponseController wrapper**:引入 drain/EOF/chunked 记账 + h2 专门处理,范围远超本缺陷。❌ -- **由 hook 自行推算 cap(`now+RHT` 等)**:等于在 SILO 侧复刻 stdlib 的 deadline 算术,会随 Go 版本漂移(1.27 相对旧版已改两处)。计划「记住 stdlib 实际要求的值」更稳健。❌ -- **按「显式值是否短于 idle 窗口」自动判定**:生产默认 RHT = RT = Idle = 30s 三者相等,无法区分相位,仅在 baseline 的 100ms/2s 配置下看似有效,**不是通用正确解**。❌ - -所需信息(deadline 由哪一层请求 + 当前 HTTP 相位)只在 `DeadlineConn` API 边界与 `ConnState` 两处可得,**option 4 是最小正确形状**。 - -## 五、后续(实现仍需实际执行验证,不得以推断代替) - -同意计划的 7 项矩阵,并请纳入 N2(pipelined)、N5(真·h2 断言)、N6(>30s 持续下载)三项补充;`-race` 下必须覆盖 `Read` / `SetReadDeadline` / `SetReadDeadlineStrict` 三者并发。Linux 部分若只有编译而无运行时验证,请分别如实报告。 diff --git a/docs/investigations/r8/review/opus-v1.metadata.json b/docs/investigations/r8/review/opus-v1.metadata.json deleted file mode 100644 index f0df7f2f7..000000000 --- a/docs/investigations/r8/review/opus-v1.metadata.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "baseline_sha": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "plan": "plan-v1.md", - "plan_sha256": "7cb609e37e3199ecd93c992f08d123968ba8082682a108f20490e0644d735366", - "requested_model": "claude-opus-5", - "effort": "max", - "cli_version": "2.1.270", - "status": "completed", - "created_at_utc": "2026-09-15T15:44:56.500168+00:00", - "actual_assistant_models": [ - "claude-opus-5" - ], - "result_subtype": "success", - "is_error": false, - "session_id": "7e51fd30-3842-47e9-9d19-47604d87b45b", - "duration_ms": 292649, - "raw_path": "/Users/vonng/tmp/silo-r8-01a0a5b9/opus-v1.jsonl", - "stderr_path": "/Users/vonng/tmp/silo-r8-01a0a5b9/opus-v1.stderr.log", - "raw_sha256": "7922230a171b041a3b736fa9b162282ce1d38614b57d0cf0c42f2f5eade566b7", - "review_sha256": "a6e7c616fe3a158f73e90af7414569f699eedc93e259289dcd0b5c94d75f2dbf", - "completed_at_utc": "2026-09-15T15:50:14.799630+00:00", - "verdict": "CONSENSUS", - "blocking_disagreements": 0 -} diff --git a/docs/investigations/r8/review/opus-v2.md b/docs/investigations/r8/review/opus-v2.md deleted file mode 100644 index 253d0c252..000000000 --- a/docs/investigations/r8/review/opus-v2.md +++ /dev/null @@ -1,70 +0,0 @@ -I'll begin by reading the plan and its supporting evidence directly — as an independent reviewer I won't delegate verification. - -Now the actual production code — the core of what I must verify independently. - -Now the Go 1.27.1 source, to verify zero/negative semantics of `ReadHeaderTimeout` myself. - -已完成独立只读复核。我亲自读取了 Go 1.27.1 `net/http` 源码、`minio/cli@v1.24.2` 源码、全部相关生产文件与新测试、以及 r8 目录下的计划/共识/证据文档。 - ---- - -# R8 完整方案 v2 评审 — Opus 5.0(独立、只读) - -## 结论:**CONSENSUS** - -我对 **`docs/investigations/r8/plan-v2.md`(v2,SHA256 `426127ed9fb08aeddf8259ebdc4b1c24ebec8cda751a970ed99338a44b065f4c`,基线 `9ebe81c1b3611f9cc73e676b5b741c2be62c467a`)的完整范围**(v1 连接层设计 + 新增配置绑定)表示同意,**零条阻塞性分歧**。 - -**据此授权在该方案范围内实施所提议的配置绑定**:在 `cmd/common-main.go` 的 `buildServerCtxt` 中于 `IdleTimeout` 旁新增 `ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout")`。实现与测试验收仍是独立环节,不在本次同意范围内;合并、发布、部署同样不在内。 - -**B1 判定:已解决。** 全仓库 `.go` 文件 Grep `golang.org/x/net` **零命中**;`internal/http/server_deadline_test.go:20-35` 只引入标准库与 `internal/deadlineconn`。h2 改由标准库 `stdhttp.Protocols` + `SetHTTP2(true)`(`:409-411`)强制,断言 `NegotiatedProtocol=="h2" && Proto=="HTTP/2.0"`(`:420`)与 `ProtoMajor!=2` 保护(`:383-384`)均保留——即使协议回落也会响亮失败而非静默降级。由于主模块无任何包(含测试)直接导入 `golang.org/x/net/*`,`go mod tidy` 没有理由把 `go.mod:297` 的 `// indirect` 提升为直接依赖,`Makefile:49-58` 的 `check-gen` 门禁不会被打红,也不会因此丢失该 require(它仍被其他模块间接需要)。N1 亦已落实:`deadlineconn_strict_test.go:165-178` 跨 3 个真实 300ms 周期、全程不重设上限地断言 clamp 不外推。N3 仅对 h2 夹具放宽 keep-alive idle(`server_deadline_test.go:406` `srv.IdleTimeout = 3 * time.Second`),未触及其他用例。 - -> **诚实性声明**:① 本会话仅有 Read/Grep/Glob,**无 shell**。因此我**没有重算 v2 的 SHA256**,也没有运行任何 `go test`/`go vet`/`go mod tidy`/`git`;我核验的是该路径下实际文件的**内容**(116 行,与下文逐条引用一致)。② 所有"日志显示"均为我**阅读记录**的转述,非我运行。③ 未使用子代理。④ 未验证当前 HEAD 是否等于所述基线。 - ---- - -## 一、最小充分性:我独立确认的闭链(C1–C8) - -| # | 结论 | 我核验的依据 | -|---|---|---| -| C1 | 一行赋值**充分** | 全库仅 `cmd/server-main.go:912` 读 `globalServerCtxt.ReadHeaderTimeout`;唯一 HTTP server 构造点也仅 `server-main.go:906`(`xhttp.NewServer` 全库两处命中,另一处在 patch 文档里) | -| C2 | 链路闭合 | `buildServerCtxt`(`common-main.go:370`)是 serverCtxt 唯一填充函数;`serverMain` 在 `server-main.go:799` 用它填 `globalServerCtxt` | -| C3 | **默认行为零变化** | `DefaultIdleTimeout == DefaultReadHeaderTimeout == 30s`(`internal/http/server.go:44-48`)→ `readHeaderTimeout()`(Go `server.go:3752-3757`)绑定前后都返回 30s;`tlsHandshakeTimeout()`(`:969-983`)也都是 30s。plan 第 17 行"两值相等时默认值恰好看起来正常"我独立证实 | -| C4 | YAML 不可能覆盖 | `ServerConfigCommon`/`Opts`(`internal/config/server.go:20-45`)**根本没有超时字段**;`configCommonToSrvCtx`(`server-main.go:274-302`)只处理 RootUser/Pwd/Addr/ConsoleAddr/CertsDir/FTP/SFTP。即便 merge 发生在赋值之后(`common-main.go:450-454`)也无字段可覆盖 | -| C5 | 优先级/错误面**完全不变** | `minio/cli@v1.24.2/flag.go:569-594`:env 作为 flag 默认值、命令行再覆盖 → flag > env > 静态默认;"存在但为空"的 env 在 `time.ParseDuration("")` 处报错,**发生在 flag 解析阶段,与本绑定无关、绑定前已如此**(`config-baseline-env-fixture-failure.log` 正是该现象)。新行不引入任何新错误路径 | -| C6 | h2 不受影响 | `net/http/http2.go:199-211` 只把 `ReadTimeout`/`WriteTimeout`/`IdleTimeout` 交给 h2,`ReadHeaderTimeout` **完全不进入** h2 配置。plan 第 34/71 行正确 | -| C7 | 共享调用方不受影响 | `deadlineconn.New` 仅 `listener.go:73` 与 `dial_linux.go:130`(后者不开 strict);`SetReadDeadlineStrict` 生产调用仅 `listener.go:75`、`server.go:141/145` | -| C8 | v1 在 v2 中**逐字保留**,实现与之一致 | plan-v1 `13-24/26-31/35-55/57-66/70-76/78-80` ↔ plan-v2 `23-34/36-41/52-72/74-83/94-100/102-104`;当前生产文件 `deadlineconn.go:37-38,56-87,129-154`、`listener.go:73-76`、`server.go:127-151`、`server-main.go:904-915` 与之吻合。v2 没有夹带修改已同意的 v1 语义 | - -关于**证据可信度**:我不采信"已执行"的口头声明,但 `config_baseline_test.go.txt:26,36` 的设计本身具备自证力——同一夹具用 `MINIO_IDLE_TIMEOUT=2s` 并断言 `IdleTimeout==2s` 在全部 7 个子用例成立,这是一个**对照组**,排除了"夹具坏了"的解释;而 `ReadHeaderTimeout` 恒为 0。再叠加我上面对 `flag.go:569-594` 的独立源码核验,`config-baseline.log` 的每一行(30s/100ms/170ms/80ms/-1s)都可从源码推导出来。 - -**阻塞性发现:0 条。** - ---- - -## 二、非阻塞发现(N1–N9) - -1. **N1(新耦合,建议写入交付说明)** 绑定后 RHT 首次参与 TLS 握手窗口:`tlsHandshakeTimeout()`(Go `server.go:964-983`)取 RHT/RT/WT 中**正值的最小者**,并在 `:1962-1968` 同时下发读写 deadline。绑定前 RHT 恒为 0 → 窗口 = idle;绑定后 `--read-header-timeout=1s` 会把 TLS 握手(含 h2 的握手阶段)一起压到 1s。默认下无变化(30s)。 - -2. **N2(唯一"变松"的组合,最值得单列)** 相对**当前已实现的 v1**:用户只设 `--idle-timeout=2s`、不设 RHT 时,请求头上限从 2s 变为默认 30s。相对**真实基线 v0**(滚动续期、滴入可无限延长)仍是收紧。这是两个独立旋钮的正确语义,但它是全部组合中唯一"看起来放松"的一种,交付说明应明确点名,避免被误读为回归。 - -3. **N3(负值语义在明文/TLS 下不对称)** `--read-header-timeout=-1s` → `readHeaderTimeout()` 返回 -1s(`:3752-3757`),首请求处 `server.go:2038` 的 `d>0` 不成立 → **不下发任何 deadline**:明文连接因 `readExplicit` 为零值而在 `deadlineconn.go:71` 短路 clamp,回落到滚动 idle;TLS 连接则已在 `:1990-1991` 被清零 → `infReads=true` → 首个请求头**真正无上限**。keep-alive 第二个请求处 `:2179-2180` 显式下发零值,两者统一为无上限。这是 Go 文档化的 "negative = no timeout"(`:3072-3078`)且需用户显式选择,不要求改设计;建议测试断言并在说明中写明这一差异。 - -4. **N4(正面变化,但仍是可见变化)** `--idle-timeout=0`/负值时:绑定前 RT≤0 且 RHT=0 → `readHeaderTimeout()` 返回 0 → **完全没有请求头上限**,`tlsHandshakeTimeout()` 也为 0;绑定后默认 30s 生效。这是修复带来的安全性改善,建议同样纳入说明并补一条用例。 - -5. **N5(验证矩阵缺口)** v2 矩阵未提及 `buildscripts/test-timeout.sh`(`Makefile:177-179`),而它是仓库现存唯一端到端超时测试且直接使用 `--read-header-timeout 5s --idle-timeout 5s`。我推演结论不变:两值相等 → 绑定前后 `readHeaderTimeout()` 都是 5s,三个用例(20s 慢头 / 40s 慢 body / 1s+1s 正常)判定与 `:69` 的 `<= 11s` 时限均满足。建议验收时实跑并记录。顺带值得写进报告:该脚本用的是"一次长睡眠"而非"持续滴入"(`:44-61`),这正是 R8 缺陷长期未被它捕获的原因。 - -6. **N6(强烈建议)** 把配置用例固化为仓库内**常驻**回归测试,而非仅留 `evidence/*.txt`。缺陷本质是"结构体少复制一行",只有常驻断言能防止再次静默回归;夹具可直接落地(`cmd/testdata/config/1.yaml` 存在)。注意 `zero-fallback` 子用例期望值为 0,**绑定前后都通过、不具判别力**,应保留但标注。 - -7. **N7(备案,无需改动)** 第二调用方 `cmd/fmt-gen.go:80` 也执行 `buildServerCtxt`,而 `fmtGenFlags`(`:30-45`)未注册该 flag。我核验 `minio/cli@v1.24.2/flag_generated.go:141-151`:`lookupDuration` 对未注册 flag 返回 0 且不 panic;现存 `ctx.Duration("idle-timeout")` 已在该路径长期运行,证明模式安全,且 fmt-gen 不启动 HTTP server。 - -8. **N8(勿顺手清理)** `common-main.go:444` 与 `:448` 重复赋值 `ctxt.UserTimeout`,属既有无害冗余。新行紧邻该处,**请不要在本次改动中一并清理**,以保持 diff 最小。 - -9. **N9(证据闭环)** `vet.log` 仍为 0 字节(v1 评审 N10 已接受尚未回填);v2 两条新链建议统一记录命令行、退出码与二进制 SHA256。`runtime-v1.json` 只是**修复前**证据(`expected_rejection=false`,两例均 `HTTP/1.1 200 OK`),必须补一份 `expected_rejection=true` 的同探针输出才算闭环。我另行核验了探针判据 `rejected = not data`(`runtime_probe.py:59`)**正确**:读头超时后 Go 走 `isCommonNetReadError`(`server.go:1915-1926`,`net.Error.Timeout()` 为真)→ `:2090-2091` `return // don't reply`,确实不写任何响应字节,不会出现 408 误判。 - ---- - -## 三、边界(不由本次修复覆盖,请在交付说明中重述) - -- HTTP/2 既有的**绝对** per-stream `ReadTimeout` 不在 R8 范围(`http2.go:203` 直接透传 `ReadTimeout`)。 -- TLS 握手**写**侧仍为滚动截止,属既有边界。 -- 我未执行任何测试;`darwin-race.log` / `linux-race.log` / `grid.log` / `default-30s-transfers.log` 等均为我阅读的记录,不构成我的背书。修改过的 h2 与 strict 夹具需在本轮变更后重跑并回填。 diff --git a/docs/investigations/r8/review/opus-v2.metadata.json b/docs/investigations/r8/review/opus-v2.metadata.json deleted file mode 100644 index 00151d11e..000000000 --- a/docs/investigations/r8/review/opus-v2.metadata.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "baseline_sha": "9ebe81c1b3611f9cc73e676b5b741c2be62c467a", - "plan": "plan-v2.md", - "plan_sha256": "426127ed9fb08aeddf8259ebdc4b1c24ebec8cda751a970ed99338a44b065f4c", - "requested_model": "claude-opus-5", - "effort": "max", - "cli_version": "2.1.270", - "status": "completed", - "created_at_utc": "2026-09-15T15:59:43.188547+00:00", - "actual_assistant_models": [ - "claude-opus-5" - ], - "result_subtype": "success", - "is_error": false, - "session_id": "a1e9e8a2-3417-440b-93b6-c749cbca204f", - "duration_ms": 211549, - "raw_path": "/Users/vonng/tmp/silo-r8-01a0a5b9/opus-v2.jsonl", - "stderr_path": "/Users/vonng/tmp/silo-r8-01a0a5b9/opus-v2.stderr.log", - "raw_sha256": "28a1266b05b8813295168530d9b9dcc792a379a798e74419ee1f06aad364defd", - "review_sha256": "da31df576da632377c963da2ad32e96a9bf4d5f747fb8e2493573b32a899a4ed", - "completed_at_utc": "2026-09-15T16:07:10.728735+00:00", - "verdict": "CONSENSUS", - "blocking_disagreements": 0 -} diff --git a/docs/investigations/r8/review/prompt-v1.md b/docs/investigations/r8/review/prompt-v1.md deleted file mode 100644 index d84e40a74..000000000 --- a/docs/investigations/r8/review/prompt-v1.md +++ /dev/null @@ -1,3 +0,0 @@ -You are the independent actual Opus 5.0 reviewer for SILO R8. Read-only review; no code changes. Read AGENTS.md, docs/investigations/r8/plan-v1.md (SHA256 7cb609e37e3199ecd93c992f08d123968ba8082682a108f20490e0644d735366), docs/investigations/r8/evidence/baseline.log, internal/deadlineconn/deadlineconn.go, internal/http/listener.go, internal/http/server.go, cmd/server-main.go (timeout configuration), internal/http/dial_linux.go, internal/grid/manager.go (unwrap), cmd/utils.go (TLS NextProtos). Inspect actual Go1.27.1 net/http code as needed using allowed Read/Grep tools; baseline is 9ebe81c1b3611f9cc73e676b5b741c2be62c467a. No production patch exists yet. - -The user requires real Opus discussion and explicit same-plan agreement before implementation. Independently challenge correctness and minimal compatibility. Verify exact ConnState phase ordering, TLS handshake, zero deadline/background EOF behavior, keep-alive second header, long uploads, strict mode concurrency, H2 preservation and Linux internode/grid callers. State blocking vs nonblocking findings with concrete source reasoning; compare alternatives if there is a simpler correct approach. Respond in Chinese (technical identifiers preserved), with verdict CONSENSUS or REVISE. CONSENSUS must explicitly name plan v1 and SHA256 and mean there are no remaining blocking disagreements; test implementation still follows. If any blocker exists do not give consensus. Do not simulate tools or pretend tests were run. diff --git a/docs/investigations/r8/review/prompt-v2.md b/docs/investigations/r8/review/prompt-v2.md deleted file mode 100644 index cd8f8c5cf..000000000 --- a/docs/investigations/r8/review/prompt-v2.md +++ /dev/null @@ -1,11 +0,0 @@ -You are the actual independent Opus 5.0 reviewer. Read-only; do not attempt to write any file. The user requires explicit same-version agreement for the COMPLETE R8 scope before the new configuration change is implemented. - -Review plan v2 at docs/investigations/r8/plan-v2.md, SHA256 426127ed9fb08aeddf8259ebdc4b1c24ebec8cda751a970ed99338a44b065f4c, on baseline 9ebe81c1b3611f9cc73e676b5b741c2be62c467a. V1 connection repair is already implemented after actual v1 CONSENSUS; see consensus.md and review/opus-v1.md. V1 does not cover the newly found CLI binding omission. The only new proposed production edit is ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout") beside IdleTimeout in cmd/common-main.go. It has NOT been applied. - -Evidence: evidence/config-baseline.log and config_baseline_test.go.txt exercise the REAL cli.App with serverCmd.Flags and buildServerCtxt. CLI parses correct default/flag/env/precedence/YAML/negative durations, but context is 0. Explicit zero remains 0. Also read evidence/runtime-v1.json and runtime_probe.py: a compiled v1 SILO binary was launched on disposable localhost-only storage; both flag and env set 100ms, idle 2s, but a 400ms health request header still returns HTTP 200 because missing binding leaves ReadHeaderTimeout=0 and Go falls back to idle. These are real tests by Codex, not claims that you ran them. - -Read actual cmd/common-main.go buildServerCtxt; cmd/server-main.go ServerFlags, configCommonToSrvCtx, server setup; current DeadlineConn/listener/server files and new tests; review/implementation-opus.md if it now exists (v1 implementation review only); v2 validation matrix and compatibility notes. Independently confirm minimal sufficiency and side effects of binding defaults and custom values, zero/negative semantics, env precedence, YAML retention, header vs idle behavior, keepalive, TLS, h2, body/download renewal and shared callers. Do not trust a patch/plan claim as execution. Inspect Go1.27.1 source as needed. - -Return Chinese verdict CONSENSUS or REVISE for COMPLETE plan v2, explicitly naming v2 and its SHA256. If consensus, say there are zero blocking disagreements and authorize the proposed configuration binding within that plan, with implementation/testing still separate. List numbered blocking/nonblocking findings with concrete evidence. Any substantive required design change must use REVISE. No rate-limit or missing reply counts as agreement. - -Additional completed v1 implementation review: review/implementation-opus.md returned REQUEST_CHANGES for one TEST dependency issue (new direct x/net/http2 import while go.mod marked module indirect), explicitly no production behavior defect. Read review/implementation-dispositions.md. The test now uses the standard Go HTTP/2-only Protocols setting, so no external x/net import or go.mod change is needed; h2 assertions remain. Also added repeated-clamp unit coverage and widened only h2 keep-alive idle. Independently decide whether B1 is resolved and whether COMPLETE v2 has any remaining blockers. diff --git a/docs/investigations/r8/review/v2-review-files.json b/docs/investigations/r8/review/v2-review-files.json deleted file mode 100644 index 3b879343c..000000000 --- a/docs/investigations/r8/review/v2-review-files.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "internal/deadlineconn/deadlineconn.go": "b9272ef640f1d4403b3d0af6cdbaba9186c51ad9a0226dfe449e8ef738e1ec4b", - "internal/http/listener.go": "49628575367f6ab9b6986caf594726d74d370f7d2ac4eed582903600b6eb3fa2", - "internal/http/server.go": "b7b0355f2781f8c5f7c77bc910cd4180cd3e5f22a87de41bd35ef119d36b4cdf", - "cmd/server-main.go": "04c265de211412ba0297396928096d7f2d971244a957a3126154846035263514", - "cmd/common-main.go": "c06df6c3051ceb264f7ddbdddea33ebe38a59e0c113b5c4c44906c1c2e08a01b", - "internal/deadlineconn/deadlineconn_strict_test.go": "f405690c9ff044595f48323d68f4a9b33ce695b3ad6820f54f17151067bfae5e", - "internal/http/server_deadline_test.go": "36103c155795ab24a7b8f3101f2091c793f9cbe1f2459b5bea51a8a2dc192a52", - "internal/http/dial_deadline_linux_test.go": "0939d05b72a09760d53fcdf249775989e3f89bca824b9961d0b2a657ebfdf41e" -} diff --git a/docs/investigations/release-readiness-20260911.md b/docs/investigations/release-readiness-20260911.md deleted file mode 100644 index 0ae044f19..000000000 --- a/docs/investigations/release-readiness-20260911.md +++ /dev/null @@ -1,149 +0,0 @@ -# Remaining release correctness work, 2026-09-11 - -> This is a dated investigation, with the source and runtime boundaries recorded -> below. It does not establish the current dependency pins or a later release. -> See [the current changelog](../../CHANGELOG.md) and -> [component matrix](https://silo.pgsty.com/compatibility/versions/). - - -This records the three work items agreed after the branch/PR consolidation: -OIDC #154, Linux restart/readback #116, and the related multi-pool defects -#133/#144. The maintained target is SILO with the PGSTY Console, mcli and -silo-pkg dependencies in the repository's current `go.mod`. - -## Multi-pool writes and conditional deletion (#133, #144) - -The pools layer now holds its object write lock across PUT and multipart -completion, as it already does for metadata updates and DELETE. Multipart -completion acquires the object lock before the upload lock. Queued healing and -drive healing use this namespace too; healing under a caller-owned lock retains -that lock's cancellation context. The destination-pool allocation policy is -unchanged. - -Trusted replica writes resolve the addressed version in every pool, including -draining and rebalancing pools. Retention, legal hold and tags retain their -independent ordering timestamps. A timestamp-only removal survives a stale -retransmit. Completion resolves the version persisted in the upload, including -the null version, rather than taking a later latest version's metadata. - -Successful replica replacement retires competing copies of that exact version, -so an equal-ModTime copy in an earlier pool cannot shadow the reconciled result. -Metadata updates evaluate their callback once against the merged version and -update all its copies. Replica metadata COPY rechecks ordering under the lock. -Cleanup failures propagate; a replacement may already have committed when -cleanup fails, and a retry can finish cleanup. Data movement keeps ownership of -its source cleanup. - -Retiring copies preserves any remote-tier reference still held by a surviving -copy, including temporarily restored objects. Only the last copy of that tier -reference schedules remote contents for garbage collection. This also protects -the authoritative copy if the final conditional deletion fails; unrelated tier -contents remain eligible for cleanup. - -Conditional DELETE evaluates its precondition against the logical latest or -explicitly addressed version. It checks all pools before mutation, removes -secondary copies before the authoritative one, and returns cleanup errors. -Versioned DELETE without a version ID creates a delete marker and preserves -version history. Retention and replication callbacks evaluate the reconciled -logical version. The existing all-pool delete helper also propagates errors -from non-first pools. - -The deterministic two-pool, 32-drive fixtures cover version selection, -duplicate removal, delete failure propagation, PUT/DELETE and completion/DELETE -interleavings, independent lock winners, draining/rebalancing owners, null -versions, metadata COPY, metadata/healing serialization and cleanup retry. -Tiered-copy tests inspect the persisted garbage-collection markers after -metadata COPY, restored COPY, successful deletion and failed primary deletion, -with distinct remote references as cleanup controls. -The original branch reproduced the wrong-version lookup, surviving duplicate, -suppressed delete error, PUT/DELETE race, and PUT/completion lock-state failures -before the fixes were applied. - -Before the final tier-reference guard, local validation passed the full `cmd` -suite (255.720 s), all `internal` tests, `go vet ./...`, generated-file checks -and the branding/entrypoint checks. -Focused race checks cover the pooled interleavings, SSE-C lock regressions, -conditional deletion, access-tier movement and TLS defaults. The two-pool and -related replica/delete/movement tests also passed as a Linux/arm64 test binary -in an isolated container with an 8 GiB `/tmp` tmpfs. Its initial 1 GiB tmpfs -was insufficient for the existing single-drive test fixtures' free-space guard. -The final tier-reference guard passed all six new cases in that Linux fixture; -the retained restart binary below predates this guard and has no remote tier -configured. - -## Linux restart/readback (#116) - -The [runner](issue-116/run-linux.py) creates four Linux/arm64 server containers -on one Docker Desktop Linux VM, with separate network identities and one drive -per node (EC 2+2). A holder container keeps four 256 MiB Linux tmpfs named -volumes mounted across full server stops. Docker's ordinary filesystem had -only 3.8 GiB free out of 2 TiB and correctly hit the server's free-space limit; -the test uses the separate tmpfs filesystems without changing that threshold. -This is the four-containers-on-one-Linux-host option explicitly accepted in -the [#116 V1 plan](https://github.com/pgsty/silo/issues/116). - -This covers process/container restart and TCP peer reconnection on one Linux -host. It does not establish independent-host, host-reboot or physical-media -durability. Every resource created by the runner is removed in its cleanup. -The [retained summary](issue-116/evidence-20260911.json) records image identities, -per-coordinator observations and readback counts. - -| Binary | Full restart to all admin views online | Admin gate to complete 4 PUT / 16 GET round | Timed readbacks | Final readback | -|---|---:|---:|---:|---:| -| 0806 release | 2.084 s | 14.449 s | 660 / 660 | 240 / 240 | -| 0903 release | 2.284 s | 0.191 s | 96 / 96 | 52 / 52 | -| Current fix candidate | 2.461 s | 14.489 s | 1356 / 1356 | 904 / 904 | - -Each canary has one hard 60-second deadline covering setup, requests, response -body reads and sleeps, with SDK retries disabled. Every acknowledged PUT uses -a unique versioned key and immediately records its VersionId, size and SHA-256. -The timed checks reread these same objects through all four coordinators at -15, 30 and 60 seconds **after the data canary succeeds**. They do not replace -early acknowledgements with later writes. The final check also includes writes -made during the one-node outage and subsequent rejoin canary. - -All three runs passed the bounded canary, scheduled readbacks, one-node-outage -read/write and rejoin readback. The candidate also showed a rejoin window: -admin online at 2.490 s, complete data canary 13.977 s later. These are individual -observations, not a latency guarantee or a comparison proving one version -faster. Admin/health readiness still must be followed by a data-path check. - -To repeat with cached images and a native mcli executable: - -```sh -uv run --with boto3==1.43.92 docs/investigations/issue-116/run-linux.py \ - 0806 0903 --output /tmp/silo-linux-acceptance --mcli /path/to/mcli - -CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -tags kqueue -o /tmp/silo-linux . -uv run --with boto3==1.43.92 docs/investigations/issue-116/run-linux.py \ - current --output /tmp/silo-linux-candidate --mcli /path/to/mcli \ - --current-binary /tmp/silo-linux --source-revision "$(git rev-parse HEAD)" -``` - -The runner uses `--pull=never`; preload the two release images, `alpine:3.23`, -and the Linux base image selected with `--current-image`. Match the candidate -binary architecture to that image. Detailed local evidence, including the -acknowledgement ledger and full logs, remains in the requested output directory. - -## OIDC (#154) - -The TLS implementation fix was already merged in `48e1846525cc`: transports -honor Go's key-exchange defaults, so `GODEBUG=tlsmlkem=0` can opt out of ML-KEM -for an ingress that rejects it. It does not disable certificate validation or -force an automatic protocol downgrade. See the existing -[Go 1.27 investigation](go127-stack.md) and [issue investigation](issue-154.md). - -A Linux build of main `b32f2d9dd01a` passed fresh isolated fixture checks: - -- ML-KEM-intolerant IdP with `tlsmlkem=0`: discovery, IAM, Console login (204), - authenticated bucket listing (200). -- Normal TLS 1.3 IdP without that override: the same complete login chain. -- Add OIDC through the real administration API with the compatibility setting. -- Invalid JWT signature and audience: no session cookie and bucket access 403. -- Untrusted CA: discovery fails and cluster readiness remains 503. - -The affected customer's discovery URL and ingress configuration are still -unavailable. This establishes the supported local fix and its negative -controls, not the root cause or recovery of that hidden deployment. #154 stays -open for an affected-environment retest; no release date or published artifact -is implied by these checks. diff --git a/docs/security/README.md b/docs/security/README.md deleted file mode 100644 index 6984c418e..000000000 --- a/docs/security/README.md +++ /dev/null @@ -1,186 +0,0 @@ -# Silo Security Overview - -For fork-specific security advisories and upgrade notes in `pgsty/minio`, see [advisories.md](advisories.md). - -For which peers may tell the server where a request came from — and therefore whether `aws:SourceIp` conditions and audit client addresses can be relied on — see [Client source address trust](source-address-trust.md). - -## Server-Side Encryption - -Silo supports two different types of server-side encryption ([SSE](#sse)): - -- **SSE-C**: The Silo server en/decrypts an object with a secret key provided by the S3 client as part of the HTTP request headers. Therefore, [SSE-C](#ssec) requires TLS/HTTPS. -- **SSE-S3**: The Silo server en/decrypts an object with a secret key managed by a KMS. Therefore, Silo requires a valid KMS configuration for [SSE-S3](#sses3). - -### Server-Side Encryption - Preliminaries - -#### Secret Keys - -The Silo server uses a unique, randomly generated secret key per object also known as, Object Encryption Key ([OEK](#oek)). Neither the client-provided SSE-C key nor the KMS-managed key is directly used to en/decrypt an object. Instead, the OEK is stored as part of the object metadata next to the object in an encrypted form. To en/decrypt the OEK another secret key is needed also known as, Key Encryption Key ([KEK](#kek)). - -The Silo server runs a key-derivation algorithm to generate the KEK using a pseudo-random function ([PRF](#prf)): -`KEK := PRF(EK, IV, context_values)` where: - -- [EK](#ek): is the external key. In case of SSE-C this is the client-provided key. In case of SSE-S3 this is secret key generated by the KMS. For further details see [SSE-C](#Server-Side-Encryption-with-client-provided-Keys) or [SSE-S3](#Server-Side-Encryption-with-a-KMS). -- [IV](#iv): is a randomly generated initialization vector. It is public and part of the object metadata. -- `context_values`: are values like the bucket and object name and other information which should be cryptographically bound to the KEK. - -To summarize for any encrypted object there exists (at least) three different keys: - -- [OEK](#oek): A secret and unique key used to encrypted the object, stored in an encrypted form as part of the object metadata and only loaded to RAM in plaintext during en/decrypting the object. -- [KEK](#kek): A secret and unique key used to en/decrypt the OEK and never stored anywhere. It is(re-)generated whenever en/decrypting an object using an external secret key and public parameters. -- [EK](#ek): An external secret key - either the SSE-C client-provided key or a secret key generated by the KMS. - -#### Content Encryption - -The Silo server uses an authenticated encryption scheme ([AEAD](#aead)) to en/decrypt and authenticate the object content. The AEAD is combined with some state to build a *Secure Channel*. A *Secure Channel* is a cryptographic construction that ensures confidentiality and integrity of the processed data. In particular the *Secure Channel* splits the plaintext content into fixed size chunks and en/decrypts each chunk separately using a unique key-nonce combination. - -##### Figure 1 - Secure Channel construction - -``` -plaintext := chunk_0 || chunk_1 || chunk_2 || ... - | | | - | | | - AEAD <- key, nonce + 0 AEAD <- key, nonce + 1 AEAD <- key, nonce + 2 ... - | | | - | | | -ciphertext := sealed_chunk_0 || sealed_chunk_1 || sealed_chunk_2 || ... -``` - -In case of a S3 multi-part operation each part is en/decrypted with the scheme shown in Figure 1. However, for each part a unique secret key is derived from the OEK and the part number using a PRF. So in case of multi-part not the OEK but the output of `PRF(OEK, part_id)` is used as secret key. - -#### Cryptographic Primitives - -The SSE schemes described in [Secret Keys](#Secret-Keys) and [Content Encryption](#Content-Encryption) are generic over the cryptographic primitives. However, the Silo server uses the following cryptographic primitive implementations: - -- [PRF](#prf): HMAC-SHA-256 -- [AEAD](#aead): AES-256-GCM if the CPU supports AES-NI, ChaCha20-Poly1305 otherwise. More specifically AES-256-GCM is only selected for X86-64 CPUs with AES-NI extension. - -Further any secret key (apart from the KMS-generated ones) is 256 bits long. The KMS-generated keys may be 256 bits but this depends on the KMS capabilities and configuration. - -The *Secure Channel* splits the object content into chunks of a fixed size of `65536` bytes. The last chunk may be smaller to avoid adding additional overhead and is treated specially to prevent truncation attacks. The nonce value is 96 bits long and generated randomly per object / multi-part part. The *Secure Channel* supports plaintexts up to `65536 * 2^32 = 256 TiB`. - -#### Randomness - -The Silo server generates unique keys and other cryptographic values using a cryptographically secure pseudo-random number generator ([CSPRNG](#csprng)). However, in the context of SSE, the Silo server does not require that the CSPRNG generates values that are indistinguishable from truly random bit strings. Instead, it is sufficient if the generated values are unique - which is a weaker requirement. Nevertheless other parts - for example the TLS-stack - may require that CSPRNG-generated values are indistinguishable from truly random bit strings. - -### Server-Side Encryption with client-provided Keys - -SSE-C allows an S3 client to en/decrypt an object at the Silo server. Therefore the S3 client sends a secret key as part of the HTTP request. This secret key is **never** stored by the Silo server and only resides in RAM during the en/decryption process. - -Silo does not assume or require that the client-provided key is unique. It may be used for multiple objects or buckets. Especially a single client-provided key may be used for all objects - even though all objects must be treated as compromised if that key is ever compromised. - -#### Key rotation - -S3 clients can change the client-provided key of an existing object. Therefore an S3 client must perform a S3 COPY operation where the copy source and destination are equal. Further the COPY request headers must contain the current and the new client key: - -- `X-Amz-Server-Side-Encryption-Customer-Key`: Base64 encoded new key. -- `X-Amz-Copy-Source-Server-Side-Encryption-Customer-Key`: Base64 encoded current key. - -Such a special COPY request is also known as S3 SSE-C key rotation. - -### Server-Side Encryption with a KMS - -SSE-S3 allows an S3 client to en/decrypt an object at the Silo server using a KMS. The Silo -server only assumes that the KMS provides two services: - -- `GenerateKey`: Takes a key ID and generates a new data key from a master key referenced by the key ID. It returns the new data key in two different forms: The plain data key and the data key encrypted using the master key. - -- `DecryptKey`: Takes a key ID and an encrypted data key and returns the plain data key - the decryption of the encrypted data key using the master key referenced by the key ID - on success or an error otherwise. - -More details about supported KMS implementations and configuration can be found at the [KMS guide](https://github.com/pgsty/minio/blob/master/docs/kms/README.md). - -The Silo server requests a new data key from the KMS for each uploaded object and uses that data key as EK. Additionally it stores the encrypted form of the data key and the master key ID as part of the object metadata. The plain data only resides in RAM during the en/decryption process. The Silo server does not store any SSE-related key at the KMS. Instead the KMS is treated as trusted component that performs key sealing/unsealing operations to build a key hierarchy: - -#### Figure 2 - KMS key hierarchy - -``` - CMK (master key) - | - +-----------------------------------+-----------------------------------+ - | | | - +-------+----------------+ +-------+----------------+ ... - | EK_1 | EK_1_encrypted | | EK_2 | EK_2_encrypted | - +---+----------+---------+ +---+----------+---------+ - | | | | - | | | | - +---+---+ | +---+---+ | - | KEK_1 | | | KEK_2 | | - +---+---+ | +---+---+ | - | | | | - | | | | - +---+---+ | +---+---+ | - | OEK_1 | | | OEK_2 | | - +---+---+ | +---+---+ | - | | - | | - | | - +---------+---------+ +---------+---------+ - | object_metadata_1 | | object_metadata_2 | - +-------------------+ +-------------------+ -``` - -#### Key rotation - Basic Operation - -The Silo server supports key rotation for SSE-S3 encrypted objects. The Silo server decrypts the OEK using the current encrypted data key and the master key ID of the object metadata. If this succeeds, the server requests a new data key from the KMS using the master key ID of the **current Silo KMS configuration** and re-wraps the *OEK* with a new *KEK* derived from the new data key / EK: - -##### Figure 3 - KMS data key rotation - -``` - object metadata KMS - | | - | +----------------+ 1a | +-------+ - |-------------------->| EK_1_encrypted |-----------|->| CMK_1 | - | +----------------+ | +---+---+ - | | | - | +---------------+ +------+ 1b | | - |------------->| OEK_encrypted | | EK_1 |<---|------+ - | +-------+-------+ +------+ | - | \ / | - | \___ 2 ___/ | - | \___/ | - | | | - | +--+--+ | - | | OEK | | +-------+ - | +--+--+ | | CMK_2 | - | | | +---+---+ - | | | | - | 5 +----------------+ |4 +------+ 3a | | - |<------| OEK_encrypted' |<----+-------| EK_2 |<---|------+ - | +----------------+ +------+ | | - | +----------------+ 3b | | - |<-------------------| EK_2_encrypted |<-----------|------+ - | +----------------+ | - | | - - -1a) Send encrypted data key and master key ID to KMS. -1b) Receive decrypted data key. -2) Decrypt encrypted object key with the KEK derived from the data key. -3a) Receive new plain data key from the KMS using the master key ID of the server config. -3b) Receive encrypted form of the data key from the KMS. -4) Derive a new KEK from the new data key and re-encrypt the OEK with it. -5) Store the encrypted OEK encrypted data key and master key ID in object metadata. - ``` - -Only the root/admin user can perform an SSE-S3 key rotation using the Admin-API via [mc](https://github.com/minio/mc). For more details about how to perform key management operations using the CLI refer to [mc admin guide](https://github.com/minio/mc/blob/master/docs/minio-admin-complete-guide.md) or run `mc admin kms key`. - -#### Secure Erasure and Locking - -The Silo server requires an available KMS to en/decrypt SSE-S3 encrypted objects. Therefore it is possible to erase or lock some or all encrypted objects. For example in case of a detected attack or other emergency situations the following actions can be taken: - -- Seal the KMS such that it cannot be accessed by the Silo server anymore. That will lock **all** SSE-S3 encrypted objects protected by master keys stored on the KMS. All these objects can not be decrypted as long as the KMS is sealed. -- Seal/Unmount one/some master keys. That will lock all SSE-S3 encrypted objects protected by these master keys. All these objects can not be decrypted as long as the key(s) are sealed. -- Delete one/some master keys. From a security standpoint, this is equal to erasing all SSE-S3 encrypted objects protected by these master keys. All these objects are lost forever as they cannot be decrypted. Especially deleting all master keys at the KMS is equivalent to secure erasing all SSE-S3 encrypted objects. - -## Acronyms - -- **AEAD**: Authenticated Encryption with Associated Data -- **CSPRNG**: Cryptographically Secure Pseudo Random Number Generator -- **EK**: External Key -- **IV**: Initialization Vector -- **KEK**: Key Encryption Key -- **OEK**: Object Encryption Key -- **PRF**: Pseudo Random Function -- **SSE**: Server-Side Encryption -- **SSE-C**: Server-Side Encryption with client-provided Keys -- **SSE-S3**: Server-Side Encryption with a KMS diff --git a/docs/security/advisories.md b/docs/security/advisories.md deleted file mode 100644 index c68db97eb..000000000 --- a/docs/security/advisories.md +++ /dev/null @@ -1,64 +0,0 @@ -# pgsty/silo Security Advisories - -This document summarizes fork-specific security fixes and closely related upgrade-impacting security notes in `pgsty/silo`. It is intentionally narrower than a full changelog and focuses on release-impacting security behavior. - -Entries carry a CVE identifier where one exists. Where none does, they carry a fork-local `SN--` identifier so that a finding without a CVE can still be referenced stably from release notes, commits and issues. An `SN-` identifier is **not** a CVE and is not registered in any vulnerability database; it is deliberately not written in CVE form so that scanners do not mistake it for one. Upstream `minio/minio` is archived, so for findings in inherited code there is no upstream maintainer to coordinate a CVE assignment with. `SN-2026-001` is the streaming-flush regression in `trackingResponseWriter`, which is a reliability defect rather than a security one and is tracked in the release notes rather than here. - -## Inherited upstream advisory baseline - -The first Silo community release was cut from upstream history that already contained the following security fix. Upstream and Silo links are both recorded even when the fork preserves the same commit object and SHA; that identity is the inheritance evidence, not a claim that Silo independently reimplemented the patch. - -| ID | Upstream remediation | Silo inheritance | Regression evidence | Release / operator note | -| :-- | :-- | :-- | :-- | :-- | -| [CVE-2025-62506](https://github.com/advisories/GHSA-jjjj-jwhf-8rgr) | [minio/minio#21642](https://github.com/minio/minio/pull/21642), merged as [`c1a49490`](https://github.com/minio/minio/commit/c1a49490c78e9c3ebcad86ba0662319138ace190) | The same commit object is present as [`pgsty/silo@c1a49490`](https://github.com/pgsty/silo/commit/c1a49490c78e9c3ebcad86ba0662319138ace190) | The inherited [service-account](https://github.com/pgsty/silo/blob/c1a49490c78e9c3ebcad86ba0662319138ace190/cmd/admin-handlers-users_test.go#L211-L212) and [STS](https://github.com/pgsty/silo/blob/c1a49490c78e9c3ebcad86ba0662319138ace190/cmd/sts-handlers_test.go#L45-L46) regression groups run for root and non-root parents through `go test ./cmd` | Resets `DenyOnly` while evaluating a restricted session policy so service or STS accounts cannot mint an unrestricted child service account. Upstream first fixed this in [`RELEASE.2025-10-15T17-29-55Z`](https://github.com/minio/minio/releases/tag/RELEASE.2025-10-15T17-29-55Z); every Silo community release, beginning with [`RELEASE.2025-12-03T12-00-00Z`](https://github.com/pgsty/silo/releases/tag/RELEASE.2025-12-03T12-00-00Z), contains it. Operators migrating from an older upstream build should upgrade and audit service accounts created by restricted service or STS identities. | - -## Current release boundary (2026-09-13) - -The latest published Server is `RELEASE.2026-09-03T13-18-01Z`. -SN-2026-011 is fixed on main but remains present in that release and all earlier -public Server releases. A newer mcli, pkg or standalone Console does not patch -an installed Server. See [CHANGELOG.md](../../CHANGELOG.md) and the -[component matrix](https://silo.pgsty.com/compatibility/versions/) for source pins. - -## Advisories since `RELEASE.2026-03-21T00-00-00Z` - -| ID | Fixed by | Affected area | Remote exploitability | Summary | Upgrade / workaround notes | -| :-- | :-- | :-- | :-- | :-- | :-- | -| `CVE-2026-33322` | `d24f449e0` | OIDC STS (`AssumeRoleWithWebIdentity`, `AssumeRoleWithClientGrants`) | Yes | Closes JWT algorithm confusion by removing HMAC/shared-secret verification and requiring JWKS-backed verifier keys | Breaking change: providers issuing `HS256`, `HS384`, or `HS512` tokens for these STS flows must switch to JWKS-backed RSA or ECDSA signing before upgrading. `PS256` and `EdDSA` are not currently supported. | -| `CVE-2026-33419` | `3b950f8fa` | LDAP STS authentication | Yes | Prevents username enumeration by unifying unknown-user and bad-password responses and adds in-memory login throttling | Unknown users and invalid passwords now both return `400 InvalidParameterValue`. Rate limiting is per-node, in-memory, and not currently configurable. Follow-up hardening landed in `18b712d49`, `9e10f6d9a`, and `f44110890`. | -| `CVE-2026-34204` | `56fa63bfd` | Replication metadata handling | Yes | Blocks untrusted `X-Minio-Replication-*` headers from being smuggled into internal replication metadata and leaving objects unreadable | Upgrade any server that accepts untrusted `PutObject` or `CopyObject` requests, which in practice means almost any production server that accepts writes. | -| `CVE-2026-39414` | `3252d5b7f` | S3 Select oversized record handling | Yes | Rejects oversized CSV records and non-`simdjson` line-delimited JSON records with `OverMaxRecordSize` instead of buffering them unchecked | Split oversized JSON lines client-side if you rely on `simdjson` input paths until the fast path gains the same pre-check. | -| [CVE-2026-41145](https://github.com/advisories/GHSA-hv4r-mvr4-25vw) | `f444b6f37` | Unsigned-trailer PUT and multipart upload authentication | Yes | Closes the query-string authentication bypass in unsigned-trailer streaming requests | Upgrade if clients can reach object write endpoints using the `STREAMING-UNSIGNED-PAYLOAD-TRAILER` content-sha256 mode together with query-string SigV4 credentials. | -| [CVE-2026-40344](https://github.com/advisories/GHSA-9c4q-hq6p-c237) | `efb6e5b00` | Snowball auto-extract authentication | Yes | Verifies request authentication before tar extraction in Snowball unsigned-trailer flows | Upgrade if you use `PutObjectExtract` or Snowball uploads. | -| [CVE-2026-42600](https://github.com/advisories/GHSA-xh8f-g2qw-gcm7) | `73ac52472` | Internode `ReadMultiple` storage-REST endpoint | Yes (cluster-root JWT required) | Removes the unused endpoint that allowed path traversal outside configured drive roots | Upgrade distributed-erasure deployments. Single-node deployments do not register this route. | -| `SN-2026-002` | `ca7baa670` and follow-ups | Internode storage-REST and Grid RPC payloads | Yes (cluster-root / internode JWT required) | Completes CVE-2026-42600. Its fix removed one endpoint that exercised the gap; the gap itself -- request bodies and grid frames never reaching the validity middleware, and no containment in the storage layer -- remained across three further protocol surfaces. Closes path traversal on both the volume and path axes (including the peer-S3 bucket RPCs, which bypass the storage-REST wrapper entirely), an unrecoverable divide-by-zero that killed a node per RPC frame, metadata that reported truncated shards as intact, and three allocations sized from caller-declared values. | Upgrade distributed-erasure deployments. Single-node deployments register none of these routes. No S3 API behaviour changes; object keys containing `.` or `..` path segments were already refused at the S3 boundary. | -| `SN-2026-003` | [`silo-pkg v3.11.0`](https://github.com/pgsty/silo-pkg/releases/tag/v3.11.0) and [`2f55347f7`](https://github.com/pgsty/silo/commit/2f55347f78352aed8e08866d370c9426c73362cf) | S3/IAM bucket-policy condition values | Yes (policy-dependent) | Prevents raw request entries that spell condition-key names from shadowing or synthesizing internal condition values; confines `s3:signatureAge` to verified SigV4 presigned requests; separates query-only list fields from header-backed `x-amz-*` fields; and stops client request tags from impersonating stored existing-object tags. | The compatible query form remains for storage class and upload tagging on handlers that consume it; an explicitly present header wins, including an empty header. The historical `X-Amz-Tagging` Header mapping remains a client-supplied `RequestObjectTag` source, so use request-tag conditions only on operations that consume tags. Header-only `x-amz-*` policy keys no longer accept query substitutes. `aws:SourceIp` was left following the existing forwarding-header trust model; that model is addressed separately in the next row. See [Condition value sources and precedence](https://silo.pgsty.com/administration/identity-access-management/policy-based-access-control/#condition-value-sources). | -| Not a vulnerability | `fe6dc4780` | Client source address (`aws:SourceIp`, audit `remotehost`, event notification `Host`) | N/A -- opt-in hardening | Adds an enforceable forwarded-header trust boundary, `MINIO_API_TRUSTED_PROXIES`. Set to a list of addresses or CIDR blocks, forwarded headers are believed only from those peers and forwarding chains are read right-to-left past listed hops -- which also stops the client-supplied left-most entry that an appending proxy (the stock nginx `$proxy_add_x_forwarded_for` recipe, or HAProxy's added second header line) leaves in place. Set to `none`, no forwarded header is believed at all. This is the guarantee `_MINIO_API_XFF_HEADER=off` never provided: it suppresses `X-Forwarded-For` alone, so `X-Real-IP` and RFC 7239 `Forwarded` remain one-line substitutions for anyone that setting was meant to stop. | **No behaviour change for any existing deployment**, so there is nothing to do on upgrade unless you want the new boundary. Not assigned a CVE: the default matches upstream, and upstream's own position (maintainer response in [discussion #17878](https://github.com/minio/minio/discussions/17878), Aug 2023) is that IP-based restrictions are impractical without reliable source-IP visibility. The gap being closed is that this was never written anywhere an operator would find it -- an `IpAddress` condition is accepted and behaves as though it works. **If you use `IpAddress` or `NotIpAddress` conditions, note that they were not enforceable before this change**, including behind a reverse proxy whose `X-Forwarded-For` recipe appends rather than overwrites. If you do not, the change affects only the accuracy of client addresses in logs. The new variable is opt-in and inert when unset; `_MINIO_API_XFF_HEADER` keeps its exact upstream semantics, and upstream's `TestXFFDisabled` is retained unmodified as the proof. An `IpAddress` condition remains unenforceable by default against a client with direct network access to the API port -- that is the condition the allowlist exists to fix, not a regression introduced here. When enabling the allowlist: it must name proxies, not the subnet they sit in, because entries are skipped while walking the chain, so a range that also covers clients lets those clients forge. Multi-node deployments must include their own node addresses, since MinIO forwards some requests between nodes and a client can force a hop through the `ListObjectsV2` continuation token; prefer the allowlist over `none` on a cluster for that reason. Loopback is always trusted as a peer so FTP and SFTP keep attributing their sessions. A malformed value stops startup, as does one that names no proxy at all (`","`) or one whose `env://` remote could not be read -- `env.Get` discards that error and yields an empty string, which would otherwise read as unset. Whitespace-only remains equivalent to unset. The policy is read after `MINIO_CONFIG_ENV_FILE` is loaded so environment-file deployments are covered; `_MINIO_API_XFF_HEADER` deliberately keeps upstream's earlier read timing, where a value written into an environment file is ignored. `MINIO_IDENTITY_LDAP_STS_TRUSTED_PROXIES` now shares the same list parser, but is behaviourally untouched: the extraction is pure code motion, verified identical to the previous implementation across every combination of 37 allowlist values and 21 peer addresses. See [Client source address trust](source-address-trust.md). | -| `SN-2026-004` | [`silo-pkg v3.11.0`](https://github.com/pgsty/silo-pkg/releases/tag/v3.11.0) and [`97b7d2804`](https://github.com/pgsty/silo/commit/97b7d28040d109061c0a46a4c01bfc7800a97cc1) | IAM policy evaluation of bucket-level actions | Yes (policy-dependent) | Withholds twelve sensitive bucket-level writes from an object-only resource pattern. The IAM matcher appended a trailing slash for bucket-level requests (empty object name), so a resource of `arn:aws:s3:::bucket/*` matched `"bucket/"` and authorized bucket-level actions it was never meant to reach -- upstream [minio/minio#20449](https://github.com/minio/minio/issues/20449). The bucket-policy evaluation path never had the slash and was already reference-correct. | **This is an authorization tightening; read this row before upgrading if you write your own bucket-scoped policies.** Withheld from `bucket/*` on `Allow` statements only: `PutBucketPolicy`, `DeleteBucketPolicy`, `PutBucketObjectLockConfiguration`, `PutBucketVersioning`, `PutReplicationConfiguration`, `PutBucketLifecycle`, `DeleteBucket`, `ForceDeleteBucket`, `PutBucketCors`, `DeleteBucketCors`, `PutBucketQOS`, `PutInventoryConfiguration`. Membership was decided by one question -- does reaching this action give the caller something its object-scoped grant does not already give it? -- because the bug only fires when the statement already grants the bucket action, which in practice means `s3:*`, so the affected principal already holds full object CRUD. Only actions that hand out access to others, defeat a protection aimed at write-holders, act under server credentials, outlive the grant, or destroy the bucket entity qualify. **Deliberately not withheld, and asserted by test so re-adding one is a deliberate act**: `ListBucket`, `GetBucketLocation` and the read/list family, `PutBucketTagging`, `PutBucketEncryption`, `PutBucketNotification`, and `CreateBucket` -- so `mc ls`, SDK session setup and ordinary tenant self-service keep working through `bucket/*`. Breaking those is what got upstream's own full fix reverted. **What to change**: add the bare bucket ARN (`arn:aws:s3:::bucket`) alongside `arn:aws:s3:::bucket/*` in any statement that legitimately grants one of the twelve. Built-in canned policies are unaffected (all use `Resource: "*"`). `Deny` statements are untouched, so no bucket lock is ever weakened, and `NotResource` exclusions keep their full reach. The hardening is monotone by construction rather than by argument: the protected path requires **both** the bare and the historical `"bucket/"` form to match, an intersection with the historical decision -- without that, a fixed-width wildcard such as `mybucke?` would match `"mybucket"` while never having matched `"mybucket/"`, and the hardening would have granted a write the buggy matcher refused. `MINIO_API_LEGACY_BUCKET_RESOURCE_MATCH=on` restores the historical behaviour in full; it is read once at startup. Still deferred to a migration-gated release: the read/list family, a startup audit naming affected policies, and a self-explaining denial log. | -| `SN-2026-005` | [`silo-pkg v3.12.0`](https://github.com/pgsty/silo-pkg/releases/tag/v3.12.0) and [`eee05a17c`](https://github.com/pgsty/silo/commit/eee05a17c34a07cebb27220d12697be74c8bd617) | IAM named-policy and service-account policy writes | No direct remote exploit; policy-dependent | Rejects S3, S3 Tables, and KMS ARN namespace prefixes that name no resource, including their historical `*arn:...` serialization, in both `Resource` and `NotResource`. A resource-matching `Deny` using such a prefix could silently fail to deny, while an `Allow` with the prefix in `NotResource` could match far more broadly than intended. The guard applies when creating named policies and when creating or updating service-account session policies. | **This is an authorization tightening for new and updated policies.** Existing policies keep loading, matching, importing, and replicating with unchanged runtime behavior, but a policy containing one of these prefixes cannot be submitted unchanged; replace it with the intended concrete resource, or use an explicit wildcard such as `arn:aws:s3:::*` only when all resources are intended. Enabling the strict path also rejects an admin statement that combines `Resource` with `NotResource`, and rejects non-S3 resources on bucket-scoped admin actions. Here “bare ARN prefix” means a namespace with no resource after it (`arn:aws:s3:::`); it is distinct from the valid “bare bucket ARN” in `SN-2026-004` (`arn:aws:s3:::bucket`). IAM import, site-replication receive paths, stored-policy loading, and STS inline policies remain on the permissive compatibility path in this release. | -| `SN-2026-006` | [`b73581b05`](https://github.com/pgsty/silo/commit/b73581b05) and [`c4fd97d0b`](https://github.com/pgsty/silo/commit/c4fd97d0b) ([#82](https://github.com/pgsty/silo/issues/82)) | SSE-C reads of zero-byte objects (`GetObject`, `HeadObject`, `CopyObject` source, `GetObjectAttributes`) | Yes; requires read access to the object | Zero-byte SSE-C objects never unsealed the customer-provided key, so a wrong key was accepted with `200` instead of `403`, and a copy or new version could be created under a key of the caller's choosing without knowing the current one. | Wrong keys now fail with `403 AccessDenied` as on AWS; correct keys behave as before and no client change is needed. Inherited from upstream; every earlier release is affected. | -| `SN-2026-007` | [`474cd5801`](https://github.com/pgsty/silo/commit/474cd5801), [`74c97d005`](https://github.com/pgsty/silo/commit/74c97d005), [`21870fa2e`](https://github.com/pgsty/silo/commit/21870fa2e) ([#84](https://github.com/pgsty/silo/issues/84)) | `GetObjectAttributes` on SSE-C objects | Yes; requires read access to the object | Attributes of SSE-C objects were returned without authenticating the customer key, and a bare `X-Minio-Source-Replication-Request` header skipped the check entirely. | A wrong key returns `403`, a replication marker without the key returns `400`; replication peers holding `s3:ReplicateObject` are unaffected. Inherited from upstream. | -| `SN-2026-008` | [PR #101](https://github.com/pgsty/silo/pull/101) ([`938603458`](https://github.com/pgsty/silo/commit/938603458) through [`04b097fd9`](https://github.com/pgsty/silo/commit/04b097fd9)) | Internal replication request headers such as `X-Minio-Source-Etag`, `X-Minio-Source-Mtime`, `X-Minio-Source-Replication-Request`, the replication SSE key headers, and `X-Amz-Bucket-Replication-Status` on object reads, writes, multipart uploads, deletes, Snowball extraction, and bucket events | Yes; any authenticated principal that can read or write the object | Completes CVE-2026-34204. The server still trusted these internal headers on presence in most handlers: any client could preserve arbitrary ETags and modification times, read SSE-C ciphertext without the key, inject replication checksums and Object Lock timestamps, suppress bucket notifications, and route deletes as replication deletes. | Replication semantics now require the exact marker value together with `s3:ReplicateObject` or `s3:ReplicateDelete`; other requests have these headers removed after signature verification and are processed as ordinary requests. Site replication service accounts and bucket-replication targets that already hold the replication permissions are unaffected. Inherited from upstream. | -| `SN-2026-009` | [`58735ee38`](https://github.com/pgsty/silo/commit/58735ee38) and [`229fe2b3c`](https://github.com/pgsty/silo/commit/229fe2b3c) ([PR #73](https://github.com/pgsty/silo/pull/73)) | Admin `SetUserStatus` and `SetGroupStatus` | Yes; authenticated admin API | Status changes were authorized against `admin:EnableUser` / `admin:EnableGroup` regardless of the requested status, so a principal allowed only to enable could also disable, and vice versa. | Enable and disable now require the action matching the target status. Policies that grant only one of the pair lose the other operation; `admin:*` and the built-in `consoleAdmin` policy are unaffected. Inherited from upstream. | -| `SN-2026-010` | [PR #104](https://github.com/pgsty/silo/pull/104) ([`75a6734e4`](https://github.com/pgsty/silo/commit/75a6734e4) through [`d2d47a41f`](https://github.com/pgsty/silo/commit/d2d47a41f), [#58](https://github.com/pgsty/silo/issues/58)) | `DeleteObject` and `DeleteObjects` with an explicit `versionId` | Yes; authenticated S3 API | Explicit version deletes were authorized as `s3:DeleteObject` with only a deny check on `s3:DeleteObjectVersion`, diverging from AWS. | Explicit version deletes now require `s3:DeleteObjectVersion`, as on AWS. **Two policy effects:** principals granted only `s3:DeleteObject` can no longer delete specific versions, and a policy that relied on `Deny s3:DeleteObject` to block permanent deletes must also deny `s3:DeleteObjectVersion`, because `Allow s3:*` now permits explicit version deletes. Replication targets keep the `s3:ReplicateDelete` contract. Inherited from upstream. | -| `SN-2026-011` | [`123325430`](https://github.com/pgsty/silo/commit/1233254309b15571f101b2b26d531951ceaeef1e) | SigV4 signed-header coverage; `x-amz-copy-source` dispatch to `CopyObject` / `UploadPartCopy` | Yes; a party holding only a presigned PUT URL, or any signed PUT, needs no credentials of its own | SigV4 verification only checked that each named signed header was present and never inspected the `x-amz-*` headers that actually arrived (the meta-header check covered only `X-Amz-Meta-` and ran only on the presigned path), while the router dispatches any PUT carrying `x-amz-copy-source` to `CopyObjectHandler`. An unsigned `x-amz-copy-source` therefore turned a one-object write grant into a server-side copy of any object the signing key can read, executed as the signer; both the presigned and Authorization-header paths were affected, and where the destination bucket allows anonymous `GetObject` the copied private bytes become readable unauthenticated. | Any unsigned `x-amz-*` request header is now refused with `AccessDenied` on both paths, matching AWS S3 (AWS returns `403`; Silo returns `400 AccessDenied`, otherwise identical). Membership in the signed-headers list is required, so a header whose first value is empty cannot slip through. `X-Amz-Content-Sha256` remains accepted unsigned (payload hash, taken from the query for presigned requests and bound into the string-to-sign for signed ones); the internal `X-Amz-Signature-Age` scratch header is exempt so repeated verification stays idempotent; `PutObjectTagging` now injects its body-derived `X-Amz-Tagging` header after signature verification. Every AWS SDK, `minio-go`, and `mc` already signs its `x-amz-*` headers, so legitimate clients need no change. Inherited unchanged from upstream `minio/minio`; every earlier release is affected. Reported by Oren Yomtov; a CVE has been requested. | - -## Dependency security updates - -| ID | Fixed by | Summary | -| :-- | :-- | :-- | -| `CVE-2026-34986` | `68e0ba997` | Upgrades `go-jose` to `v4.1.4`. | -| `CVE-2026-39883` | `1869bd30b`, `e4fa06394` | Updates OpenTelemetry dependencies. | -| Upstream Go security fixes | [Go 1.26.5](https://go.dev/doc/devel/release#go1.26.5) | Bumps the required toolchain to Go 1.26.5, which includes security fixes to `crypto/tls` and `os`. | -| Toolchain and dependency refresh | [Go 1.27.1](https://go.dev/doc/devel/release#go1.27.1) via [`43f4bb7ed`](https://github.com/pgsty/silo/commit/43f4bb7ed), [`edc8be6ed`](https://github.com/pgsty/silo/commit/edc8be6ed), [`4d6e1ea8e`](https://github.com/pgsty/silo/commit/4d6e1ea8e) | Moves the toolchain to Go 1.27 (1.27.1 as of the release) and refreshes the dependency stack (etcd client v3.7.1, `jwx` v3.0.13, `klauspost/compress` v1.19.2). The pre-release cleanup then returns to upstream `minio-go` (v7.3.1 pre-release) and retires the `silo-go` fork; `govulncheck` reports no reachable vulnerability on the release candidate. | -| [GO-2026-6354](https://pkg.go.dev/vuln/GO-2026-6354) / [GO-2026-6355](https://pkg.go.dev/vuln/GO-2026-6355) | `golang.org/x/crypto` `v0.56.0` ([`edf36bcbf`](https://github.com/pgsty/silo/commit/edf36bcbf)) | Updates `x/crypto/ssh` to the first fixed version for denial of service on deadlocked undecided and established channels. Reachable through the SFTP server (`startSFTPServer` → `sftp.Server.Listen` → `ssh.NewServerConn`); every earlier release that enables SFTP is affected. | -| [GO-2026-6061](https://pkg.go.dev/vuln/GO-2026-6061) / [GHSA-hrxh-6v49-42gf](https://github.com/advisories/GHSA-hrxh-6v49-42gf) | gRPC `v1.82.1` | Updates gRPC to the first fixed version for vulnerabilities in the xDS RBAC authorization engine and HTTP/2 transport server. | -| [CVE-2026-84304](https://github.com/advisories/GHSA-vp52-pcj8-j9qc) | gRPC `v1.83.1` | Updates gRPC-Go to the first fixed version for unauthenticated heap exhaustion through highly fragmented HTTP/2 DATA frames. Silo pulls gRPC transitively rather than registering a gRPC server itself, but selects the fixed version for the complete module graph. | -| [GO-2026-5970](https://pkg.go.dev/vuln/GO-2026-5970) / `CVE-2026-56852` | `x/text` `v0.39.0` | Updates `x/text` to the first fixed version for an infinite loop on invalid input. | - -## Operationally significant security-related fixes - -| Change | Fixed by | Summary | -| :-- | :-- | :-- | -| Replicated Object Lock updates ignored their timestamps | pre-release cleanup for the release after 20260806 | A replicated `CopyObject` rebuilt the metadata from the request before comparing replication timestamps, so the stored retention and legal-hold timestamps were never seen: any replica update was applied regardless of order, and the legal-hold timestamp was written under the retention key. A stale replica could therefore turn a newer legal hold off or shorten a newer retention. The stored state is now captured first, a replica update is applied only when its timestamp is newer, a stale one leaves the stored state in place, and each timestamp is kept under its own key. Inherited from upstream; every earlier release is affected. | -| LDAP TLS regression | `ce1c537eb` | Restores TLS configuration propagation for `ldaps://` `DialURL()` connections so `MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY` and custom root CAs work again. | diff --git a/docs/security/source-address-trust.md b/docs/security/source-address-trust.md deleted file mode 100644 index 1712bd82d..000000000 --- a/docs/security/source-address-trust.md +++ /dev/null @@ -1,223 +0,0 @@ -# Client source address trust - -Silo decides where a request came from, and that decision is load-bearing. The -address it settles on becomes: - -- `aws:SourceIp`, so it decides `IpAddress` and `NotIpAddress` policy conditions -- the audit log's `remotehost` field, so it decides who every logged action is - attributed to -- the `Host` field of S3 event notifications, and the client shown by - `mc admin trace` - -None of that is derived from the TCP connection by default. It is read out of the -`X-Forwarded-For`, `X-Real-IP` and RFC 7239 `Forwarded` request headers, which -any client can set to any value. This document states which peers Silo believes, -how to change that, and what each choice costs. - -## The three modes - -One setting selects all three. It is read from the environment, not from -`mc admin config`. - -| Mode | `MINIO_API_TRUSTED_PROXIES` | Source address | -| :-- | :-- | :-- | -| Untrusted (default) | unset | left-most `X-Forwarded-For` entry, else `X-Real-IP`, else `Forwarded`, else the TCP peer — from any client | -| Trust nobody | `none` | always the TCP peer | -| Allow-listed | a list of addresses and CIDR blocks | forwarded headers, but only from listed peers | - -`off` is accepted as a synonym for `none`, and the value is matched -case-insensitively. - -The setting is read once at startup, after `MINIO_CONFIG_ENV_FILE` is loaded, so -an environment file is a valid place to put it. A malformed value stops the -server rather than silently changing what every policy condition resolves to. - -> **`_MINIO_API_XFF_HEADER` is not this setting.** It suppresses parsing of -> `X-Forwarded-For` and nothing else, leaving `X-Real-IP` and `Forwarded` -> honoured, so it cannot stop a client naming its own address — a client refused -> one header simply sends another. It keeps its original upstream meaning, and -> applies *within* whichever mode above is in force. If you set it hoping to stop -> source-address forgery, that is what `MINIO_API_TRUSTED_PROXIES` is for. -> -> It also keeps upstream's read timing, which is *earlier* than the one described -> above: it is taken at package initialisation, before environment files are -> read, so writing it into `MINIO_CONFIG_ENV_FILE` has no effect. That quirk is -> deliberately left in place rather than repaired, because repairing it would -> make an already-deployed setting start taking effect. Set it in the process -> environment if you want it honoured. - -### Untrusted (default) - -Every deployment behaves exactly as it did before this setting existed. Any -client that can open a connection to the S3 API port can name its own address, -so under this mode: - -**An `IpAddress` policy condition is not enforceable, and audit client addresses -are not evidence.** Both are attacker-chosen for anyone with direct network -access to the API port. - -This mode is sound only when every route to the API port passes through a proxy -that overwrites all three headers. Two things commonly break that assumption: - -- **A second way in.** On Kubernetes an Ingress and a ClusterIP Service usually - coexist. The Ingress sanitises headers; the Service does not, and any pod in - the cluster can reach it. The boundary is assumed to be the Ingress but is - actually the pod network. -- **Appending proxies.** The stock nginx recipe - `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;` *appends* to - what the client sent. A client sending `X-Forwarded-For: 1.2.3.4` produces - `1.2.3.4, ` at Silo, and this mode reads the left-most entry — - the client's. Overwriting with `$remote_addr` avoids this; so does the - allow-listed mode below, which reads the chain from the other end. - -### Trust nobody - -``` -MINIO_API_TRUSTED_PROXIES=none -``` - -No forwarded header is believed. The source address is the TCP peer, always. - -Use this when Silo is reached directly and you would rather have a correct -address that is sometimes a proxy than a plausible one that is sometimes a lie. -Behind a proxy, every request will be attributed to the proxy. - -**On a multi-node deployment this also applies to Silo's own nodes.** Some -requests are forwarded between nodes (see [Multi-node](#multi-node-deployments)), -and the receiving node's peer is the forwarding node, so those requests are -attributed to it rather than to the client. Nothing corrects this under this -mode, because it believes nothing. Multi-node clusters that want direct requests -protected *and* internally-forwarded ones attributed correctly should use the -allow-listed mode with the node addresses included. - -The scheme headers (`X-Forwarded-Proto`, `X-Forwarded-Scheme`) are deliberately -unaffected by any of this. They feed the `Location` URL in S3 responses, not a -policy decision, and suppressing them would hand `http://` URLs to every -deployment terminating TLS at a proxy. - -### Allow-listed - -``` -MINIO_API_TRUSTED_PROXIES=10.0.0.1,10.0.0.2 -``` - -A comma-, semicolon- or whitespace-separated list of addresses and CIDR blocks. -Forwarded headers are believed only when the request's TCP peer matches. Every -other peer is attributed to itself, whatever its headers claim. - -Write entries in their plain form. An address written in IPv4-mapped notation -(`::ffff:192.168.1.10`) is accepted but matches nothing, because peers are -reduced to plain form before matching — so the entry grants no trust and the -proxy is treated as any other client. This is long-standing behaviour shared -with `MINIO_IDENTITY_LDAP_STS_TRUSTED_PROXIES`, and it fails closed. - -This is the only mode under which `aws:SourceIp` is enforceable against a client -that can reach the API port directly. - -Catch-all ranges (`0.0.0.0/0`, `::/0`) are rejected. They would trust every peer -and quietly reinstate the untrusted mode under a name that suggests otherwise. -Note that only `/0` itself is rejected — a pair like `0.0.0.0/1,128.0.0.0/1` -covers the same ground and is accepted, so the check is a guardrail, not a proof. - -> **List proxies, not the subnet they sit in.** This is the one way to configure -> the setting so that it makes things worse rather than better, so it is worth -> stating plainly. -> -> Entries on the list are skipped during the chain walk described below. If the -> list covers addresses that *clients* also occupy — `10.0.0.0/8` when the load -> balancer is `10.0.0.1` and application servers are elsewhere in `10/8` — then -> those clients' addresses are skipped too, and the walk continues past the real -> client into whatever it placed to the left. A client at `10.5.5.5` sending -> `X-Forwarded-For: 8.8.8.8` would then be recorded as `8.8.8.8`. -> -> A broad list does not merely trust more peers; it lets those peers forge. -> Prefer exact host entries or the narrowest prefix that contains only proxies. - -**Chain handling.** `X-Forwarded-For` and `Forwarded` are read right-to-left, -stepping over entries that name a listed proxy, and the first remaining address -wins. Each proxy appends the peer it actually saw, so an entry the client -injected sits to the left of the one its proxy wrote, and the walk stops before -reaching it. Appending proxies are therefore safe here. List every proxy hop's -address so intermediate hops are skipped. Repeated header lines are handled as -one chain, so proxies that add a second `X-Forwarded-For` line rather than -extending the first — HAProxy's `option forwardfor` — work correctly. - -**`X-Real-IP` is a single value with no chain, so it cannot be checked against -the list.** It is trusted verbatim, and only when the chain headers yield -nothing. The deployment contract is that a listed proxy overwrites whichever -headers it sets — for nginx, `proxy_set_header X-Real-IP $remote_addr;`. A proxy -that relays the client's copy instead is choosing to let the client answer the -question, and nothing Silo does can undo that. - -> **Strip at the edge every source-address header your proxy does not itself -> write.** This is the one rule that covers every case, and it is worth following -> even if the rest of this section is skipped. -> -> Listing a peer means believing all three headers from it, and they are -> consulted in order: `X-Forwarded-For`, then `X-Real-IP`, then `Forwarded`. A -> header your proxy does not write is entirely under the client's control, and if -> it is consulted before the one your proxy *does* write, the client wins. -> -> The common way to get bitten: a proxy that authors only `X-Real-IP` (some nginx -> configurations) or only `Forwarded` (RFC 7239-native proxies) relays the -> client's `X-Forwarded-For` untouched — and `X-Forwarded-For` is read first, so -> a client sending `X-Forwarded-For: 8.8.8.8` is recorded as `8.8.8.8` despite -> the proxy having correctly written the real address elsewhere. - -(`MINIO_IDENTITY_LDAP_STS_TRUSTED_PROXIES` orders the first two the other way -round. Neither order is safe for every proxy — the mirror hazard is a proxy that -authors only `X-Forwarded-For` and relays a client's `X-Real-IP`, which is what -AWS ALB does. This setting prefers the header it can chain-validate, because it -decides access control rather than rate-limit bucketing. Stripping what your -proxy does not write makes the ordering irrelevant, which is why it is the rule -worth remembering.) - -**Loopback is always trusted as a peer**, even when not listed. The FTP and SFTP -front-ends reach the S3 layer over `127.0.0.1` and declare their session's client -with `X-Forwarded-For`; excluding loopback would attribute every FTP and SFTP -request to the server itself. A `127.0.0.1` entry appearing *inside* a chain is -not skipped — only peers are exempt, not chain entries. - -## Multi-node deployments - -**A cluster must list its own nodes.** Silo forwards some requests between -nodes — bucket-DNS and site-replication routing, listing continuation, -heal-by-token, batch jobs and pool decommissioning. The receiving node's peer is -the forwarding node, so unless the cluster's own addresses are on the list, those -requests resolve to the forwarding node rather than to the client. Include the -node addresses alongside your proxy's. - -This is not a rare path. A `ListObjectsV2` continuation token carries the node -index, so any client can cause its own request to be forwarded. If the nodes are -not listed, those requests are evaluated with `aws:SourceIp` set to an internal -node address and audited against one — which an `IpAddress` condition that allows -internal ranges would treat as a pass. - -## Choosing - -Behind a proxy you control, with no other route to the API port, the default is -fine and you need not set anything — but confirm the proxy overwrites rather than -appends, or you are in the second case below. Otherwise: - -- Direct exposure, no proxy: `MINIO_API_TRUSTED_PROXIES=none`. -- Behind a proxy, but the port is also reachable directly (the usual Kubernetes - Ingress-plus-Service case, and the usual Pigsty case): set - `MINIO_API_TRUSTED_PROXIES` to the proxy addresses plus the Silo node - addresses. This is the configuration that makes an `IpAddress` condition mean - something. -- Multi-node clusters: use the allow-list with node addresses included, not - `none`. -- Behind a proxy you are not sure about: allow-list it, and strip at the edge - every source-address header it does not itself write. - -Whichever applies, two rules govern every allow-list deployment: name proxies, -not the subnet they occupy; and strip what your proxy does not write. - -## Relationship to LDAP STS trusted proxies - -`MINIO_IDENTITY_LDAP_STS_TRUSTED_PROXIES` is a separate allow-list governing -which peers may supply the client address used for **LDAP STS login rate-limit -bucketing** only. It does not affect `aws:SourceIp` or audit addresses, and this -setting does not affect STS rate limiting. The two use the same list syntax and -the same chain-walking rules, and in most deployments should be set to the same -value. See [`docs/sts/ldap.md`](../sts/ldap.md). diff --git a/docs/sts/ldap.md b/docs/sts/ldap.md index 7e6931883..2f0bb2d61 100644 --- a/docs/sts/ldap.md +++ b/docs/sts/ldap.md @@ -119,7 +119,7 @@ Only requests whose peer address matches this allowlist may supply forwarded cli The RFC 7239 `Forwarded` header is not used for this bucket; deployments that only send `Forwarded` fall back to the peer-address bucket. -This allowlist governs LDAP STS rate-limit bucketing only. The client address used for `aws:SourceIp`, audit logs and event notifications is governed separately by `MINIO_API_TRUSTED_PROXIES` — see [Client source address trust](../security/source-address-trust.md). The two use the same list syntax and the same chain-walking rules, and in most deployments should be set to the same value. They differ deliberately in one respect: this setting prefers `X-Real-IP` over `X-Forwarded-For`, while `MINIO_API_TRUSTED_PROXIES` prefers the chain-validated `X-Forwarded-For`, because it decides access control rather than rate-limit bucketing. Neither order is safe for every proxy; the linked document explains the trade-off. +This allowlist governs LDAP STS rate-limit bucketing only. The client address used for `aws:SourceIp`, audit logs and event notifications is governed separately by `MINIO_API_TRUSTED_PROXIES` — see [Client source address trust](https://silo.pgsty.com/reference/minio-server/settings/core/#client-source-address-trust). The two use the same list syntax and the same chain-walking rules, and in most deployments should be set to the same value. They differ deliberately in one respect: this setting prefers `X-Real-IP` over `X-Forwarded-For`, while `MINIO_API_TRUSTED_PROXIES` prefers the chain-validated `X-Forwarded-For`, because it decides access control rather than rate-limit bucketing. Neither order is safe for every proxy; the linked document explains the trade-off. ### Lookup-Bind