Files
olp/.github/workflows/alignment.yml
T
taodengandClaude Opus 4.7 ed82e65859 chore: D19 — cleanup batch (Findings 8 + 14 + 15 + D17 dead import)
cold-audit catch from 2026-05-23

Batched 4 small P3 mechanical cleanups per Iron Rule 11 IDR cleanup-batch
convention (all P3, all small, no semantic feature changes beyond defensive
validation).

Changes (7 files):

1. lib/ir/ir-to-openai.mjs (+30 / -3) — Finding 8 defensive validator:
   - Added OPENAI_FINISH_REASON_ENUM Set with the 6 spec-allowed values
     (stop / length / tool_calls / content_filter / function_call / null)
   - Added normalizeFinishReason(value) helper that returns value unchanged
     if in enum, else 'stop'
   - Routed both irChunkToOpenAISSE (streaming path) and
     irResponseToOpenAINonStream (non-stream path) through the helper
   - Bonus tightening (in-scope, same finish_reason concept):
     irResponseToOpenAINonStream's gate changed from `if (chunk.finish_reason)`
     (truthy check) to `if (chunk.finish_reason !== undefined)` so an
     explicit null (valid spec value meaning "still in progress") is no
     longer silently dropped by the truthy guard
   - Note: undefined → null collapse via `?? null` is unreachable in the
     current codebase (all provider plugins explicitly set finish_reason
     on stop chunks); defensive only against a future plugin that omits
     the field — documented inline

2. .github/workflows/alignment.yml (-16) — Finding 14 dead CI cleanup:
   - Removed `setup.mjs` from path triggers (push + pull_request) — the
     file does not exist in the repo
   - Removed the dead `KNOWN_PROVIDERS=(...)` bash array from job 1 and
     its comment block — no later step iterated over it, so the array
     was abandoned
   - LEFT untouched: the Node.js inline KNOWN_PROVIDERS array in the
     models-registry validation job — that one is actively consumed by
     the schema validation script

3. lib/providers/anthropic.mjs / codex.mjs / mistral.mjs (3 × 1 line) —
   Finding 15: removed unused `PROVIDER_ERROR_CODES` from import lines.
   Each line went from `import { ProviderError, PROVIDER_ERROR_CODES } from
   './base.mjs';` to `import { ProviderError } from './base.mjs';`. The
   constant remains exported from base.mjs (its declaration site, where
   it IS used for validation).

4. server.mjs (1 line) — D17 reviewer's observation: removed unused
   `getProviderForModel` from the import line. The function is only
   called by lib/fallback/engine.mjs which imports it directly from
   lib/providers/index.mjs. server.mjs's import was dead (the routing
   SPOT lives in engine.mjs after D17 — server.mjs uses buildDefaultChain
   exclusively).

5. test-features.mjs (+44) — Suite 3 (irChunkToOpenAISSE format) extended
   with 4 new finish_reason normalization tests:
   - Test 1: non-spec streaming finish_reason ('timeout', 'overloaded',
     'cancelled') → mapped to 'stop'
   - Test 2: spec-enum streaming finish_reason (all 6 incl. null) preserved
   - Test 3: non-spec non-stream finish_reason → mapped to 'stop'
   - Test 4: spec-enum non-stream finish_reason preserved (null
     intentionally omitted — documented inline)

Tests: 324 → 328 (+4). All pass on Node 20.

Pre-commit fold-ins (per evidence-first checkpoint #4):

- **D19 reviewer suggestion #1**: added inline comment to
  normalizeFinishReason explaining the unreachable `undefined → null`
  branch (defensive only, no current plugin omits the field). Cheap
  future-reader clarity.
- **D19 reviewer suggestion #2**: added inline comment to Test 4
  explaining why null is intentionally omitted from the spec-enum list
  (non-stream `!== undefined` gate enters with null and overwrites
  default 'stop' to null — semantically odd but spec-valid).

Reviewer suggestion #3 (consider stricter `undefined → 'stop'` on
streaming-stop path vs `null → null` on delta path) explicitly marked
out of scope by reviewer — would require call-site context awareness;
filed mentally as potential future work, not tracked as an issue
since no current path triggers it.

Authority:
- ALIGNMENT.md Rule 2(b) — only spec-defined fields in OpenAI responses
- OpenAI Chat Completions spec finish_reason enum
  https://platform.openai.com/docs/api-reference/chat/object#finish_reason
- CC 开发铁律 v1.6 § 10.x — Cold Audit Findings 8 / 14 / 15

Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE. Verified the unreachable `undefined → null` branch
claim by grep-checking all 3 provider plugins (none emit undefined);
verified the two KNOWN_PROVIDERS arrays were correctly distinguished
(only the dead bash one removed); ran npm test independently to confirm
328/328.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 12:16:19 +10:00

204 lines
8.3 KiB
YAML

name: Alignment Guardrail
on:
pull_request:
paths:
- 'server.mjs'
- 'lib/**'
- 'scripts/**'
- 'models-registry.json'
- '.github/workflows/alignment.yml'
push:
branches: [main]
paths:
- 'server.mjs'
- 'lib/**'
- 'scripts/**'
- 'models-registry.json'
- '.github/workflows/alignment.yml'
jobs:
blacklist:
name: source blacklist (hard fail)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Scan source for hallucinated tokens
shell: bash
run: |
set -euo pipefail
# Known-hallucinated tokens. Extend only via an ALIGNMENT.md amendment PR.
# Each token is matched as a fixed string against the OLP source tree
# (excluding docs/, CHANGELOG, README, this workflow, and tests that
# may legitimately reference the historical token as a guardrail).
#
# Inherited transitively from OCP's 2026-04-11 drift:
# - api.anthropic.com/api/oauth/usage : fabricated Anthropic OAuth
# usage endpoint. Does not appear in any shipped @anthropic-ai/
# claude-code cli.js. Carried forward as a transitive guardrail.
#
# OLP-native entries: added as drift incidents accumulate.
BLACKLIST=(
"api.anthropic.com/api/oauth/usage"
)
# Source files in scope. Exclude docs, CHANGELOG, README, the
# workflow itself, and the test file (which may pin historical
# strings intentionally).
SOURCE_FILES="$(git ls-files \
| grep -E '\.(mjs|js|ts|json)$' \
| grep -v -E '^(docs/|CHANGELOG\.md|README\.md|test-features\.mjs|\.github/workflows/alignment\.yml)')"
FAIL=0
# 1. Blacklist scan
for token in "${BLACKLIST[@]}"; do
if echo "$SOURCE_FILES" | xargs grep -n -F "$token" 2>/dev/null; then
echo "::error::Blacklisted token '$token' detected in OLP source."
FAIL=1
fi
done
# 2. Excluded-provider scan: Google Antigravity is permanently
# excluded per ADR 0006 / ALIGNMENT.md Risk Tier A. Any reference
# to a `google-antigravity` plugin file or provider key in source
# (outside docs which may discuss the exclusion) is a finding.
FORBIDDEN_PROVIDER_TOKENS=(
"google-antigravity"
"antigravity"
)
for token in "${FORBIDDEN_PROVIDER_TOKENS[@]}"; do
HITS="$(echo "$SOURCE_FILES" | xargs grep -n -F "$token" 2>/dev/null || true)"
if [ -n "$HITS" ]; then
echo "::error::Tier-A-excluded provider token '$token' detected in OLP source. Per ALIGNMENT.md / ADR 0006, this provider is excluded by default; re-inclusion requires ADR 0006 supersession/amendment with new primary-source evidence."
echo "$HITS"
FAIL=1
fi
done
if [ "$FAIL" -ne 0 ]; then
echo ""
echo "============================================================"
echo "ALIGNMENT GUARDRAIL FAILURE"
echo "============================================================"
echo "OLP source contains a token on the alignment blacklist or"
echo "references a Tier-A-excluded provider."
echo ""
echo "Blacklist tokens were introduced by LLM hallucinations and"
echo "do not appear in the relevant authority (provider CLI,"
echo "OpenAI spec, or ADR). See ALIGNMENT.md and (where the"
echo "token is inherited from OCP) the OCP 2026-04-11 drift"
echo "record at https://github.com/dtzp555-max/ocp."
echo ""
echo "Excluded providers are listed in ALIGNMENT.md \xc2\xa7 Risk Tier"
echo "Framework and ADR 0006. Tier-A exclusion means not"
echo "bundled, not pluggable, not added via opt-in;"
echo "re-inclusion requires ADR 0006 amendment with new"
echo "primary-source evidence."
echo ""
echo "Required action:"
echo " 1. Remove the token from source."
echo " 2. Cite the real authority (provider CLI doc / OpenAI"
echo " spec section / ADR) for the operation you intended."
echo " 3. See ALIGNMENT.md Rules 1, 2, and 5."
echo ""
echo "Do not add allowlist entries to this workflow without an"
echo "amendment PR to ALIGNMENT.md (see Amendment Procedure)."
echo "============================================================"
exit 1
fi
echo "Blacklist + excluded-provider scan clean."
models-registry:
name: models-registry.json sanity (hard fail)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate models-registry.json
shell: bash
run: |
set -euo pipefail
if [ ! -f models-registry.json ]; then
echo "models-registry.json not found yet (bootstrap phase). Skipping."
exit 0
fi
# Basic JSON validity.
if ! node -e "JSON.parse(require('fs').readFileSync('models-registry.json','utf8'))"; then
echo "::error::models-registry.json is not valid JSON."
exit 1
fi
# Provider keys in models-registry.json must match the inventory
# in ALIGNMENT.md.
KNOWN_PROVIDERS='["anthropic","openai","mistral","grok","kimi","minimax","glm","qwen"]'
node -e "
const fs = require('fs');
const known = ${KNOWN_PROVIDERS};
const reg = JSON.parse(fs.readFileSync('models-registry.json','utf8'));
const providers = reg.providers || {};
const bad = Object.keys(providers).filter(p => !known.includes(p));
if (bad.length > 0) {
console.error('::error::Unknown provider key(s) in models-registry.json: ' + bad.join(', '));
console.error('Known providers per ALIGNMENT.md: ' + known.join(', '));
process.exit(1);
}
console.log('models-registry.json provider keys OK: ' + Object.keys(providers).join(', '));
"
commit-citation:
name: per-provider commit citation (soft check)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Checkout full history
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Scan PR commits for uncited assertions
if: github.event_name == 'pull_request'
shell: bash
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
if [ -z "${BASE_SHA:-}" ] || [ -z "${HEAD_SHA:-}" ]; then
echo "No PR context; skipping."
exit 0
fi
# Use process substitution `< <(...)` rather than piping into the
# while loop. A piped while runs in a subshell, so `WARN=1` would
# never propagate back out to this scope — the if-check below would
# always report "clean" even when warnings were emitted. Classic
# Bash subshell trap; see commit history for the codex review that
# caught this.
WARN=0
while read -r sha; do
BODY="$(git log -1 --format=%B "$sha")"
if echo "$BODY" | grep -E -i -q '(provider|claude|codex|vibe|grok|kimi|minimax|glm|qwen|cli)[[:space:]]+(code[[:space:]]+)?uses'; then
if echo "$BODY" | grep -E -i -q '(cli[[:space:]]+v[0-9]+|https?://|ADR[[:space:]]+[0-9]{4})'; then
echo "OK $sha: assertion cited."
else
echo "::warning::Commit $sha asserts 'Provider X uses ...' or '<provider> CLI uses ...' but does not cite a CLI version, docs URL, or ADR number. See CLAUDE.md -> Commit message conventions."
WARN=1
fi
fi
done < <(git log --format="%H" "${BASE_SHA}..${HEAD_SHA}")
if [ "$WARN" -ne 0 ]; then
echo "Soft check raised warnings. Reviewer: please enforce per CLAUDE.md."
else
echo "Commit citation soft check clean."
fi