diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0fff5fae4a95..fd9b2bbfef05 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,56 +1,62 @@ # Protect the ownership rules themselves. -/.github/CODEOWNERS @steipete +/.github/CODEOWNERS @timeleft-- # WARNING: GitHub CODEOWNERS uses last-match-wins semantics. -# If you add overlapping rules below the secops block, include @openclaw/openclaw-secops -# on those entries too or you can silently remove required secops review. -# Security-sensitive code, config, and docs require secops review. -/SECURITY.md @openclaw/openclaw-secops -/.github/dependabot.yml @openclaw/openclaw-secops -/.github/codeql/ @openclaw/openclaw-secops -/.github/workflows/codeql.yml @openclaw/openclaw-secops -/.github/workflows/codeql-android-critical-security.yml @openclaw/openclaw-secops -/.github/workflows/codeql-critical-quality.yml @openclaw/openclaw-secops -/src/security/ @openclaw/openclaw-secops -/src/secrets/ @openclaw/openclaw-secops -/src/config/*secret*.ts @openclaw/openclaw-secops -/src/config/**/*secret*.ts @openclaw/openclaw-secops -/src/gateway/*auth*.ts @openclaw/openclaw-secops -/src/gateway/**/*auth*.ts @openclaw/openclaw-secops -/src/gateway/*secret*.ts @openclaw/openclaw-secops -/src/gateway/**/*secret*.ts @openclaw/openclaw-secops -/src/gateway/security-path*.ts @openclaw/openclaw-secops -/src/gateway/resolve-configured-secret-input-string*.ts @openclaw/openclaw-secops -/src/gateway/protocol/**/*secret*.ts @openclaw/openclaw-secops -/src/gateway/server-methods/secrets*.ts @openclaw/openclaw-secops -/src/agents/*auth*.ts @openclaw/openclaw-secops -/src/agents/**/*auth*.ts @openclaw/openclaw-secops -/src/agents/auth-profiles*.ts @openclaw/openclaw-secops -/src/agents/auth-health*.ts @openclaw/openclaw-secops -/src/agents/auth-profiles/ @openclaw/openclaw-secops -/src/agents/sandbox.ts @openclaw/openclaw-secops -/src/agents/sandbox-*.ts @openclaw/openclaw-secops -/src/agents/sandbox/ @openclaw/openclaw-secops -/src/infra/secret-file*.ts @openclaw/openclaw-secops -/src/cron/stagger.ts @openclaw/openclaw-secops -/src/cron/service/jobs.ts @openclaw/openclaw-secops -/docs/security/ @openclaw/openclaw-secops -/docs/gateway/authentication.md @openclaw/openclaw-secops -/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md @openclaw/openclaw-secops -/docs/gateway/sandboxing.md @openclaw/openclaw-secops -/docs/gateway/secrets-plan-contract.md @openclaw/openclaw-secops -/docs/gateway/secrets.md @openclaw/openclaw-secops -/docs/gateway/security/ @openclaw/openclaw-secops -/docs/cli/approvals.md @openclaw/openclaw-secops -/docs/cli/sandbox.md @openclaw/openclaw-secops -/docs/cli/security.md @openclaw/openclaw-secops -/docs/cli/secrets.md @openclaw/openclaw-secops -/docs/reference/secretref-credential-surface.md @openclaw/openclaw-secops -/docs/reference/secretref-user-supplied-credentials-matrix.json @openclaw/openclaw-secops +# This downstream repo can only name collaborators or teams with write access here. +# Security-sensitive code, config, and docs require owner review. +/SECURITY.md @timeleft-- +/.github/dependabot.yml @timeleft-- +/.github/codeql/ @timeleft-- +/.github/workflows/codeql.yml @timeleft-- +/.github/workflows/codeql-android-critical-security.yml @timeleft-- +/.github/workflows/codeql-critical-quality.yml @timeleft-- +/src/security/ @timeleft-- +/src/secrets/ @timeleft-- +/src/config/*secret*.ts @timeleft-- +/src/config/**/*secret*.ts @timeleft-- +/src/gateway/*auth*.ts @timeleft-- +/src/gateway/**/*auth*.ts @timeleft-- +/src/gateway/*secret*.ts @timeleft-- +/src/gateway/**/*secret*.ts @timeleft-- +/src/gateway/security-path*.ts @timeleft-- +/src/gateway/resolve-configured-secret-input-string*.ts @timeleft-- +/src/gateway/protocol/**/*secret*.ts @timeleft-- +/src/gateway/server-methods/secrets*.ts @timeleft-- +/src/agents/*auth*.ts @timeleft-- +/src/agents/**/*auth*.ts @timeleft-- +/src/agents/auth-profiles*.ts @timeleft-- +/src/agents/auth-health*.ts @timeleft-- +/src/agents/auth-profiles/ @timeleft-- +/src/agents/sandbox.ts @timeleft-- +/src/agents/sandbox-*.ts @timeleft-- +/src/agents/sandbox/ @timeleft-- +/src/infra/secret-file*.ts @timeleft-- +/src/cron/stagger.ts @timeleft-- +/src/cron/service/jobs.ts @timeleft-- +/docs/security/ @timeleft-- +/docs/gateway/authentication.md @timeleft-- +/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md @timeleft-- +/docs/gateway/sandboxing.md @timeleft-- +/docs/gateway/secrets-plan-contract.md @timeleft-- +/docs/gateway/secrets.md @timeleft-- +/docs/gateway/security/ @timeleft-- +/docs/cli/approvals.md @timeleft-- +/docs/cli/sandbox.md @timeleft-- +/docs/cli/security.md @timeleft-- +/docs/cli/secrets.md @timeleft-- +/docs/reference/secretref-credential-surface.md @timeleft-- +/docs/reference/secretref-user-supplied-credentials-matrix.json @timeleft-- # Release workflow and its supporting release-path checks. -/.github/workflows/openclaw-npm-release.yml @openclaw/openclaw-release-managers -/docs/reference/RELEASING.md @openclaw/openclaw-release-managers -/scripts/openclaw-npm-publish.sh @openclaw/openclaw-release-managers -/scripts/openclaw-npm-release-check.ts @openclaw/openclaw-release-managers -/scripts/release-check.ts @openclaw/openclaw-release-managers +/.github/workflows/openclaw-npm-release.yml @timeleft-- +/docs/reference/RELEASING.md @timeleft-- +/scripts/openclaw-npm-publish.sh @timeleft-- +/scripts/openclaw-npm-release-check.ts @timeleft-- +/scripts/release-check.ts @timeleft-- + +# ProdClaw downstream governance and release surfaces. +# Keep these rules last so broader upstream workflow rules cannot override @timeleft--. +/PRODCLAW.md @timeleft-- +/PRODCLAW_UPSTREAM.json @timeleft-- +/docs/reference/prodclaw-release-policy.md @timeleft-- +/.github/workflows/prodclaw-*.yml @timeleft-- diff --git a/.github/workflows/auto-response.yml b/.github/workflows/auto-response.yml index f079f8d79b01..ecbdd1a2a2b9 100644 --- a/.github/workflows/auto-response.yml +++ b/.github/workflows/auto-response.yml @@ -19,6 +19,9 @@ permissions: {} jobs: auto-response: + # ProdClaw does not carry OpenClaw maintainer app secrets. Keep this + # inherited automation upstream-only unless ProdClaw deliberately replaces it. + if: github.repository == 'openclaw/openclaw' || github.repository == 'openclaw-org/openclaw' permissions: contents: read issues: write diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index dbc38db73ecc..8784ef291c04 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -27,6 +27,9 @@ permissions: {} jobs: label: + # ProdClaw does not carry OpenClaw maintainer app secrets. Keep this + # inherited triage automation upstream-only unless ProdClaw deliberately replaces it. + if: github.repository == 'openclaw/openclaw' || github.repository == 'openclaw-org/openclaw' permissions: contents: read pull-requests: write @@ -441,7 +444,7 @@ jobs: } backfill-pr-labels: - if: github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' && (github.repository == 'openclaw/openclaw' || github.repository == 'openclaw-org/openclaw') permissions: contents: read pull-requests: write @@ -741,6 +744,7 @@ jobs: core.info(`Processed ${processed} pull requests.`); label-issues: + if: github.repository == 'openclaw/openclaw' || github.repository == 'openclaw-org/openclaw' permissions: issues: write runs-on: ubuntu-24.04 diff --git a/.github/workflows/prodclaw-governance.yml b/.github/workflows/prodclaw-governance.yml new file mode 100644 index 000000000000..e477296e598d --- /dev/null +++ b/.github/workflows/prodclaw-governance.yml @@ -0,0 +1,117 @@ +name: ProdClaw Governance + +on: + pull_request: + types: [opened, edited, synchronize, reopened, ready_for_review] + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + +jobs: + semantic-pr: + name: Validate PR + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Check PR title + id: lint-pr-title + uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Add PR title error comment + uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 + if: always() && (steps.lint-pr-title.outputs.error_message != null) + with: + header: prodclaw-pr-title-lint-error + message: | + ProdClaw PR titles must follow Conventional Commits. + + ``` + ${{ steps.lint-pr-title.outputs.error_message }} + ``` + + - name: Delete PR title error comment + uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 + if: ${{ steps.lint-pr-title.outputs.error_message == null }} + with: + header: prodclaw-pr-title-lint-error + delete: true + + prodclaw-governance: + name: ProdClaw Governance + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - name: Validate ProdClaw boundary docs + shell: bash + run: | + set -euo pipefail + files=( + PRODCLAW.md + docs/reference/prodclaw-release-policy.md + AGENTS.md + CONTRIBUTING.md + PRODCLAW_UPSTREAM.json + .github/workflows/prodclaw-governance.yml + .github/workflows/prodclaw-release.yml + ) + + for f in "${files[@]}"; do + test -f "$f" + done + + # These checks are lightweight tripwires for accidental boundary-doc deletion. + # CODEOWNERS review is still the authoritative governance control. + grep -q "\\*\\*GA\\*\\* is the current production-ready channel" PRODCLAW.md + grep -q "\\*\\*LTS\\*\\* is the conservative channel" PRODCLAW.md + grep -q "at least 10 days old" PRODCLAW.md + grep -q "ProdClaw uses SemVer tags" PRODCLAW.md + + private_pattern='MachineWisdomAI/''iris|iris''-[a-z0-9-]+|azureuser''@|/Users''/|/home/''younes|OPENROUTER_''API_KEY|TELEGRAM_''BOT_TOKEN' + if grep -RInE "$private_pattern" "${files[@]}"; then + echo "ProdClaw governance files must not contain private deployment details." >&2 + exit 1 + fi + + - name: Validate ProdClaw tag policy + shell: bash + run: | + set -euo pipefail + node --input-type=module <<'NODE' + const accepted = [ + "v1.0.0", + "v1.2.3", + "v2.0.0-rc.1", + "v12.34.56-rc.10", + "v1.0.0-rc.10", + ]; + const rejected = [ + "v2026.4.27", + "2026.4.27", + "v1.0", + "v1.0.0-beta.1", + "v0.1.0", + "v1.02.3", + "v1.2.03", + "v1.2.3-rc.01", + ]; + const pattern = /^v(?!\d{4}\.)[1-9]\d*\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-rc\.[1-9]\d*)?$/; + for (const tag of accepted) { + if (!pattern.test(tag)) { + throw new Error(`expected accepted ProdClaw tag: ${tag}`); + } + } + for (const tag of rejected) { + if (pattern.test(tag)) { + throw new Error(`expected rejected ProdClaw tag: ${tag}`); + } + } + NODE diff --git a/.github/workflows/prodclaw-release.yml b/.github/workflows/prodclaw-release.yml new file mode 100644 index 000000000000..a5c3331034e2 --- /dev/null +++ b/.github/workflows/prodclaw-release.yml @@ -0,0 +1,269 @@ +name: ProdClaw Release + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: ProdClaw tag to package, for example v1.2.3 or v1.2.3-rc.1 + required: true + type: string + channel: + description: ProdClaw release channel for this tag + required: true + type: choice + options: + - ga + - lts + +permissions: + contents: write + +concurrency: + group: prodclaw-release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} + cancel-in-progress: false + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + NODE_VERSION: "24.x" + PNPM_VERSION: "10.32.1" + PRODCLAW_UPSTREAM_REMOTE: "https://github.com/openclaw-org/openclaw" + +jobs: + package: + name: Package ProdClaw release + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - name: Resolve release tag + id: release_tag + shell: bash + env: + INPUT_TAG: ${{ inputs.tag || '' }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + tag="${INPUT_TAG:-$REF_NAME}" + if [[ ! "$tag" =~ ^v([1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-rc\.([1-9][0-9]*))?$ ]]; then + echo "Invalid ProdClaw tag: $tag" >&2 + exit 1 + fi + major="${BASH_REMATCH[1]}" + if (( major >= 1000 )); then + echo "ProdClaw versions must not reuse upstream date-version tags: $tag" >&2 + exit 1 + fi + echo "tag=$tag" >> "$GITHUB_OUTPUT" + + - name: Checkout release tag + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ steps.release_tag.outputs.tag }} + fetch-depth: 0 + persist-credentials: false + + - name: Resolve release channel + id: release_channel + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + INPUT_CHANNEL: ${{ inputs.channel || '' }} + TARGET_COMMITISH: ${{ github.event.release.target_commitish || '' }} + run: | + set -euo pipefail + git fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*' + + containing_branches="$(git branch -r --contains HEAD | sed 's/^[ *]*//' || true)" + has_ga=false + has_lts=false + if printf '%s\n' "$containing_branches" | grep -Eq '^(origin/main$|origin/ga/)'; then + has_ga=true + fi + if printf '%s\n' "$containing_branches" | grep -Eq '^origin/lts/'; then + has_lts=true + fi + + channel="" + if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then + case "$INPUT_CHANNEL" in + ga) channel="GA" ;; + lts) channel="LTS" ;; + *) + echo "Invalid ProdClaw release channel: $INPUT_CHANNEL" >&2 + exit 1 + ;; + esac + else + case "$TARGET_COMMITISH" in + main|ga/*) channel="GA" ;; + lts/*) channel="LTS" ;; + ""|v*) + ;; + *) + echo "Release target_commitish must be main, ga/*, lts/*, or a tag whose commit is reachable from one of those branches: $TARGET_COMMITISH" >&2 + exit 1 + ;; + esac + fi + + if [[ -z "$channel" ]]; then + if [[ "$has_ga" == true && "$has_lts" == false ]]; then + channel="GA" + elif [[ "$has_lts" == true && "$has_ga" == false ]]; then + channel="LTS" + else + echo "Unable to infer a single release channel for HEAD." >&2 + echo "Containing branches:" >&2 + printf '%s\n' "$containing_branches" >&2 + exit 1 + fi + fi + + if [[ "$channel" == "GA" && "$has_ga" != true ]]; then + echo "GA releases must be cut from main or ga/*." >&2 + printf '%s\n' "$containing_branches" >&2 + exit 1 + fi + if [[ "$channel" == "LTS" && "$has_lts" != true ]]; then + echo "LTS releases must be cut from lts/*." >&2 + printf '%s\n' "$containing_branches" >&2 + exit 1 + fi + + echo "channel=$channel" >> "$GITHUB_OUTPUT" + + - name: Setup Node environment + uses: ./.github/actions/setup-node-env + with: + node-version: ${{ env.NODE_VERSION }} + pnpm-version: ${{ env.PNPM_VERSION }} + install-bun: "true" + + - name: Verify package version matches tag + shell: bash + env: + TAG: ${{ steps.release_tag.outputs.tag }} + run: | + set -euo pipefail + expected="${TAG#v}" + actual="$(node -p "require('./package.json').version")" + if [[ "$actual" != "$expected" ]]; then + echo "package.json version $actual does not match ProdClaw tag $TAG" >&2 + exit 1 + fi + + - name: Build package + run: pnpm build:strict-smoke + + - name: Pack release artifact + id: pack + shell: bash + env: + TAG: ${{ steps.release_tag.outputs.tag }} + run: | + set -euo pipefail + mkdir -p "$RUNNER_TEMP/prodclaw-release" + pnpm pack --pack-destination "$RUNNER_TEMP/prodclaw-release" + mapfile -t tarballs < <(find "$RUNNER_TEMP/prodclaw-release" -maxdepth 1 -type f -name '*.tgz' -print) + if (( ${#tarballs[@]} != 1 )); then + echo "pnpm pack produced ${#tarballs[@]} tarballs in $RUNNER_TEMP/prodclaw-release; expected exactly one" >&2 + printf '%s\n' "${tarballs[@]}" >&2 + exit 1 + fi + tarball="${tarballs[0]}" + if [[ -z "$tarball" || ! -f "$tarball" ]]; then + echo "pnpm pack did not produce a tarball in $RUNNER_TEMP/prodclaw-release" >&2 + exit 1 + fi + artifact="prodclaw-${TAG#v}.tgz" + mv "$tarball" "$RUNNER_TEMP/prodclaw-release/$artifact" + (cd "$RUNNER_TEMP/prodclaw-release" && sha256sum "$artifact" > "$artifact.sha256") + echo "artifact=$RUNNER_TEMP/prodclaw-release/$artifact" >> "$GITHUB_OUTPUT" + echo "checksum=$RUNNER_TEMP/prodclaw-release/$artifact.sha256" >> "$GITHUB_OUTPUT" + + - name: Write release metadata + id: metadata + shell: bash + env: + TAG: ${{ steps.release_tag.outputs.tag }} + CHANNEL: ${{ steps.release_channel.outputs.channel }} + run: | + set -euo pipefail + metadata="$RUNNER_TEMP/prodclaw-release/prodclaw-release-metadata.json" + node --input-type=module - "$metadata" "$TAG" "$CHANNEL" "$PRODCLAW_UPSTREAM_REMOTE" <<'NODE' + import { execFileSync } from "node:child_process"; + import { readFileSync, writeFileSync } from "node:fs"; + const [outPath, tag, releaseChannel, expectedUpstreamRemote] = process.argv.slice(2); + const pkg = JSON.parse(execFileSync("node", ["-p", "JSON.stringify(require('./package.json'))"], { encoding: "utf8" })); + const upstream = JSON.parse(readFileSync("PRODCLAW_UPSTREAM.json", "utf8")); + if (upstream.remote !== expectedUpstreamRemote) { + throw new Error(`PRODCLAW_UPSTREAM.json remote ${upstream.remote} does not match expected ${expectedUpstreamRemote}`); + } + const requiredUpstreamStrings = ["packageVersion", "tag", "commit", "releaseDate"]; + for (const key of requiredUpstreamStrings) { + if (typeof upstream[key] !== "string" || upstream[key].length === 0) { + throw new Error(`PRODCLAW_UPSTREAM.json must include upstream ${key}`); + } + } + if (upstream.tag !== `v${upstream.packageVersion}`) { + throw new Error(`PRODCLAW_UPSTREAM.json tag ${upstream.tag} must match packageVersion ${upstream.packageVersion}`); + } + if (!/^[0-9a-f]{40}$/i.test(upstream.commit)) { + throw new Error(`PRODCLAW_UPSTREAM.json commit must be a 40-character Git SHA: ${upstream.commit}`); + } + if (Number.isNaN(Date.parse(upstream.releaseDate))) { + throw new Error(`PRODCLAW_UPSTREAM.json releaseDate must be an ISO date: ${upstream.releaseDate}`); + } + if (!["GA", "LTS"].includes(releaseChannel)) { + throw new Error(`invalid releaseChannel ${releaseChannel}`); + } + const upstreamAgeDays = Math.floor((Date.now() - Date.parse(upstream.releaseDate)) / 86_400_000); + if (releaseChannel === "GA" && upstreamAgeDays < 10) { + throw new Error(`upstream release is only ${upstreamAgeDays} days old; GA requires at least 10 days`); + } + const git = (...args) => execFileSync("git", args, { encoding: "utf8" }).trim(); + const metadata = { + product: "ProdClaw", + tag, + version: pkg.version, + sourceSha: git("rev-parse", "HEAD"), + upstreamOpenClaw: upstream, + releaseChannel, + }; + writeFileSync(outPath, `${JSON.stringify(metadata, null, 2)}\n`); + NODE + echo "metadata=$metadata" >> "$GITHUB_OUTPUT" + + - name: Upload release assets + if: ${{ github.event_name == 'release' }} + shell: bash + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.release_tag.outputs.tag }} + ARTIFACT: ${{ steps.pack.outputs.artifact }} + CHECKSUM: ${{ steps.pack.outputs.checksum }} + METADATA: ${{ steps.metadata.outputs.metadata }} + run: | + set -euo pipefail + release_assets="$(gh release view "$TAG" --json assets --jq '.assets[].name')" + for path in "$ARTIFACT" "$CHECKSUM" "$METADATA"; do + asset="$(basename "$path")" + if printf '%s\n' "$release_assets" | grep -Fxq "$asset"; then + echo "Release asset already exists and will not be overwritten: $asset" >&2 + exit 1 + fi + done + # TODO: attach SLSA provenance or sigstore signing here once available. + # See docs/reference/prodclaw-release-policy.md "Release Gate" section. + gh release upload "$TAG" "$ARTIFACT" "$CHECKSUM" "$METADATA" + # TODO: attach SLSA provenance or sigstore signing here once available. + # See docs/reference/prodclaw-release-policy.md "Release Gate" section. + - name: Upload workflow artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: prodclaw-release-${{ steps.release_tag.outputs.tag }} + path: | + ${{ steps.pack.outputs.artifact }} + ${{ steps.pack.outputs.checksum }} + ${{ steps.metadata.outputs.metadata }} diff --git a/AGENTS.md b/AGENTS.md index 96459cd7d87c..8270be7bc8cc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,14 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work. ## Start -- Repo: `https://github.com/openclaw/openclaw` +- Repo: `https://github.com/MachineWisdomAI/ProdClaw` +- Downstream: ProdClaw is a production-stability downstream of OpenClaw. + Keep changes generic to the runtime; no private deployment product, + customer, host, tenant, OAuth bridge, or fleet-operation details belong here. +- Upstream: `https://github.com/openclaw-org/openclaw`. Intake upstream only + through explicit review PRs; do not auto-merge upstream. +- Releases: ProdClaw uses GA and LTS maturity channels with SemVer tags. Do not + ship OpenClaw date-versioned tags as ProdClaw releases. - Replies: repo-root refs only: `extensions/telegram/src/index.ts:80`. No absolute paths, no `~/`. - Run docs list first: `pnpm docs:list` if available; read relevant docs only. - High-confidence answers only when fixing/triaging: verify source, tests, shipped/current behavior, and dependency contracts before deciding. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 433a2c5bb921..aa3cde27691b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,27 @@ -# Contributing to OpenClaw +# Contributing to ProdClaw -Welcome to the lobster tank! 🦞 +ProdClaw is MachineWisdom's production-stability downstream of OpenClaw. +Contributions should keep that boundary clear: generic runtime stability, +release maturity, and safer defaults belong here; private deployment products, +customer-specific policy, fleet topology, hostnames, tenant names, and private +bridges do not. + +ProdClaw follows two release channels: + +- **GA**: production-ready releases cut at most every two weeks from upstream + OpenClaw releases that are at least 10 days old. +- **LTS**: quarterly conservative releases promoted from proven GA releases, + with security and critical regression backports only. + +ProdClaw uses SemVer tags (`vMAJOR.MINOR.PATCH` and +`vMAJOR.MINOR.PATCH-rc.N`). Do not use upstream OpenClaw date-version tags for +ProdClaw releases. + +The upstream OpenClaw contribution notes below are retained for codebase +orientation, but ProdClaw PRs are reviewed against the downstream release and +stability policy in [`PRODCLAW.md`](PRODCLAW.md). + +## Upstream OpenClaw Notes ## Quick Links diff --git a/PRODCLAW.md b/PRODCLAW.md new file mode 100644 index 000000000000..8e4b9fe09e2c --- /dev/null +++ b/PRODCLAW.md @@ -0,0 +1,81 @@ +# ProdClaw + +ProdClaw is a production-stability downstream of OpenClaw. It exists to make +OpenClaw usable for production operators who need slower intake, safer defaults, +repeatable releases, and explicit maturity channels. + +ProdClaw is not a private deployment product. Keep customer-specific operations, +private bridges, fleet topology, hostnames, tenant names, and managed-service +runbooks out of this repository. + +## Boundary + +ProdClaw owns generic runtime stability: + +- curated upstream intake from OpenClaw; +- safer default behavior for customer-visible channels; +- source-level review of config schema and default changes; +- package acceptance and release provenance; +- GA and LTS release channels. + +Private deployments built on ProdClaw own their own infrastructure: + +- cloud hosts, images, bastions, and fleet rollout; +- tenant lifecycle and isolation; +- private OAuth/account bridges; +- customer-specific prompts, policies, and support workflows; +- operational monitoring and incident runbooks. + +If a change names a private deployment, customer, host, or bridge, it belongs in +that deployment repository, not in ProdClaw. If a change makes OpenClaw safer or +more predictable for any production operator, it can belong here. + +## Release Channels + +ProdClaw has two maturity channels. + +**GA** is the current production-ready channel. GA releases are cut at most every +two weeks, and only from upstream OpenClaw releases that are at least 10 days +old. A GA release may be delayed when community signal or local evidence shows +regression risk. + +**LTS** is the conservative channel. LTS releases are promoted quarterly from a +proven GA release and receive only security fixes and critical regression +backports. LTS is for operators who value stability over new OpenClaw features. + +ProdClaw uses SemVer tags: + +- `vMAJOR.MINOR.PATCH` for GA and LTS releases; +- `vMAJOR.MINOR.PATCH-rc.N` for release candidates. + +ProdClaw release tags start at major version 1. Do not use upstream OpenClaw +date versions as ProdClaw versions. Record upstream OpenClaw provenance in +release metadata instead. Keep `PRODCLAW_UPSTREAM.json` current so release +artifacts carry the upstream package version that the ProdClaw release is based +on. + +## Upstream Intake + +Every upstream intake starts as an explicit PR. The PR must include: + +- upstream OpenClaw version, tag, commit, and release date; +- confirmation that the upstream release is at least 10 days old; +- changelog summary and source diff summary; +- config schema/default diff for high-risk runtime surfaces; +- package contents diff; +- community regression scan; +- GA or LTS impact statement. + +High-risk surfaces include channels, delivery, streaming, tools, commands, +config mutation, gateway restart/update, cron, sessions, plugins, MCP, and +credentials. + +## Hardening Defaults + +ProdClaw should prefer safe defaults for production-visible behavior. New +customer-visible output, tool progress, raw tool-call details, channel config +writes, restart controls, and operator command surfaces should be opt-in unless +there is a reviewed production reason to expose them by default. + +Any risky boolean defaulting to `true` needs an explicit allowlist entry, tests, +and reviewer justification. diff --git a/PRODCLAW_UPSTREAM.json b/PRODCLAW_UPSTREAM.json new file mode 100644 index 000000000000..797c6995e49e --- /dev/null +++ b/PRODCLAW_UPSTREAM.json @@ -0,0 +1,7 @@ +{ + "remote": "https://github.com/openclaw-org/openclaw", + "packageVersion": "2026.4.27", + "tag": "v2026.4.27", + "commit": "cbc2ba0931468259f26a7c547131a06e03ca6c6c", + "releaseDate": "2026-04-29T22:28:04.240Z" +} diff --git a/README.md b/README.md index 186952c852bc..6ca720472975 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ +# ProdClaw — Production-Stability Downstream of OpenClaw + +ProdClaw is MachineWisdom's production-stability downstream of OpenClaw. It +keeps the OpenClaw runtime model while adding slower upstream intake, safer +release maturity, and GA/LTS release channels for production operators. + +This repository should stay generic. Private deployment products, customer +policy, fleet topology, hostnames, tenant names, and private bridge details +belong outside ProdClaw. + +See [`PRODCLAW.md`](PRODCLAW.md) and +[`docs/reference/prodclaw-release-policy.md`](docs/reference/prodclaw-release-policy.md) +for the downstream boundary and release policy. + +--- + # 🦞 OpenClaw — Personal AI Assistant

diff --git a/docs/reference/prodclaw-release-policy.md b/docs/reference/prodclaw-release-policy.md new file mode 100644 index 000000000000..fcb50c0bc536 --- /dev/null +++ b/docs/reference/prodclaw-release-policy.md @@ -0,0 +1,73 @@ +--- +summary: "ProdClaw GA and LTS release maturity policy" +title: "ProdClaw release policy" +read_when: + - Planning a ProdClaw GA or LTS release + - Reviewing upstream OpenClaw intake for production risk + - Checking ProdClaw version and release channel rules +--- + +ProdClaw is a production-stability downstream of OpenClaw. It uses explicit +release maturity channels instead of tracking OpenClaw's daily release stream. + +## Channels + +**GA** is the current production-ready channel. + +- GA releases are cut at most every two weeks. +- GA candidates must be based on an upstream OpenClaw release at least 10 days + old. +- GA may be delayed when community reports, local tests, or package acceptance + show regression risk. + +**LTS** is the conservative channel. + +- LTS releases are promoted quarterly from a proven GA release. +- LTS receives security fixes and critical regression backports. +- LTS should avoid feature intake unless the feature is required to fix a + production regression. + +## Versioning + +ProdClaw uses SemVer: + +- GA and LTS tag: `vMAJOR.MINOR.PATCH` +- Release candidate tag: `vMAJOR.MINOR.PATCH-rc.N` + +ProdClaw release tags start at major version 1. Do not use upstream OpenClaw +date versions as ProdClaw release versions. The upstream OpenClaw version, tag, +commit, and release date belong in release metadata. Keep +`PRODCLAW_UPSTREAM.json` current so packaged release metadata can distinguish +ProdClaw's SemVer package version from the upstream OpenClaw package version it +is based on. + +## Intake Gate + +Each upstream intake PR must include: + +- upstream OpenClaw version, tag, commit, and release date; +- proof that the upstream release is at least 10 days old; +- changelog summary; +- source diff summary; +- config schema/default diff; +- package contents diff; +- community regression scan; +- GA or LTS impact statement. + +Review high-risk surfaces first: customer channels, delivery, streaming, tools, +commands, config mutation, gateway restart/update, cron, sessions, plugins, MCP, +and credentials. + +## Release Gate + +A release must pass: + +- build and focused runtime tests; +- config schema/default snapshot review; +- package acceptance from the produced tarball; +- release metadata generation; +- artifact checksum generation. + +Release artifacts must be built from a clean checkout and attached to the +GitHub Release with checksums. Provenance or attestation should be attached when +available. diff --git a/src/commands/doctor-state-integrity.ts b/src/commands/doctor-state-integrity.ts index 423743ecc94f..ed31eebaefad 100644 --- a/src/commands/doctor-state-integrity.ts +++ b/src/commands/doctor-state-integrity.ts @@ -22,7 +22,6 @@ import { } from "../config/sessions/paths.js"; import { loadSessionStore } from "../config/sessions/store-load.js"; import { updateSessionStore } from "../config/sessions/store.js"; -import type { SessionEntry } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { resolveRequiredHomeDir } from "../infra/home-dir.js"; import { resolveMemoryBackendConfig } from "../memory-host-sdk/engine-storage.js"; @@ -872,7 +871,7 @@ export async function noteStateIntegrity( const wedgedSubagentSessions = entries.filter(([, entry]) => isSubagentRecoveryWedgedEntry(entry), - ) as Array<[string, SessionEntry]>; + ); if (wedgedSubagentSessions.length > 0) { const wedgedCount = countLabel(wedgedSubagentSessions.length, "wedged subagent session"); warnings.push(