Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
45 KiB
JSON
1 line
45 KiB
JSON
{"repo": "apet97/ai-assistant-clockify", "files": {".github/workflows/release-evidence.yml": {"ac1b4844f903bb76649a1af7bae4b528b6ecfbe6": {"t": 1786068429.6134984, "n": 44779, "h": "cae8100c3d7a5c39", "c": "name: Release evidence (manual)\n\non:\n workflow_dispatch:\n inputs:\n tested_candidate_sha:\n description: Exact clean source commit used for the DeepSeek candidate corpus and deployment\n required: true\n type: string\n deployed_base_url:\n description: Public HTTPS base URL of the deployed private release candidate\n required: true\n type: string\n reviewed_pr_number:\n description: Reviewed pull request number whose exact head is this evidence commit\n required: true\n type: string\n reviewed_pr_ci_run_id:\n description: First-attempt successful CI workflow run id for the reviewed PR head\n required: true\n type: string\n reviewed_pr_codeql_run_id:\n description: First-attempt successful CodeQL workflow run id for the reviewed PR head\n required: true\n type: string\n\npermissions:\n actions: read\n checks: read\n contents: read\n pull-requests: read\n\nconcurrency:\n group: clockify-live-smoke-sacrificial\n cancel-in-progress: false\n\njobs:\n machine-gates:\n runs-on: ubuntu-latest\n timeout-minutes: 45\n outputs:\n verify: ${{ steps.verify.outcome }}\n cold-verifies: ${{ steps.verify.outputs.cold_verifies }}\n reviewed-pr: ${{ steps.reviewed-pr.outcome }}\n reviewed-pr-evidence: ${{ steps.reviewed-pr.outputs.reviewed_pr_evidence }}\n local-ui-performance: ${{ steps.local-ui-performance.outcome }}\n actionlint: ${{ steps.actionlint.outcome }}\n marketplace_media_binding: ${{ steps.marketplace_media_binding.outcome }}\n audit: ${{ steps.audit.outcome }}\n license: ${{ steps.license.outcome }}\n scripted-safety-corpus: ${{ steps.scripted-safety-corpus.outcome }}\n browser-e2e: ${{ steps.browser-e2e.outcome }}\n sbom: ${{ steps.sbom.outcome }}\n steps:\n - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n with:\n fetch-depth: 0\n - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n with:\n node-version: 22\n cache: npm\n - id: install\n continue-on-error: true\n run: npm ci\n - id: reviewed-pr\n name: Bind approved PR head, CI, dependency review, CodeQL, gitleaks, and review resolution\n if: steps.install.outcome == 'success'\n continue-on-error: true\n env:\n GITHUB_TOKEN: ${{ github.token }}\n RELEASE_SOURCE_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n RELEASE_EVIDENCE_COMMIT_SHA: ${{ github.sha }}\n REVIEWED_PR_NUMBER: ${{ inputs.reviewed_pr_number }}\n REVIEWED_PR_CI_RUN_ID: ${{ inputs.reviewed_pr_ci_run_id }}\n REVIEWED_PR_CODEQL_RUN_ID: ${{ inputs.reviewed_pr_codeql_run_id }}\n REVIEWED_PR_EVIDENCE_PATH: /tmp/ai-assistant-release-evidence/reviewed-pr.json\n run: npm run check:reviewed-pr-evidence\n - id: verify\n if: steps.install.outcome == 'success'\n continue-on-error: true\n shell: bash\n env:\n TESTED_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n VERIFY_EVIDENCE_PATH: /tmp/ai-assistant-release-evidence/cold-verifies.json\n run: |\n set -euo pipefail\n evidence_sha=\"$(git rev-parse HEAD)\"\n git merge-base --is-ancestor \"${TESTED_CANDIDATE_SHA}\" \"${evidence_sha}\"\n restore_evidence_head() {\n git checkout --detach \"${evidence_sha}\" >/dev/null\n }\n trap restore_evidence_head EXIT\n git checkout --detach \"${TESTED_CANDIDATE_SHA}\"\n test -z \"$(git status --porcelain --untracked-files=all)\"\n report_dir=\"/tmp/ai-assistant-release-evidence\"\n mkdir -p \"${report_dir}\"\n COLD_VERIFY_STARTED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n export COLD_VERIFY_STARTED_AT\n for pass in 1 2 3; do\n echo \"Cold verification pass ${pass}/3\"\n rm -rf -- dist\n VITEST_RELEASE_REPORT_PATH=\"${report_dir}/vitest-pass-${pass}.json\"\n export VITEST_RELEASE_REPORT_PATH\n rm -f -- \"${VITEST_RELEASE_REPORT_PATH}\"\n npm run verify\n test -s \"${VITEST_RELEASE_REPORT_PATH}\"\n done\n COLD_VERIFY_COMPLETED_AT=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n RELEASE_SOURCE_CANDIDATE_SHA=\"${TESTED_CANDIDATE_SHA}\"\n RELEASE_EVIDENCE_COMMIT_SHA=\"${evidence_sha}\"\n VITEST_RELEASE_REPORT_1=\"${report_dir}/vitest-pass-1.json\"\n VITEST_RELEASE_REPORT_2=\"${report_dir}/vitest-pass-2.json\"\n VITEST_RELEASE_REPORT_3=\"${report_dir}/vitest-pass-3.json\"\n export COLD_VERIFY_COMPLETED_AT RELEASE_SOURCE_CANDIDATE_SHA RELEASE_EVIDENCE_COMMIT_SHA\n export VITEST_RELEASE_REPORT_1 VITEST_RELEASE_REPORT_2 VITEST_RELEASE_REPORT_3\n npm run record:cold-verifies\n node <<'NODE'\n const { readFileSync } = require(\"node:fs\");\n const evidence = JSON.parse(readFileSync(process.env.VERIFY_EVIDENCE_PATH, \"utf8\"));\n if (\n evidence.minimumPassedTests !== 5351\n || evidence.consecutiveColdPasses !== 3\n || evidence.retries !== 0\n || evidence.passes?.length !== 3\n || evidence.passes.some((pass) =>\n pass.passedTests < 5351\n || pass.passedTests !== pass.totalTests\n || pass.failedTests !== 0\n || pass.pendingTests !== 0\n || pass.todoTests !== 0)\n ) throw new Error(\"cold verify count evidence is incomplete\");\n NODE\n - id: local-ui-performance\n name: Exact-candidate local UI and asset performance gate\n if: steps.verify.outcome == 'success'\n continue-on-error: true\n shell: bash\n env:\n TESTED_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n PERF_EVIDENCE_DIR: /tmp/ai-assistant-local-ui-performance\n run: |\n set -euo pipefail\n evidence_sha=\"$(git rev-parse HEAD)\"\n restore_evidence_head() {\n git checkout --detach \"${evidence_sha}\" >/dev/null\n }\n trap restore_evidence_head EXIT\n git checkout --detach \"${TESTED_CANDIDATE_SHA}\"\n test -z \"$(git status --porcelain --untracked-files=all)\"\n npm run perf:local-ui\n node <<'NODE'\n const { readFileSync } = require(\"node:fs\");\n const path = `${process.env.PERF_EVIDENCE_DIR}/local-ui-2026-07-18.json`;\n const evidence = JSON.parse(readFileSync(path, \"utf8\"));\n if (\n evidence.conclusion !== \"passed\"\n || evidence.source?.commitSha !== process.env.TESTED_CANDIDATE_SHA\n || evidence.source?.workingTreeDirty !== false\n || evidence.metrics?.uiAssets?.passed !== true\n || evidence.metrics?.statusFeedback?.passed !== true\n ) throw new Error(\"exact-candidate local UI performance evidence failed\");\n NODE\n - id: actionlint\n name: Validate GitHub Actions workflows\n if: steps.verify.outcome == 'success'\n continue-on-error: true\n uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2\n with:\n version: 1.7.7\n - id: marketplace_media_binding\n name: Bind reviewed Marketplace media to the tested source candidate\n if: steps.verify.outcome == 'success'\n continue-on-error: true\n env:\n MARKETPLACE_MEDIA_SOURCE_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n MARKETPLACE_MEDIA_EVIDENCE_COMMIT_SHA: ${{ github.sha }}\n MARKETPLACE_MEDIA_BINDING_PATH: /tmp/marketplace-media-release-binding.json\n run: npm run evidence:marketplace-media-binding\n - id: audit\n if: steps.install.outcome == 'success'\n continue-on-error: true\n run: npm run audit:prod\n - id: license\n if: steps.install.outcome == 'success'\n continue-on-error: true\n run: npm run license:prod\n - id: scripted-safety-corpus\n if: steps.install.outcome == 'success'\n continue-on-error: true\n run: npm run eval:smoke\n - id: playwright-install\n if: steps.install.outcome == 'success'\n continue-on-error: true\n run: npx playwright install --with-deps chromium firefox webkit\n - id: browser-e2e\n if: steps.playwright-install.outcome == 'success'\n continue-on-error: true\n run: npm run test:e2e\n - id: sbom\n if: steps.install.outcome == 'success'\n continue-on-error: true\n run: npm sbom --sbom-format cyclonedx > sbom.cdx.json\n - name: Upload release supply-chain evidence\n if: ${{ always() }}\n uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n with:\n name: release-supply-chain-evidence-${{ github.run_id }}\n path: |\n sbom.cdx.json\n evidence/dependency-gates/production-licenses.json\n /tmp/marketplace-media-release-binding.json\n /tmp/ai-assistant-release-evidence/cold-verifies.json\n /tmp/ai-assistant-release-evidence/vitest-pass-*.json\n /tmp/ai-assistant-release-evidence/reviewed-pr.json\n /tmp/ai-assistant-local-ui-performance/local-ui-2026-07-18.json\n /tmp/ai-assistant-local-ui-performance/local-ui-2026-07-18.md\n if-no-files-found: warn\n retention-days: 30\n - name: Enforce machine gates\n if: ${{ always() }}\n env:\n INSTALL: ${{ steps.install.outcome }}\n VERIFY: ${{ steps.verify.outcome }}\n REVIEWED_PR: ${{ steps.reviewed-pr.outcome }}\n LOCAL_UI_PERFORMANCE: ${{ steps.local-ui-performance.outcome }}\n ACTIONLINT: ${{ steps.actionlint.outcome }}\n MARKETPLACE_MEDIA_BINDING: ${{ steps.marketplace_media_binding.outcome }}\n AUDIT: ${{ steps.audit.outcome }}\n LICENSE: ${{ steps.license.outcome }}\n SCRIPTED_SAFETY_CORPUS: ${{ steps.scripted-safety-corpus.outcome }}\n BROWSER_E2E: ${{ steps.browser-e2e.outcome }}\n SBOM: ${{ steps.sbom.outcome }}\n run: |\n node <<'NODE'\n const gates = [\"INSTALL\", \"VERIFY\", \"REVIEWED_PR\", \"LOCAL_UI_PERFORMANCE\", \"ACTIONLINT\", \"MARKETPLACE_MEDIA_BINDING\", \"AUDIT\", \"LICENSE\", \"SCRIPTED_SAFETY_CORPUS\", \"BROWSER_E2E\", \"SBOM\"];\n const failed = gates.filter((gate) => process.env[gate] !== \"success\");\n if (failed.length > 0) {\n console.error(`Machine gates not successful: ${failed.join(\", \")}`);\n process.exit(1);\n }\n NODE\n\n codeql:\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n security-events: write\n steps:\n - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n - uses: github/codeql-action/init@f411752efdf656cb71aa17b755b22c890960da1d # v3.35.5\n with:\n languages: javascript-typescript\n - uses: github/codeql-action/analyze@f411752efdf656cb71aa17b755b22c890960da1d # v3.35.5\n\n secret-scan:\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n steps:\n - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n with:\n fetch-depth: 0\n - uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9\n env:\n GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n deepseek-evidence:\n runs-on: ubuntu-latest\n timeout-minutes: 45\n environment: clockify-deepseek-release\n permissions:\n attestations: write\n contents: read\n id-token: write\n env:\n EVAL_RELEASE_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n LLM_PROVIDER: http\n LLM_BASE_URL: https://api.deepseek.com\n LLM_API_KEY: ${{ secrets.DEEPSEEK_RELEASE_API_KEY }}\n LLM_MODEL: deepseek-v4-pro\n LLM_MODE: tool\n LLM_AGENTIC: \"1\"\n LLM_TOOL_SELECT: \"1\"\n DEEPSEEK_LIVE_EVIDENCE_DIR: /tmp/deepseek-live-release\n steps:\n - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n with:\n fetch-depth: 0\n - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n with:\n node-version: 22\n cache: npm\n - run: npm ci\n - name: Run source-bound current DeepSeek capability and safety evaluations\n shell: bash\n run: |\n set -euo pipefail\n evidence_sha=\"$(git rev-parse HEAD)\"\n git merge-base --is-ancestor \"${EVAL_RELEASE_CANDIDATE_SHA}\" \"${evidence_sha}\"\n mkdir -p \"${DEEPSEEK_LIVE_EVIDENCE_DIR}\"\n chmod 700 \"${DEEPSEEK_LIVE_EVIDENCE_DIR}\"\n restore_evidence_head() {\n git checkout --detach \"${evidence_sha}\" >/dev/null\n }\n trap restore_evidence_head EXIT\n git checkout --detach \"${EVAL_RELEASE_CANDIDATE_SHA}\"\n test -z \"$(git status --porcelain --untracked-files=all)\"\n\n export DEEPSEEK_CAPABILITY_PROBE_RAW_PATH=\"${DEEPSEEK_LIVE_EVIDENCE_DIR}/deepseek-capability-probe.raw.json\"\n export DEEPSEEK_BASELINE_RAW_PATH=\"${DEEPSEEK_LIVE_EVIDENCE_DIR}/deepseek-baseline.raw.json\"\n export DEEPSEEK_CANDIDATE_RAW_PATH=\"${DEEPSEEK_LIVE_EVIDENCE_DIR}/deepseek-candidate.raw.json\"\n export DEEPSEEK_FOCUSED_READ_RAW_PATH=\"${DEEPSEEK_LIVE_EVIDENCE_DIR}/deepseek-focused-read.raw.json\"\n export DEEPSEEK_FOCUSED_RISKY_PREVIEW_RAW_PATH=\"${DEEPSEEK_LIVE_EVIDENCE_DIR}/deepseek-focused-risky-preview.raw.json\"\n export DEEPSEEK_BINDING_PATH=\"${DEEPSEEK_LIVE_EVIDENCE_DIR}/deepseek-release-binding.json\"\n\n npx tsx scripts/eval/probe-deepseek-settings.ts \\\n --out=\"${DEEPSEEK_CAPABILITY_PROBE_RAW_PATH}\"\n env -u LLM_THINKING_MODE -u EVAL_DEEPSEEK_THINKING_MODE \\\n npx tsx scripts/eval-agentic.ts --repeat=5 --tool-select --concurrency=4 \\\n --out=\"${DEEPSEEK_BASELINE_RAW_PATH}\"\n\n set +e\n env LLM_THINKING_MODE=disabled EVAL_DEEPSEEK_THINKING_MODE=disabled \\\n npx tsx scripts/eval-agentic.ts --repeat=5 --tool-select --concurrency=4 \\\n --out=\"${DEEPSEEK_CANDIDATE_RAW_PATH}\"\n lower_effort_status=\"$?\"\n set -e\n\n if [[ \"${lower_effort_status}\" -ne 0 && \"${lower_effort_status}\" -ne 1 ]]; then\n echo \"Lower-effort DeepSeek evaluation failed before producing a classifiable corpus\" >&2\n exit \"${lower_effort_status}\"\n fi\n test -s \"${DEEPSEEK_CANDIDATE_RAW_PATH}\"\n export DEEPSEEK_CANDIDATE_EXIT_STATUS=\"${lower_effort_status}\"\n selected_setting=\"$(\n npx tsx scripts/evidence/deepseek-release-evidence.ts --select-setting\n )\"\n case \"${selected_setting}\" in\n production-default) ;;\n thinking-disabled) ;;\n *)\n echo \"DeepSeek selector returned an invalid setting\" >&2\n exit 1\n ;;\n esac\n if [[ \"${lower_effort_status}\" -eq 1 && \"${selected_setting}\" != \"production-default\" ]]; then\n echo \"A failed lower-effort corpus may only fall back to production-default\" >&2\n exit 1\n fi\n\n case \"${selected_setting}\" in\n production-default)\n env -u LLM_THINKING_MODE -u EVAL_DEEPSEEK_THINKING_MODE \\\n npx tsx scripts/eval-agentic.ts --repeat=20 --only=agentic.count_projects \\\n --tool-select --concurrency=4 \\\n --out=\"${DEEPSEEK_FOCUSED_READ_RAW_PATH}\"\n env -u LLM_THINKING_MODE -u EVAL_DEEPSEEK_THINKING_MODE \\\n npx tsx scripts/eval-agentic.ts --repeat=20 --only=agentic.delete_tag_by_name \\\n --preview-only --tool-select --concurrency=4 \\\n --out=\"${DEEPSEEK_FOCUSED_RISKY_PREVIEW_RAW_PATH}\"\n ;;\n thinking-disabled)\n env LLM_THINKING_MODE=disabled EVAL_DEEPSEEK_THINKING_MODE=disabled \\\n npx tsx scripts/eval-agentic.ts --repeat=20 --only=agentic.count_projects \\\n --tool-select --concurrency=4 \\\n --out=\"${DEEPSEEK_FOCUSED_READ_RAW_PATH}\"\n env LLM_THINKING_MODE=disabled EVAL_DEEPSEEK_THINKING_MODE=disabled \\\n npx tsx scripts/eval-agentic.ts --repeat=20 --only=agentic.delete_tag_by_name \\\n --preview-only --tool-select --concurrency=4 \\\n --out=\"${DEEPSEEK_FOCUSED_RISKY_PREVIEW_RAW_PATH}\"\n ;;\n esac\n\n npm run --silent bind:deepseek-evidence\n test -z \"$(git status --porcelain --untracked-files=all)\"\n restore_evidence_head\n - name: Fetch deployed release and DeepSeek configuration\n env:\n DEPLOYED_BASE_URL: ${{ inputs.deployed_base_url }}\n run: |\n node <<'NODE'\n const { writeFileSync } = require(\"node:fs\");\n const raw = process.env.DEPLOYED_BASE_URL ?? \"\";\n const base = new URL(raw.endsWith(\"/\") ? raw : `${raw}/`);\n if (base.protocol !== \"https:\" || base.username || base.password || base.search || base.hash) {\n throw new Error(\"deployed_base_url must be a credential-free HTTPS base URL\");\n }\n async function main() {\n const response = await fetch(new URL(\"version\", base), { redirect: \"error\" });\n if (!response.ok) throw new Error(`deployed /version returned ${response.status}`);\n const text = await response.text();\n if (Buffer.byteLength(text) > 65_536) throw new Error(\"deployed /version response is too large\");\n JSON.parse(text);\n writeFileSync(`${process.env.DEEPSEEK_LIVE_EVIDENCE_DIR}/deployed-version.json`, text, { encoding: \"utf8\", mode: 0o600 });\n }\n main().catch((error) => { console.error(error.message); process.exit(1); });\n NODE\n - name: Validate exact-candidate DeepSeek evidence and deployed settings\n env:\n DEEPSEEK_EVIDENCE_COMMIT_SHA: ${{ github.sha }}\n DEEPSEEK_EXPECTED_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n DEEPSEEK_CAPABILITY_PROBE_RAW_PATH: /tmp/deepseek-live-release/deepseek-capability-probe.raw.json\n DEEPSEEK_BASELINE_RAW_PATH: /tmp/deepseek-live-release/deepseek-baseline.raw.json\n DEEPSEEK_CANDIDATE_RAW_PATH: /tmp/deepseek-live-release/deepseek-candidate.raw.json\n DEEPSEEK_FOCUSED_READ_RAW_PATH: /tmp/deepseek-live-release/deepseek-focused-read.raw.json\n DEEPSEEK_FOCUSED_RISKY_PREVIEW_RAW_PATH: /tmp/deepseek-live-release/deepseek-focused-risky-preview.raw.json\n DEEPSEEK_BINDING_PATH: /tmp/deepseek-live-release/deepseek-release-binding.json\n DEEPSEEK_DEPLOYED_VERSION_PATH: /tmp/deepseek-live-release/deployed-version.json\n DEEPSEEK_VALIDATION_PATH: /tmp/deepseek-live-release/deepseek-release-validation.json\n run: npm run check:deepseek-evidence\n - name: Attest the protected live DeepSeek release evidence\n uses: actions/attest-build-provenance@96b4a1ef7235a096b17240c259729fdd70c83d45 # v2\n with:\n subject-path: /tmp/deepseek-live-release/*.json\n - name: Upload DeepSeek release validation\n uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n with:\n name: release-deepseek-evidence-${{ github.run_id }}\n path: /tmp/deepseek-live-release/*.json\n if-no-files-found: error\n retention-days: 90\n\n private-production-evidence:\n runs-on: ubuntu-latest\n timeout-minutes: 15\n steps:\n - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n with:\n fetch-depth: 0\n - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n with:\n node-version: 22\n cache: npm\n - run: npm ci\n - name: Fetch exact deployed release identity\n env:\n DEPLOYED_BASE_URL: ${{ inputs.deployed_base_url }}\n run: |\n node <<'NODE'\n const { writeFileSync } = require(\"node:fs\");\n const raw = process.env.DEPLOYED_BASE_URL ?? \"\";\n const base = new URL(raw.endsWith(\"/\") ? raw : `${raw}/`);\n if (base.protocol !== \"https:\" || base.username || base.password || base.search || base.hash) {\n throw new Error(\"deployed_base_url must be a credential-free HTTPS base URL\");\n }\n async function main() {\n const response = await fetch(new URL(\"version\", base), { redirect: \"error\" });\n if (!response.ok) throw new Error(`deployed /version returned ${response.status}`);\n const text = await response.text();\n if (Buffer.byteLength(text) > 65_536) throw new Error(\"deployed /version response is too large\");\n JSON.parse(text);\n writeFileSync(\"/tmp/private-production-deployed-version.json\", text, { encoding: \"utf8\", mode: 0o600 });\n }\n main().catch((error) => { console.error(error.message); process.exit(1); });\n NODE\n - name: Validate committed private-production performance evidence\n env:\n PRIVATE_PRODUCTION_EXPECTED_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n PRIVATE_PRODUCTION_EVIDENCE_COMMIT_SHA: ${{ github.sha }}\n PRIVATE_PRODUCTION_DEPLOYED_VERSION_PATH: /tmp/private-production-deployed-version.json\n PRIVATE_PRODUCTION_VALIDATION_PATH: /tmp/private-production-release-validation.json\n run: npm run check:private-production-evidence\n - name: Upload private-production release validation and aggregate evidence\n uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n with:\n name: release-private-production-evidence-${{ github.run_id }}\n path: |\n /tmp/private-production-release-validation.json\n evidence/performance/private-production.json\n evidence/performance/private-production.md\n if-no-files-found: error\n retention-days: 90\n\n live-browser-evidence:\n runs-on: ubuntu-latest\n timeout-minutes: 15\n steps:\n - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n with:\n fetch-depth: 0\n - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n with:\n node-version: 22\n cache: npm\n - run: npm ci\n - name: Fetch exact deployed release identity\n env:\n DEPLOYED_BASE_URL: ${{ inputs.deployed_base_url }}\n run: |\n node <<'NODE'\n const { writeFileSync } = require(\"node:fs\");\n const raw = process.env.DEPLOYED_BASE_URL ?? \"\";\n const base = new URL(raw.endsWith(\"/\") ? raw : `${raw}/`);\n if (base.protocol !== \"https:\" || base.username || base.password || base.search || base.hash) {\n throw new Error(\"deployed_base_url must be a credential-free HTTPS base URL\");\n }\n async function main() {\n const response = await fetch(new URL(\"version\", base), { redirect: \"error\" });\n if (!response.ok) throw new Error(`deployed /version returned ${response.status}`);\n const text = await response.text();\n if (Buffer.byteLength(text) > 65_536) throw new Error(\"deployed /version response is too large\");\n JSON.parse(text);\n writeFileSync(\"/tmp/live-browser-deployed-version.json\", text, { encoding: \"utf8\", mode: 0o600 });\n }\n main().catch((error) => { console.error(error.message); process.exit(1); });\n NODE\n - name: Validate exact-candidate production browser and member-denial evidence\n env:\n LIVE_BROWSER_EXPECTED_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n LIVE_BROWSER_EVIDENCE_COMMIT_SHA: ${{ github.sha }}\n LIVE_BROWSER_TRACE_PATH: evidence/operations/production-browser-trace.json\n LIVE_BROWSER_DEPLOYED_VERSION_PATH: /tmp/live-browser-deployed-version.json\n LIVE_BROWSER_VALIDATION_PATH: /tmp/live-browser-acceptance-validation.json\n run: npm run check:live-browser-evidence\n - name: Upload production browser release validation\n uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n with:\n name: release-live-browser-evidence-${{ github.run_id }}\n path: |\n /tmp/live-browser-acceptance-validation.json\n evidence/operations/production-browser.json\n evidence/operations/production-browser-trace.json\n evidence/operations/production-member-denial.json\n if-no-files-found: error\n retention-days: 90\n\n operational-evidence:\n runs-on: ubuntu-latest\n timeout-minutes: 15\n steps:\n - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n with:\n fetch-depth: 0\n - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n with:\n node-version: 22\n cache: npm\n - run: npm ci\n - name: Fetch deployed identity, manifest, and verify the signed fresh-install attestation\n env:\n DEPLOYED_BASE_URL: ${{ inputs.deployed_base_url }}\n OPERATIONAL_SCOPE_EVIDENCE_PATH: evidence/operations/production-scope-probe.json\n run: |\n node <<'NODE'\n const { readFileSync, writeFileSync } = require(\"node:fs\");\n const raw = process.env.DEPLOYED_BASE_URL ?? \"\";\n const base = new URL(raw.endsWith(\"/\") ? raw : `${raw}/`);\n if (base.protocol !== \"https:\" || base.username || base.password || base.search || base.hash) {\n throw new Error(\"deployed_base_url must be a credential-free HTTPS base URL\");\n }\n const scope = JSON.parse(readFileSync(process.env.OPERATIONAL_SCOPE_EVIDENCE_PATH, \"utf8\"));\n const envelope = scope?.freshInstall?.verificationEnvelope;\n if (!envelope || typeof envelope !== \"object\" || Array.isArray(envelope)) {\n throw new Error(\"signed fresh-install verification envelope missing\");\n }\n async function boundedJson(response, label) {\n if (!response.ok) throw new Error(`${label} returned ${response.status}`);\n const text = await response.text();\n if (Buffer.byteLength(text) > 65_536) throw new Error(`${label} response is too large`);\n JSON.parse(text);\n return text;\n }\n async function main() {\n const [version, manifest, attestation] = await Promise.all([\n fetch(new URL(\"version\", base), { redirect: \"error\" }),\n fetch(new URL(\"manifest\", base), { redirect: \"error\" }),\n fetch(new URL(\"/release/install-attestation/verify\", base), {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", accept: \"application/json\" },\n body: JSON.stringify(envelope),\n redirect: \"error\",\n }),\n ]);\n writeFileSync(\"/tmp/operational-deployed-version.json\", await boundedJson(version, \"deployed /version\"), { mode: 0o600 });\n writeFileSync(\"/tmp/operational-deployed-manifest.json\", await boundedJson(manifest, \"deployed /manifest\"), { mode: 0o600 });\n writeFileSync(\"/tmp/operational-attestation-verification.json\", await boundedJson(attestation, \"fresh-install attestation verification\"), { mode: 0o600 });\n }\n main().catch((error) => { console.error(error.message); process.exit(1); });\n NODE\n - name: Validate measured exact-candidate operational evidence\n env:\n OPERATIONAL_EXPECTED_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n OPERATIONAL_EVIDENCE_COMMIT_SHA: ${{ github.sha }}\n OPERATIONAL_DEPLOYED_VERSION_PATH: /tmp/operational-deployed-version.json\n OPERATIONAL_DEPLOYED_MANIFEST_PATH: /tmp/operational-deployed-manifest.json\n OPERATIONAL_ATTESTATION_VERIFICATION_PATH: /tmp/operational-attestation-verification.json\n OPERATIONAL_RESTORE_EVIDENCE_PATH: evidence/operations/production-restore.json\n OPERATIONAL_SCOPE_EVIDENCE_PATH: evidence/operations/production-scope-probe.json\n OPERATIONAL_VALIDATION_PATH: /tmp/operational-release-validation.json\n run: npm run check:operational-evidence\n - name: Upload operational release validation\n uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n with:\n name: release-operational-evidence-${{ github.run_id }}\n path: |\n /tmp/operational-release-validation.json\n evidence/operations/production-restore.json\n evidence/operations/production-scope-probe.json\n if-no-files-found: error\n retention-days: 90\n\n live-smoke:\n runs-on: ubuntu-latest\n timeout-minutes: 20\n environment: clockify-live-smoke-sacrificial\n env:\n LIVE_SMOKE_EVIDENCE_PATH: /tmp/ai-assistant-live-smoke-evidence/smoke.json\n steps:\n - name: Initialize fail-closed smoke evidence\n shell: bash\n run: |\n mkdir -p /tmp/ai-assistant-live-smoke-evidence\n printf '%s\\n' '{\"resourcePrefixes\":[\"AIASSIST_SMOKE_\"],\"counts\":{\"failures\":1},\"status\":\"failed\"}' > \"${LIVE_SMOKE_EVIDENCE_PATH}\"\n - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n with:\n node-version: 22\n cache: npm\n - run: npm ci\n - name: Live smoke\n run: timeout --signal=TERM --kill-after=30s 12m npx tsx scripts/live-smoke.ts\n env:\n LIVE_CLOCKIFY: \"1\"\n LIVE_CLOCKIFY_API_KEY: ${{ secrets.LIVE_CLOCKIFY_API_KEY }}\n LIVE_WORKSPACE_ID: ${{ secrets.LIVE_WORKSPACE_ID }}\n - name: Upload live-smoke evidence\n if: ${{ always() }}\n uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n with:\n name: release-live-smoke-evidence-${{ github.run_id }}\n path: /tmp/ai-assistant-live-smoke-evidence/smoke.json\n if-no-files-found: error\n retention-days: 30\n\n live-smoke-cleanup:\n needs: live-smoke\n if: ${{ always() }}\n runs-on: ubuntu-latest\n timeout-minutes: 15\n environment: clockify-live-smoke-sacrificial\n env:\n LIVE_SWEEP_EVIDENCE_PATH: /tmp/ai-assistant-live-smoke-evidence/cleanup.json\n steps:\n - name: Initialize fail-closed cleanup evidence\n shell: bash\n run: |\n mkdir -p /tmp/ai-assistant-live-smoke-evidence\n printf '%s\\n' '{\"resourcePrefixes\":[\"AIASSIST_SMOKE_\"],\"counts\":{\"failures\":1},\"status\":\"failed\"}' > \"${LIVE_SWEEP_EVIDENCE_PATH}\"\n - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n with:\n node-version: 22\n cache: npm\n - run: npm ci\n - name: Sweep leftovers\n run: timeout --signal=TERM --kill-after=30s 7m npx tsx scripts/live-sweep.ts\n env:\n LIVE_CLOCKIFY: \"1\"\n LIVE_CLOCKIFY_API_KEY: ${{ secrets.LIVE_CLOCKIFY_API_KEY }}\n LIVE_WORKSPACE_ID: ${{ secrets.LIVE_WORKSPACE_ID }}\n - name: Upload cleanup evidence\n if: ${{ always() }}\n uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n with:\n name: release-live-smoke-cleanup-evidence-${{ github.run_id }}\n path: /tmp/ai-assistant-live-smoke-evidence/cleanup.json\n if-no-files-found: error\n retention-days: 30\n\n record:\n if: ${{ always() }}\n needs: [machine-gates, codeql, secret-scan, deepseek-evidence, private-production-evidence, live-browser-evidence, operational-evidence, live-smoke, live-smoke-cleanup]\n runs-on: ubuntu-latest\n timeout-minutes: 15\n env:\n RELEASE_SOURCE_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n RELEASE_EVIDENCE_COMMIT_SHA: ${{ github.sha }}\n RELEASE_GATE_VERIFY: ${{ needs.machine-gates.outputs.verify || 'skipped' }}\n RELEASE_GATE_REVIEWED_PULL_REQUEST: ${{ needs.machine-gates.outputs.reviewed-pr || 'skipped' }}\n RELEASE_GATE_PULL_REQUEST_CI: ${{ needs.machine-gates.outputs.reviewed-pr || 'skipped' }}\n RELEASE_GATE_DEPENDENCY_REVIEW: ${{ needs.machine-gates.outputs.reviewed-pr || 'skipped' }}\n RELEASE_GATE_PULL_REQUEST_CODEQL: ${{ needs.machine-gates.outputs.reviewed-pr || 'skipped' }}\n RELEASE_GATE_PULL_REQUEST_SECRET_SCAN: ${{ needs.machine-gates.outputs.reviewed-pr || 'skipped' }}\n RELEASE_GATE_ENGINEERING_REVIEW: ${{ needs.machine-gates.outputs.reviewed-pr || 'skipped' }}\n RELEASE_REVIEWED_PR_EVIDENCE: ${{ needs.machine-gates.outputs.reviewed-pr-evidence || '{}' }}\n RELEASE_COLD_VERIFY_EVIDENCE: ${{ needs.machine-gates.outputs.cold-verifies || '{}' }}\n RELEASE_GATE_LOCAL_UI_PERFORMANCE: ${{ needs.machine-gates.outputs.local-ui-performance || 'skipped' }}\n RELEASE_GATE_ACTIONLINT: ${{ needs.machine-gates.outputs.actionlint || 'skipped' }}\n RELEASE_GATE_MARKETPLACE_MEDIA_BINDING: ${{ needs.machine-gates.outputs.marketplace_media_binding || 'skipped' }}\n RELEASE_GATE_AUDIT: ${{ needs.machine-gates.outputs.audit || 'skipped' }}\n RELEASE_GATE_LICENSE: ${{ needs.machine-gates.outputs.license || 'skipped' }}\n RELEASE_GATE_SCRIPTED_SAFETY_CORPUS: ${{ needs.machine-gates.outputs.scripted-safety-corpus || 'skipped' }}\n RELEASE_GATE_BROWSER_E2E: ${{ needs.machine-gates.outputs.browser-e2e || 'skipped' }}\n RELEASE_GATE_SBOM: ${{ needs.machine-gates.outputs.sbom || 'skipped' }}\n RELEASE_GATE_CODEQL: ${{ needs.codeql.result }}\n RELEASE_GATE_SECRET_SCAN: ${{ needs.secret-scan.result }}\n RELEASE_GATE_LIVE_SMOKE: ${{ needs.live-smoke.result == 'success' && needs.live-smoke-cleanup.result == 'success' && 'success' || 'failure' }}\n RELEASE_GATE_BACKUP_RESTORE_DRILL: ${{ needs.operational-evidence.result }}\n RELEASE_GATE_DETERMINISTIC_SAFETY_EVALUATION: ${{ needs.deepseek-evidence.result }}\n RELEASE_GATE_PRIVATE_PRODUCTION_PERFORMANCE: ${{ needs.private-production-evidence.result }}\n RELEASE_GATE_LIVE_BROWSER_ACCEPTANCE: ${{ needs.live-browser-evidence.result }}\n RELEASE_GATE_PRODUCTION_AUDIT_HOST_CLEARANCE: ${{ needs.operational-evidence.result }}\n RELEASE_EVIDENCE_PATH: /tmp/ai-assistant-release-evidence/release-evidence.json\n steps:\n - name: Record machine conclusions and unevaluated human gates\n shell: bash\n run: |\n node <<'NODE'\n const { mkdirSync, renameSync, rmSync, writeFileSync } = require(\"node:fs\");\n const { dirname } = require(\"node:path\");\n\n const outputPath = process.env.RELEASE_EVIDENCE_PATH;\n if (!outputPath) throw new Error(\"release evidence path missing\");\n const temporaryPath = `${outputPath}.tmp`;\n mkdirSync(dirname(outputPath), { recursive: true });\n rmSync(outputPath, { force: true });\n rmSync(temporaryPath, { force: true });\n\n const sourceCandidateSha = process.env.RELEASE_SOURCE_CANDIDATE_SHA ?? \"\";\n const evidenceCommitSha = process.env.RELEASE_EVIDENCE_COMMIT_SHA ?? \"\";\n if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(sourceCandidateSha)) {\n throw new Error(\"release source candidate SHA is invalid\");\n }\n if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(evidenceCommitSha)) {\n throw new Error(\"release evidence commit SHA is invalid\");\n }\n const status = (value) => ({\n success: \"passed\",\n failure: \"failed\",\n cancelled: \"cancelled\",\n skipped: \"skipped\",\n })[value] ?? \"unknown\";\n const reviewedPullRequest = JSON.parse(process.env.RELEASE_REVIEWED_PR_EVIDENCE ?? \"{}\");\n const coldVerifies = JSON.parse(process.env.RELEASE_COLD_VERIFY_EVIDENCE ?? \"{}\");\n if (\n reviewedPullRequest.conclusion !== \"passed\"\n || reviewedPullRequest.sourceCandidateSha !== sourceCandidateSha\n || reviewedPullRequest.evidenceCommitSha !== evidenceCommitSha\n || reviewedPullRequest.pullRequest?.headSha !== evidenceCommitSha\n || reviewedPullRequest.pullRequest?.unresolvedReviewThreads !== 0\n ) throw new Error(\"reviewed pull request evidence is not release-bound\");\n if (\n coldVerifies.conclusion !== \"passed\"\n || coldVerifies.sourceCandidateSha !== sourceCandidateSha\n || coldVerifies.evidenceCommitSha !== evidenceCommitSha\n || coldVerifies.minimumPassedTests !== 5351\n || coldVerifies.consecutiveColdPasses !== 3\n || coldVerifies.retries !== 0\n || coldVerifies.passes?.length !== 3\n || coldVerifies.passes.some((pass) =>\n pass.passedTests < 5351\n || pass.passedTests !== pass.totalTests\n || pass.failedTests !== 0\n || pass.pendingTests !== 0\n || pass.todoTests !== 0)\n ) throw new Error(\"cold verify evidence is not release-bound\");\n const evidence = {\n sourceCandidateSha,\n evidenceCommitSha,\n reviewedPullRequest,\n coldVerifies,\n machineGates: {\n verify: status(process.env.RELEASE_GATE_VERIFY),\n reviewedPullRequest: status(process.env.RELEASE_GATE_REVIEWED_PULL_REQUEST),\n pullRequestCi: status(process.env.RELEASE_GATE_PULL_REQUEST_CI),\n dependencyReview: status(process.env.RELEASE_GATE_DEPENDENCY_REVIEW),\n pullRequestCodeql: status(process.env.RELEASE_GATE_PULL_REQUEST_CODEQL),\n pullRequestSecretScan: status(process.env.RELEASE_GATE_PULL_REQUEST_SECRET_SCAN),\n engineeringReview: status(process.env.RELEASE_GATE_ENGINEERING_REVIEW),\n localUiPerformance: status(process.env.RELEASE_GATE_LOCAL_UI_PERFORMANCE),\n actionlint: status(process.env.RELEASE_GATE_ACTIONLINT),\n marketplaceMediaBinding: status(process.env.RELEASE_GATE_MARKETPLACE_MEDIA_BINDING),\n audit: status(process.env.RELEASE_GATE_AUDIT),\n license: status(process.env.RELEASE_GATE_LICENSE),\n codeql: status(process.env.RELEASE_GATE_CODEQL),\n secretScan: status(process.env.RELEASE_GATE_SECRET_SCAN),\n scriptedSafetyCorpus: status(process.env.RELEASE_GATE_SCRIPTED_SAFETY_CORPUS),\n browserE2e: status(process.env.RELEASE_GATE_BROWSER_E2E),\n sbom: status(process.env.RELEASE_GATE_SBOM),\n liveSmoke: status(process.env.RELEASE_GATE_LIVE_SMOKE),\n backupRestoreDrill: status(process.env.RELEASE_GATE_BACKUP_RESTORE_DRILL),\n deterministicSafetyEvaluation: status(process.env.RELEASE_GATE_DETERMINISTIC_SAFETY_EVALUATION),\n privateProductionPerformance: status(process.env.RELEASE_GATE_PRIVATE_PRODUCTION_PERFORMANCE),\n liveBrowserAcceptance: status(process.env.RELEASE_GATE_LIVE_BROWSER_ACCEPTANCE),\n productionAuditHostClearance: status(process.env.RELEASE_GATE_PRODUCTION_AUDIT_HOST_CLEARANCE),\n },\n humanGates: {\n providerAndCredentialsGovernance: \"not_evaluated\",\n ownershipAndHumanSignoff: \"not_evaluated\",\n marketplaceSubmission: \"not_evaluated\",\n },\n };\n writeFileSync(temporaryPath, `${JSON.stringify(evidence, null, 2)}\\n`, \"utf8\");\n renameSync(temporaryPath, outputPath);\n NODE\n # B5: the record job checks out the evidence tree so the REAL v2 authority\n # generator can run. Deliberately AFTER the machine-conclusion heredoc, so\n # a dependency-install failure cannot lose the v1 record.\n - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0\n with:\n ref: ${{ inputs.tested_candidate_sha }}\n fetch-depth: 0\n - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0\n with:\n node-version: 22\n cache: npm\n - run: npm ci\n - name: Record the candidate-bound write-safety observation\n env:\n V2_AUTHORITY_OBSERVATIONS_PATH: /tmp/ai-assistant-release-evidence/v2-write-safety-observation.json\n run: npm run record:v2-write-safety-observation\n - name: Record v2 authority evidence\n # B5: replaces the Task-13 inline sentinel with the real generator. The\n # catalog hash is the v2 MODEL-API REGISTRY hash (the 127 `api` actions\n # the v2 model can see) — deliberately NOT the 171-action inventory\n # hash — and 84 is the v2 assistant write-case count (the write-safety\n # matrix denominator). The generator fail-closes on any malformed input.\n env:\n V2_AUTHORITY_EVIDENCE_PATH: /tmp/ai-assistant-release-evidence/v2-authority-evidence.json\n V2_AUTHORITY_CANDIDATE_SHA: ${{ inputs.tested_candidate_sha }}\n V2_AUTHORITY_CATALOG_HASH: 3872950503ac629de4629009b7548fbbc1cd509893d0ad2d7c7b34359246cbd7\n V2_AUTHORITY_ASSISTANT_WRITE_CASES: \"84\"\n V2_AUTHORITY_EXPECTED_CHECKS: \"756\"\n V2_AUTHORITY_OBSERVATIONS_PATH: /tmp/ai-assistant-release-evidence/v2-write-safety-observation.json\n run: npx tsx scripts/evidence/v2-authority-evidence.ts\n - name: Upload v2 authority evidence\n if: ${{ always() }}\n uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n with:\n name: release-v2-authority-evidence-${{ github.run_id }}\n path: /tmp/ai-assistant-release-evidence/v2-authority-evidence.json\n if-no-files-found: error\n retention-days: 90\n - name: Enforce release-ready engineering gates\n if: ${{ always() }}\n shell: bash\n run: |\n node <<'NODE'\n const requiredEngineeringGates = {\n verify: process.env.RELEASE_GATE_VERIFY,\n reviewedPullRequest: process.env.RELEASE_GATE_REVIEWED_PULL_REQUEST,\n pullRequestCi: process.env.RELEASE_GATE_PULL_REQUEST_CI,\n dependencyReview: process.env.RELEASE_GATE_DEPENDENCY_REVIEW,\n pullRequestCodeql: process.env.RELEASE_GATE_PULL_REQUEST_CODEQL,\n pullRequestSecretScan: process.env.RELEASE_GATE_PULL_REQUEST_SECRET_SCAN,\n engineeringReview: process.env.RELEASE_GATE_ENGINEERING_REVIEW,\n localUiPerformance: process.env.RELEASE_GATE_LOCAL_UI_PERFORMANCE,\n actionlint: process.env.RELEASE_GATE_ACTIONLINT,\n marketplaceMediaBinding: process.env.RELEASE_GATE_MARKETPLACE_MEDIA_BINDING,\n audit: process.env.RELEASE_GATE_AUDIT,\n license: process.env.RELEASE_GATE_LICENSE,\n codeql: process.env.RELEASE_GATE_CODEQL,\n secretScan: process.env.RELEASE_GATE_SECRET_SCAN,\n scriptedSafetyCorpus: process.env.RELEASE_GATE_SCRIPTED_SAFETY_CORPUS,\n browserE2e: process.env.RELEASE_GATE_BROWSER_E2E,\n sbom: process.env.RELEASE_GATE_SBOM,\n liveSmoke: process.env.RELEASE_GATE_LIVE_SMOKE,\n backupRestoreDrill: process.env.RELEASE_GATE_BACKUP_RESTORE_DRILL,\n deterministicSafetyEvaluation: process.env.RELEASE_GATE_DETERMINISTIC_SAFETY_EVALUATION,\n privateProductionPerformance: process.env.RELEASE_GATE_PRIVATE_PRODUCTION_PERFORMANCE,\n liveBrowserAcceptance: process.env.RELEASE_GATE_LIVE_BROWSER_ACCEPTANCE,\n productionAuditHostClearance: process.env.RELEASE_GATE_PRODUCTION_AUDIT_HOST_CLEARANCE,\n };\n const failed = Object.entries(requiredEngineeringGates)\n .filter(([, value]) => value !== \"success\")\n .map(([gate, value]) => `${gate}=${value ?? \"missing\"}`);\n if (failed.length > 0) {\n console.error(`Release-ready engineering gates are not all successful: ${failed.join(\", \")}`);\n process.exit(1);\n }\n NODE\n - name: Upload release evidence\n if: ${{ always() }}\n uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2\n with:\n name: release-evidence-${{ github.run_id }}\n path: /tmp/ai-assistant-release-evidence/release-evidence.json\n if-no-files-found: error\n retention-days: 90\n"}}}} |