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
52 changes: 52 additions & 0 deletions .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# SPDX-License-Identifier: MPL-2.0
# conformance.yml — parser conformance gate for the Phronesis grammar.
#
# Runs the real parser (Phronesis.parse/1 = Lexer.tokenize |> Parser.parse) over
# the conformance corpus:
# * conformance/valid/*.phr MUST parse (exit 0)
# * conformance/invalid/*.phr MUST fail (exit non-zero)
# The corpus is the executable contract for spec/grammar.ebnf (v0.2.0). Any drift
# between the parser and the documented grammar turns this gate red.
name: Conformance

on:
push:
branches: [main, master]
pull_request:
workflow_dispatch:

# Estate guardrail: cancel superseded runs (read-only check, safe to cancel).
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
conformance:
name: Grammar conformance (parser vs corpus)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

# Match the project toolchain (.tool-versions: elixir 1.16.0 / erlang 26.2.1).
# Pinned to the estate-vetted setup-beam SHA already used by hypatia-scan.yml.
- name: Setup Elixir
uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.18.2
with:
elixir-version: '1.16'
otp-version: '26'

- name: Fetch dependencies
run: mix deps.get

# Compile without --warnings-as-errors: the gate measures conformance, not
# lint. A genuine compile break still fails here (mix compile exits non-zero).
- name: Compile
run: mix compile

- name: Run conformance corpus
run: ./conformance/run_conformance.sh
2 changes: 1 addition & 1 deletion conformance/run_conformance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ if [[ -n "${1:-}" ]]; then
# shellcheck disable=SC2206 # deliberate word-splitting of caller's string
PARSER_CMD=(${1})
else
PARSER_CMD=(mix run -e 'Phronesis.CLI.parse(System.argv())' --)
PARSER_CMD=(mix run --no-start -e 'System.halt(case Phronesis.parse(File.read!(Enum.at(System.argv(), 0))) do {:ok, _} -> 0; _ -> 1 end)' --)
fi

PASS=0
Expand Down
14 changes: 6 additions & 8 deletions conformance/valid/v02_if_then.phr
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
# SPDX-License-Identifier: MPL-2.0
# Conformance: IF/THEN/ELSE conditional in policy
# Conformance: IF/THEN/ELSE conditional action (grammar §5 conditional_action)

POLICY conditional_check:
IF status == :valid THEN
ACCEPT "Status is valid"
ELSE
REJECT "Status is invalid"
PRIORITY 50
EXPIRES "2030-12-31"
CREATED_BY "test_suite"
value > 0
THEN IF value > 100 THEN ACCEPT("High value") ELSE REJECT("Low value")
PRIORITY: 50
EXPIRES: never
CREATED_BY: test_suite
16 changes: 8 additions & 8 deletions conformance/valid/v03_and_or.phr
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# SPDX-License-Identifier: MPL-2.0
# Conformance: compound conditions with AND/OR
# Conformance: compound conditions with AND/OR (grammar §6 logical_expr)
# AND/OR are equal-precedence, left-associative, so this parses as
# ((score > 50 AND status == "active") OR override == true).

POLICY compound_logic:
IF (score > 50 AND status == :active) OR override == true THEN
ACCEPT "Conditions met"
ELSE
REJECT "Conditions not met"
PRIORITY 75
EXPIRES "2030-12-31"
CREATED_BY "test_suite"
score > 50 AND status == "active" OR override == true
THEN ACCEPT("Conditions met")
PRIORITY: 75
EXPIRES: never
CREATED_BY: test_suite
15 changes: 6 additions & 9 deletions conformance/valid/v05_import.phr
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
# SPDX-License-Identifier: MPL-2.0
# Conformance: IMPORT clause
# Conformance: IMPORT clause + module-qualified call (grammar §3, §8)

IMPORT Std.BGP
IMPORT Std.RPKI

POLICY import_test:
rpki_result = Std.RPKI.validate(route)
IF rpki_result == :valid THEN
ACCEPT "RPKI validation passed"
ELSE
REJECT "RPKI validation failed"
PRIORITY 100
EXPIRES "2030-12-31"
CREATED_BY "test_suite"
Std.RPKI.validate(route) == "valid"
THEN ACCEPT("RPKI validation passed")
PRIORITY: 100
EXPIRES: never
CREATED_BY: test_suite
17 changes: 6 additions & 11 deletions conformance/valid/v07_nested.phr
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
# SPDX-License-Identifier: MPL-2.0
# Conformance: nested conditions
# Conformance: nested conditional actions (grammar §5 conditional_action)

POLICY nested_check:
IF level > 1 THEN
IF score > 80 THEN
ACCEPT "High score at high level"
ELSE
REJECT "Low score at high level"
ELSE
ACCEPT "Any score at base level"
PRIORITY 150
EXPIRES "2030-12-31"
CREATED_BY "test_suite"
level > 1
THEN IF score > 80 THEN ACCEPT("High score at high level") ELSE REJECT("Low score at high level")
PRIORITY: 150
EXPIRES: never
CREATED_BY: test_suite
Loading