feat: close provider promotion and support access gates
Build and Test Quality Gate / Contract Web typecheck and build (pull_request) Successful in 3m58s
Build and Test Quality Gate / Python tests and Contract API migration (pull_request) Failing after 29m6s
Security Quality Gate / npm audit, Bandit, pip-audit, detect-secrets (pull_request) Successful in 15m55s
Build and Test Quality Gate / Contract Web typecheck and build (pull_request) Successful in 3m58s
Build and Test Quality Gate / Python tests and Contract API migration (pull_request) Failing after 29m6s
Security Quality Gate / npm audit, Bandit, pip-audit, detect-secrets (pull_request) Successful in 15m55s
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
name: Provider Promotion Canary
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
mode:
|
||||
description: "Read-only verification mode"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- canary
|
||||
- revocation
|
||||
- kill-switch
|
||||
node_a:
|
||||
description: "First production AI Platform HTTPS base URL"
|
||||
required: true
|
||||
node_b:
|
||||
description: "Second production AI Platform HTTPS base URL"
|
||||
required: true
|
||||
expected_release_id:
|
||||
description: "Expected immutable release id for canary"
|
||||
required: false
|
||||
expected_receipt_id:
|
||||
description: "Expected signed receipt id for canary"
|
||||
required: false
|
||||
expected_key_id:
|
||||
description: "Expected receipt signing key id for canary"
|
||||
required: false
|
||||
expected_image_digest:
|
||||
description: "Expected immutable AI Platform image digest for canary"
|
||||
required: false
|
||||
propagation_deadline_seconds:
|
||||
description: "Maximum revocation or kill-switch propagation SLA"
|
||||
required: false
|
||||
default: "300"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: provider-promotion-control
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: [self-hosted, yuqei-smoke]
|
||||
environment: production-provider-promotion-control
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
clean: true
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Verify every production node without writes
|
||||
shell: bash
|
||||
env:
|
||||
SERVICE_KEY: ${{ secrets.AI_PLATFORM_PRODUCTION_SERVICE_KEY }}
|
||||
MODE: ${{ inputs.mode }}
|
||||
NODE_A: ${{ inputs.node_a }}
|
||||
NODE_B: ${{ inputs.node_b }}
|
||||
EXPECTED_RELEASE_ID: ${{ inputs.expected_release_id }}
|
||||
EXPECTED_RECEIPT_ID: ${{ inputs.expected_receipt_id }}
|
||||
EXPECTED_KEY_ID: ${{ inputs.expected_key_id }}
|
||||
EXPECTED_IMAGE_DIGEST: ${{ inputs.expected_image_digest }}
|
||||
PROPAGATION_DEADLINE_SECONDS: ${{ inputs.propagation_deadline_seconds }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
test -n "$SERVICE_KEY"
|
||||
mkdir -p .promotion-control-private promotion-control-output
|
||||
printf '%s' "$SERVICE_KEY" > .promotion-control-private/service-key
|
||||
args=(
|
||||
--mode "$MODE"
|
||||
--node "$NODE_A"
|
||||
--node "$NODE_B"
|
||||
--service-key-file .promotion-control-private/service-key
|
||||
)
|
||||
if [ "$MODE" = "canary" ]; then
|
||||
args+=(
|
||||
--expected-release-id "$EXPECTED_RELEASE_ID"
|
||||
--expected-receipt-id "$EXPECTED_RECEIPT_ID"
|
||||
--expected-key-id "$EXPECTED_KEY_ID"
|
||||
--expected-image-digest "$EXPECTED_IMAGE_DIGEST"
|
||||
)
|
||||
else
|
||||
args+=(--deadline-seconds "$PROPAGATION_DEADLINE_SECONDS")
|
||||
fi
|
||||
python yuqei-infra/scripts/verify-provider-promotion-nodes.py "${args[@]}" \
|
||||
| tee promotion-control-output/provider-promotion-control-summary.json
|
||||
- name: Upload read-only verification summary
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: provider-promotion-control-summary
|
||||
if-no-files-found: warn
|
||||
retention-days: 30
|
||||
path: promotion-control-output/provider-promotion-control-summary.json
|
||||
- name: Remove service credential
|
||||
if: always()
|
||||
shell: bash
|
||||
run: rm -f .promotion-control-private/service-key
|
||||
@@ -0,0 +1,199 @@
|
||||
name: Protected Provider Promotion
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
phase:
|
||||
description: "Collect technical evidence or finalize a legally signed package"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- collect
|
||||
- finalize
|
||||
shared_staging_api_base:
|
||||
description: "Authenticated shared-staging AI Platform URL"
|
||||
required: true
|
||||
default: "https://staging.example.invalid/ai"
|
||||
tenant_id:
|
||||
description: "Acceptance tenant"
|
||||
required: true
|
||||
default: "default"
|
||||
technical_run_id:
|
||||
description: "Collect workflow run id (required for finalize)"
|
||||
required: false
|
||||
default: ""
|
||||
legal_evidence_host:
|
||||
description: "Controlled shared evidence hostname (required for finalize)"
|
||||
required: false
|
||||
default: ""
|
||||
receipt_key_id:
|
||||
description: "Trusted receipt key id (required for finalize)"
|
||||
required: false
|
||||
default: ""
|
||||
target_release_id:
|
||||
description: "Immutable target release id (required for finalize)"
|
||||
required: false
|
||||
default: ""
|
||||
target_image_digest:
|
||||
description: "Immutable target AI Platform image digest (required for finalize)"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
concurrency:
|
||||
group: provider-promotion-${{ inputs.tenant_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
collect:
|
||||
if: inputs.phase == 'collect'
|
||||
runs-on: [self-hosted, yuqei-smoke]
|
||||
environment: shared-staging-provider-promotion
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
clean: true
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install locked AI Platform package
|
||||
shell: bash
|
||||
run: python -m pip install ./yuqei-ai-platform/services/ai-platform-api
|
||||
- name: Collect deterministic legal review package
|
||||
shell: bash
|
||||
env:
|
||||
SERVICE_KEY: ${{ secrets.AI_PLATFORM_SERVICE_KEY }}
|
||||
SHARED_STAGING_API_BASE: ${{ inputs.shared_staging_api_base }}
|
||||
TENANT_ID: ${{ inputs.tenant_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
test -n "$SERVICE_KEY"
|
||||
mkdir -p .promotion-private promotion-output
|
||||
printf '%s' "$SERVICE_KEY" > .promotion-private/service-key
|
||||
python yuqei-infra/scripts/run-ai-provider-acceptance.py \
|
||||
--mode collect \
|
||||
--base-url "$SHARED_STAGING_API_BASE" \
|
||||
--tenant-id "$TENANT_ID" \
|
||||
--service-key-file .promotion-private/service-key \
|
||||
--artifact-dir promotion-output \
|
||||
--review-package-file promotion-output/provider-technical-review-package.json
|
||||
- name: Upload allowlisted review artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: provider-technical-review
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
path: |
|
||||
promotion-output/provider-technical-review-package.json
|
||||
promotion-output/ai-provider-acceptance-summary.json
|
||||
promotion-output/ai-provider-acceptance-summary.md
|
||||
- name: Remove private inputs
|
||||
if: always()
|
||||
shell: bash
|
||||
run: rm -f .promotion-private/service-key
|
||||
|
||||
finalize:
|
||||
if: inputs.phase == 'finalize'
|
||||
runs-on: [self-hosted, yuqei-smoke]
|
||||
environment: shared-staging-provider-promotion
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
clean: true
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Validate protected inputs
|
||||
shell: bash
|
||||
env:
|
||||
TECHNICAL_RUN_ID: ${{ inputs.technical_run_id }}
|
||||
LEGAL_EVIDENCE_HOST: ${{ inputs.legal_evidence_host }}
|
||||
RECEIPT_KEY_ID: ${{ inputs.receipt_key_id }}
|
||||
TARGET_RELEASE_ID: ${{ inputs.target_release_id }}
|
||||
TARGET_IMAGE_DIGEST: ${{ inputs.target_image_digest }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "$TECHNICAL_RUN_ID"
|
||||
test -n "$LEGAL_EVIDENCE_HOST"
|
||||
test -n "$RECEIPT_KEY_ID"
|
||||
test -n "$TARGET_RELEASE_ID"
|
||||
test -n "$TARGET_IMAGE_DIGEST"
|
||||
- name: Download exact technical package
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: provider-technical-review
|
||||
run-id: ${{ inputs.technical_run_id }}
|
||||
github-token: ${{ github.token }}
|
||||
path: technical-input
|
||||
- name: Install locked AI Platform package
|
||||
shell: bash
|
||||
run: python -m pip install ./yuqei-ai-platform/services/ai-platform-api
|
||||
- name: Finalize signed promotion without rerunning Provider
|
||||
shell: bash
|
||||
env:
|
||||
SERVICE_KEY: ${{ secrets.AI_PLATFORM_SERVICE_KEY }}
|
||||
LEGAL_APPROVAL_B64: ${{ secrets.PROVIDER_LEGAL_APPROVAL_JSON_B64 }}
|
||||
LEGAL_KEYRING_B64: ${{ secrets.PROVIDER_LEGAL_KEYRING_JSON_B64 }}
|
||||
RECEIPT_PRIVATE_KEY_B64: ${{ secrets.PROVIDER_RECEIPT_PRIVATE_KEY_PEM_B64 }}
|
||||
SHARED_STAGING_API_BASE: ${{ inputs.shared_staging_api_base }}
|
||||
TENANT_ID: ${{ inputs.tenant_id }}
|
||||
LEGAL_EVIDENCE_HOST: ${{ inputs.legal_evidence_host }}
|
||||
RECEIPT_KEY_ID: ${{ inputs.receipt_key_id }}
|
||||
TARGET_RELEASE_ID: ${{ inputs.target_release_id }}
|
||||
TARGET_IMAGE_DIGEST: ${{ inputs.target_image_digest }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
test -n "$SERVICE_KEY"
|
||||
test -n "$LEGAL_APPROVAL_B64"
|
||||
test -n "$LEGAL_KEYRING_B64"
|
||||
test -n "$RECEIPT_PRIVATE_KEY_B64"
|
||||
mkdir -p .promotion-private promotion-output
|
||||
printf '%s' "$SERVICE_KEY" > .promotion-private/service-key
|
||||
printf '%s' "$LEGAL_APPROVAL_B64" | base64 --decode > .promotion-private/legal-approval.json
|
||||
printf '%s' "$LEGAL_KEYRING_B64" | base64 --decode > .promotion-private/legal-keyring.json
|
||||
printf '%s' "$RECEIPT_PRIVATE_KEY_B64" | base64 --decode > .promotion-private/receipt-private-key.pem
|
||||
python yuqei-infra/scripts/run-ai-provider-acceptance.py \
|
||||
--mode finalize \
|
||||
--base-url "$SHARED_STAGING_API_BASE" \
|
||||
--tenant-id "$TENANT_ID" \
|
||||
--service-key-file .promotion-private/service-key \
|
||||
--artifact-dir promotion-output \
|
||||
--review-package-file technical-input/provider-technical-review-package.json \
|
||||
--legal-approval-file .promotion-private/legal-approval.json \
|
||||
--legal-keyring-file .promotion-private/legal-keyring.json \
|
||||
--legal-evidence-allowed-host "$LEGAL_EVIDENCE_HOST" \
|
||||
--receipt-private-key-file .promotion-private/receipt-private-key.pem \
|
||||
--receipt-key-id "$RECEIPT_KEY_ID" \
|
||||
--target-release-id "$TARGET_RELEASE_ID" \
|
||||
--target-image-digest "$TARGET_IMAGE_DIGEST" \
|
||||
--receipt-output promotion-output/provider-acceptance-receipt.json \
|
||||
--promotion-bundle-output promotion-output/provider-promotion-bundle.json
|
||||
- name: Upload allowlisted promotion artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: provider-promotion-bundle
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
path: |
|
||||
promotion-output/provider-acceptance-receipt.json
|
||||
promotion-output/provider-promotion-bundle.json
|
||||
promotion-output/ai-provider-acceptance-summary.json
|
||||
promotion-output/ai-provider-acceptance-summary.md
|
||||
- name: Remove private inputs
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
rm -f \
|
||||
.promotion-private/service-key \
|
||||
.promotion-private/legal-approval.json \
|
||||
.promotion-private/legal-keyring.json \
|
||||
.promotion-private/receipt-private-key.pem
|
||||
Reference in New Issue
Block a user