diff --git a/.github/workflows/openclaw-release-telegram-qa.yml b/.github/workflows/openclaw-release-telegram-qa.yml index fc18fe63c174..e5977f117283 100644 --- a/.github/workflows/openclaw-release-telegram-qa.yml +++ b/.github/workflows/openclaw-release-telegram-qa.yml @@ -1498,13 +1498,11 @@ jobs: local config_path="${1:?}" local config_id diagnostics_dir diagnostics_real proof_bytes proof_path proof_tmp umask 077 - config_id="$(printf '%s' "$config_path" | sha256sum | awk '{print $1}')" diagnostics_dir="${EVIDENCE_ROOT}/trusted-runtime-diagnostics" install -d -o "$RUNNER_UID" -g "$RUNNER_GID" -m 0700 "$diagnostics_dir" [[ ! -L "$diagnostics_dir" ]] diagnostics_real="$(realpath -e "$diagnostics_dir")" [[ "$diagnostics_real" == "$diagnostics_dir" ]] - proof_path="${diagnostics_dir}/gateway-model-config-${config_id}.json" proof_tmp="${RUNTIME_ROOT}/gateway-model-config-${BASHPID}.json" # Capture only bounded routing identifiers before the QA suite removes its temp config. @@ -1552,6 +1550,10 @@ jobs: ' "$config_path" >"$proof_tmp" proof_bytes="$(stat -c '%s' "$proof_tmp")" ((proof_bytes > 0 && proof_bytes <= 65536)) + # Content addressing deduplicates credential-specific temp configs + # that project to the same bounded routing proof. + config_id="$(sha256sum "$proof_tmp" | awk '{print $1}')" + proof_path="${diagnostics_dir}/gateway-model-config-${config_id}.json" chown "$RUNNER_UID:$RUNNER_GID" "$proof_tmp" chmod 0600 "$proof_tmp" mv -f "$proof_tmp" "$proof_path" diff --git a/AGENTS.md b/AGENTS.md index ec005211e67a..08109296d877 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -215,6 +215,7 @@ Skills own workflows; root owns hard policy and routing. - Trusted-workflow release-branch CI: pass `target_ref` + `release_candidate_ref`; never `release_gate` (requires workflow head == target). - Agent PR landing to `main`: use only the repo-native `scripts/pr` wrapper: run `scripts/pr review-init `, follow its emitted checkout/guard guidance, initialize and complete review artifacts with `scripts/pr review-artifacts-init `, validate them with `scripts/pr review-validate-artifacts `, then run `OPENCLAW_TESTBOX=1 scripts/pr prepare-run ` and `scripts/pr merge-run `. The Testbox flag is mandatory for agents so prepare verifies hosted CI/Testbox on the current head or reuses a patch-identical pre-rebase run green within 24 hours instead of running full gates locally. `prepare-run` fails fast; invoke only after exact-head CI is complete and green. For owner-approved reviewed fork code without hosted Testbox, use `OPENCLAW_PR_GATES_REMOTE=testbox` instead. Do not rebase only because `main` advanced; merge drift is advisory unless strict drift is explicitly enabled, while GitHub still blocks conflicts. Do not idle on `auto-response` or `check-docs`. - Non-main PRs: do not run `scripts/pr prepare-run` or `merge-run`; they diff against `main`. Use review artifacts, exact base-head CI, revalidate `headRefOid`, then `gh pr merge --match-head-commit `. +- Merge guard shells: start `set -euo pipefail`; a failed `[[ ... ]]` alone does not stop a later merge command. - After `scripts/pr merge-run` removes its worktree, `cd` to a persistent repo before follow-up commands. - `scripts/pr` review JSON: land-ready recommendation `READY FOR /prepare-pr`, `issueValidation.status=valid`; never `APPROVE`. diff --git a/test/scripts/openclaw-release-telegram-qa-workflow.test.ts b/test/scripts/openclaw-release-telegram-qa-workflow.test.ts index 9a685f2ad874..bf5c6b0a2395 100644 --- a/test/scripts/openclaw-release-telegram-qa-workflow.test.ts +++ b/test/scripts/openclaw-release-telegram-qa-workflow.test.ts @@ -792,6 +792,7 @@ describe("release Telegram QA workflow", () => { const runtimeRoot = join(workdir, "runtime"); const evidenceRoot = join(workdir, "evidence"); const configPath = join(workdir, "openclaw.json"); + const duplicateConfigPath = join(workdir, "openclaw-duplicate.json"); mkdirSync(runtimeRoot); mkdirSync(evidenceRoot); writeFileSync( @@ -815,17 +816,22 @@ describe("release Telegram QA workflow", () => { }, }), ); + writeFileSync( + duplicateConfigPath, + readFileSync(configPath, "utf8").replace("not-exported", "different-ignored-value"), + ); const result = spawnSync( "bash", [ "-c", - `set -euo pipefail\n${captureSource}\ncapture_live_model_config "$CONFIG_PATH"\nfind "$EVIDENCE_ROOT/trusted-runtime-diagnostics" -type f -name 'gateway-model-config-*.json' -print`, + `set -euo pipefail\n${captureSource}\ncapture_live_model_config "$CONFIG_PATH"\ncapture_live_model_config "$DUPLICATE_CONFIG_PATH"\nfind "$EVIDENCE_ROOT/trusted-runtime-diagnostics" -type f -name 'gateway-model-config-*.json' -print`, ], { encoding: "utf8", env: { ...process.env, CONFIG_PATH: configPath, + DUPLICATE_CONFIG_PATH: duplicateConfigPath, EVIDENCE_ROOT: evidenceRoot, RUNTIME_ROOT: runtimeRoot, RUNNER_GID: String(process.getgid?.() ?? 0), @@ -834,7 +840,9 @@ describe("release Telegram QA workflow", () => { }, ); expect(result.status, result.stderr).toBe(0); - const proofPath = result.stdout.trim(); + const proofPaths = result.stdout.trim().split("\n"); + expect(proofPaths).toHaveLength(1); + const proofPath = proofPaths[0]!; const proofText = readFileSync(proofPath, "utf8"); expect(JSON.parse(proofText)).toEqual({ agentDefaultModel: {