Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/ci-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@
"prefixes": [],
"files": [
".github/workflows/nfi-compatibility.yml",
"python/nfi_backtest_engine/compatibility_automation.py",
"scripts/compatibility_issue.py",
"scripts/compatibility_review_pr.py",
"tests/test_compatibility_automation.py",
"tests/test_compatibility_issue.py",
"tests/test_compatibility_review_pr.py",
"tests/test_nfi_compatibility_workflow.py"
]
Expand Down
9 changes: 6 additions & 3 deletions .github/scripts/test_ci_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ def test_policy_and_documentation_use_policy_lane(self) -> None:
def test_compatibility_automation_uses_focused_lane(self) -> None:
paths = [
".github/workflows/nfi-compatibility.yml",
"python/nfi_backtest_engine/compatibility_automation.py",
"scripts/compatibility_issue.py",
"scripts/compatibility_review_pr.py",
"tests/test_compatibility_automation.py",
"tests/test_compatibility_issue.py",
"tests/test_compatibility_review_pr.py",
"tests/test_nfi_compatibility_workflow.py",
".github/workflows/ci.yml",
Expand All @@ -65,7 +69,7 @@ def test_runtime_or_unknown_paths_fail_closed_to_code(self) -> None:
["planning/futures-discovery-policy.json"],
[".github/workflows/release.yml"],
[
"scripts/compatibility_review_pr.py",
"scripts/compatibility_issue.py",
"rust/crates/nfi-sim-core/Cargo.toml",
],
[],
Expand Down Expand Up @@ -117,8 +121,7 @@ def test_workflow_exposes_stable_aggregate_and_lane_conditions(self) -> None:
self.assertIn("needs.changes.outputs.code_changes == 'true'", workflow)
self.assertIn("Run focused compatibility automation tests", workflow)
self.assertNotIn("pull_request_target:", workflow)
self.assertIn('paths-ignore:', workflow)
self.assertIn('- "planning/compatibility-reviews/**"', workflow)
self.assertNotIn('paths-ignore:', workflow)
for path in self.contract["push"]["release_paths"]:
self.assertIn(f'- "{path}"', workflow)

Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ on:
- ".github/product-release-contract.json"
- ".github/release-candidate-contract.json"
pull_request:
paths-ignore:
# Evidence-only semantic review PRs do not need CI until implementation code
# is added. Ignoring their PR event prevents a redundant action_required run.
- "planning/compatibility-reviews/**"
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -109,18 +105,22 @@ jobs:
if: needs.changes.outputs.automation_changes == 'true'
run: >-
uv run pytest -q
tests/test_compatibility_review_pr.py
tests/test_compatibility_automation.py
tests/test_compatibility_issue.py
tests/test_nfi_compatibility_workflow.py
tests/test_release_contract.py
- name: Lint and type-check the focused automation surface
if: needs.changes.outputs.automation_changes == 'true'
run: |
uv run ruff check \
scripts/compatibility_review_pr.py \
tests/test_compatibility_review_pr.py \
python/nfi_backtest_engine/compatibility_automation.py \
scripts/compatibility_issue.py \
tests/test_compatibility_automation.py \
tests/test_compatibility_issue.py \
tests/test_nfi_compatibility_workflow.py
uv run basedpyright --level error \
scripts/compatibility_review_pr.py
python/nfi_backtest_engine/compatibility_automation.py \
scripts/compatibility_issue.py

python:
name: Python ${{ matrix.os }} / ${{ matrix.python-version }}
Expand Down
63 changes: 0 additions & 63 deletions .github/workflows/nfi-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -472,67 +472,6 @@ jobs:
if-no-files-found: error
retention-days: 30

semantic-review:
name: Open ${{ matrix.trading_mode }} generic semantic review Draft PR
needs:
- discover
- targeted
- canary
if: >-
always()
&& needs.discover.outputs.changed == 'true'
&& needs.targeted.result == 'success'
&& needs.canary.result == 'success'
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 1
matrix:
trading_mode:
- spot
- futures
permissions:
contents: write
pull-requests: write
steps:
- name: Check out checked engine identity
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ needs.discover.outputs.engine_sha }}
persist-credentials: true
- name: Download mode automation decision
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: latest-nfi-x7-${{ matrix.trading_mode }}-targeted
path: .compatibility
- name: Select semantic review route
id: review
run: |
if [ "$(jq -r .automation_route \
.compatibility/automation-decision-${{ matrix.trading_mode }}.json)" \
= "semantic_review_draft_pr" ]; then
echo "required=true" >> "${GITHUB_OUTPUT}"
else
echo "required=false" >> "${GITHUB_OUTPUT}"
fi
- name: Install locked Python environment
if: steps.review.outputs.required == 'true'
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
python-version: "3.12"
- name: Open evidence-only Draft PR
if: steps.review.outputs.required == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
uv sync --extra dev --frozen
uv run python scripts/compatibility_review_pr.py \
--decision \
.compatibility/automation-decision-${{ matrix.trading_mode }}.json \
--repo-root "${GITHUB_WORKSPACE}" \
--repository "${GITHUB_REPOSITORY}" \
--base main

publish:
name: Preserve ledger and reconcile compatibility issue
needs:
Expand Down Expand Up @@ -678,7 +617,6 @@ jobs:
- x7
- targeted
- canary
- semantic-review
- publish
if: always()
runs-on: ubuntu-latest
Expand Down Expand Up @@ -706,5 +644,4 @@ jobs:
--stage "x7=${{ needs.x7.result }}" \
--stage "targeted=${{ needs.targeted.result }}" \
--stage "canary=${{ needs.canary.result }}" \
--stage "semantic-review=${{ needs.semantic-review.result }}" \
--stage "publish=${{ needs.publish.result }}"
12 changes: 10 additions & 2 deletions docs/ci-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ The classifier selects the cheapest lane that covers every changed path:
- `policy-only`: documentation plus explicitly listed repository metadata and the
required-CI implementation itself; a dependency-free policy self-test runs on
Ubuntu;
- `automation-only`: the allowlisted NFI semantic-review workflow, publisher, and
focused tests; one Ubuntu job runs only their tests, lint, and type check;
- `automation-only`: the allowlisted NFI compatibility routing, issue reconciler,
workflow, and focused tests; one Ubuntu job runs only their tests, lint, and type
check;
- `code`: every unlisted path, runtime source, tests, schemas, fixtures, build
inputs, and installers; the full Python matrix on Linux, Windows, and macOS,
static/Rust checks, and native full-parity fixtures run.
Expand All @@ -35,6 +36,13 @@ or downloading data. Unknown infrastructure failures still fail and remain visib
Non-candidate artifacts expire after one day; raw candles, caches, container layers,
and traces are never uploaded.

Blocked generic semantics are retained in the append-only compatibility ledger and
one automatically reconciled `nfi-compatibility` issue. They do not create evidence-
only Draft PRs. Pull requests are reserved for a compact independently exact fixture
candidate or an implementation change, so a scheduled watcher cannot grow the PR
queue. The removed publisher and test paths remain explicit classification tombstones
so the deletion commit itself also stays on this focused automation lane.

Pull requests are the normal required-check surface. A protected merge is not tested
a second time on `main`; push-triggered CI is limited to version and product-release
contract paths that require a same-commit release check. Manual dispatch remains
Expand Down
13 changes: 7 additions & 6 deletions docs/future-nfi-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,18 @@ trade surface exact, full-state exact가 모두 필요하다. 원본 fixture는
Signal/Grind 번호, pair, timerange 또는 기대 결과를 분기값으로 사용하지 않는다.

- `native_exact`: 변경 branch 도달과 trade surface/full-state exact가 모두 참일 때만 허용
- `semantic_review_draft_pr`: 새 opcode 또는 generic lowering 검토가 필요하며 실행은 official-only
- `semantic_review_issue`: 새 opcode 또는 generic lowering 검토가 필요하며 실행은 official-only
- `bounded_discovery`: 정적 lowering은 가능하지만 exact branch fixture가 부족함
- `exact_fixture_draft_pr`: discovery가 독립 exact 후보를 만들었으나 병합 전 검토가 필요함
- `external_data_deferred`: 외부 데이터 재시도 보류이며 exact 증거가 아님
- `official_only`: 탐색이 끝났거나 현재 Native exact를 증명할 수 없음

semantic review Draft PR은 compact 결정문과 검토 요구사항만 추가한다. 런타임
의미론을 추측 생성하지 않으며 자동 승인·병합하지 않는다. maintainer가 범용
opcode/lowerer, 단위 테스트, 공식 fixture를 추가하고 Required CI와 exact 검증을
통과해야 Native로 승격된다. 외부 데이터 보류를 재사용할 때도 저장된 결정문의
`execution_route=official_only`, `exact=false`를 다시 검사한다.
semantic review는 append-only ledger와 자동 조정되는 단일 compatibility issue에
기록한다. evidence-only Draft PR은 만들지 않는다. maintainer가 범용 opcode/lowerer,
단위 테스트, 공식 fixture를 추가하고 Required CI와 exact 검증을 통과해야 Native로
승격된다. PR은 독립 exact fixture 후보 또는 실제 구현 변경에만 사용한다. 외부 데이터
보류를 재사용할 때도 저장된 결정문의 `execution_route=official_only`, `exact=false`를
다시 검사한다.

## Managed exit의 단계적 Native 전환

Expand Down
2 changes: 1 addition & 1 deletion planning/acceptance-commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
"mutates_external_state": false
},
"future_nfi_compatibility_tests": {
"command": "uv run pytest -q tests/test_official_fallback.py tests/test_strategy_diff.py tests/test_targeted_verification.py tests/test_compatibility_identity.py tests/test_compatibility_canary.py tests/test_compatibility_automation.py tests/test_compatibility_review_pr.py tests/test_resolve_upstream_source.py tests/test_compatibility_fixture_registry.py tests/test_compatibility_issue.py tests/test_workflow_health_issue.py tests/test_nfi_compatibility_workflow.py tests/test_compatibility_qualification.py tests/test_state_machine_ir.py tests/test_state_machine_shadow.py tests/test_dynamic_grind_levels.py tests/test_stateful_coverage.py tests/test_strategy_compatibility.py tests/test_generic_adapter.py tests/test_probe_capture.py tests/test_research_reference.py",
"command": "uv run pytest -q tests/test_official_fallback.py tests/test_strategy_diff.py tests/test_targeted_verification.py tests/test_compatibility_identity.py tests/test_compatibility_canary.py tests/test_compatibility_automation.py tests/test_resolve_upstream_source.py tests/test_compatibility_fixture_registry.py tests/test_compatibility_issue.py tests/test_workflow_health_issue.py tests/test_nfi_compatibility_workflow.py tests/test_compatibility_qualification.py tests/test_state_machine_ir.py tests/test_state_machine_shadow.py tests/test_dynamic_grind_levels.py tests/test_stateful_coverage.py tests/test_strategy_compatibility.py tests/test_generic_adapter.py tests/test_probe_capture.py tests/test_research_reference.py",
"working_directory": ".",
"platform": "any",
"timeout_seconds": 1200,
Expand Down
11 changes: 5 additions & 6 deletions python/nfi_backtest_engine/compatibility_automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def classify_compatibility_automation(
if exact:
route = "native_exact"
elif not static_compatible:
route = "semantic_review_draft_pr"
route = "semantic_review_issue"
review_kind = "new_opcode" if added_opcodes else "generic_lowering"
elif discovery_status is None or discovery_status == "budget_exhausted":
route = "bounded_discovery"
Expand All @@ -103,9 +103,7 @@ def classify_compatibility_automation(

native_allowed = route == "native_exact"
draft_kind = (
review_kind
if route == "semantic_review_draft_pr"
else "exact_fixture"
"exact_fixture"
if route == "exact_fixture_draft_pr"
else None
)
Expand Down Expand Up @@ -154,6 +152,7 @@ def classify_compatibility_automation(
"bounded_discovery_required": route == "bounded_discovery",
"draft_pr_allowed": draft_kind is not None,
"draft_pr_kind": draft_kind,
"issue_required": route == "semantic_review_issue",
"official_fallback_available": True,
"automatic_semantic_merge_allowed": False,
"external_data_deferred_is_exact": False,
Expand Down Expand Up @@ -348,9 +347,9 @@ def _message(route: str, *, review_kind: str | None) -> str:
),
"official_only": "Native exactness is unproven; use the announced official fallback.",
}
if route == "semantic_review_draft_pr":
if route == "semantic_review_issue":
subject = "new generic opcode" if review_kind == "new_opcode" else "generic lowering"
return f"Static lowering is blocked; open a {subject} review Draft PR."
return f"Static lowering is blocked; track the {subject} in the compatibility issue."
return messages[route]


Expand Down
Loading