Skip to content

chore(release): prep v0.2.0#16

Merged
kanywst merged 2 commits into
mainfrom
chore/v0-2-0-prep
May 9, 2026
Merged

chore(release): prep v0.2.0#16
kanywst merged 2 commits into
mainfrom
chore/v0-2-0-prep

Conversation

@kanywst
Copy link
Copy Markdown
Member

@kanywst kanywst commented May 9, 2026

Summary

  • Implements the OPA conformance harness (closes the gap left by the design-only proposal: OPA conformance harness #9). tools/rego2ast.py walks opa parse JSON into zopa AST; test/conformance/run.py drives 6 starter fixtures via the new evaluate_addressed wasm export. 15 / 15 cases pass. New test (conformance) CI job.
  • Fixes a v0.1 → v0.2 regression: proxy_on_response_headers and proxy_on_request_body now opt in via the corresponding rule (allow_response / allow_body) being present in the policy. Without this gate every response on a request-only policy would have been replaced with a 503.
  • Aligns missing-path semantics with Rego: resolveValue's .ref arm folds PathNotFound / PathNotObject to Value.nil instead of propagating an error. input.user.role == "admin" against {} now denies (0) instead of erroring (-1).
  • Brings README.md, CHANGELOG.md, ROADMAP.md, docs/ast.md, docs/proxy-wasm.md up to v0.2 reality (size 50K→60K, three-phase target rules, new builtins, modules bundle).
  • New wasm export evaluate_addressed(input, ast, package, target_rule) to address into a specific package within a Modules bundle.

Test plan

  • zig fmt --check src test build.zig build.zig.zon
  • zig build and zig build --release=small (61K)
  • zig build test-unit — all green, includes new missing ref inside compare denies case
  • zig build test (Node integration) — all green
  • zig build test-wasmtime (Python wasmtime) — all green
  • zig build test-conformance — 15 / 15 pass, 0 skip, 0 fail
  • zig build bench — runs end-to-end (1.79μs / 4.67μs locally)
  • markdownlint-cli2 clean across all 29 markdown files
  • Tag v0.2.0 after merge → release.yml + oci.yml fire

Backward compatibility

Existing v0.1 policies that only define an allow rule are unaffected:

  • header phase still evaluates allow and 403s on deny (unchanged)
  • body phase short-circuits as a no-op (unchanged)
  • response phase short-circuits as a no-op (unchanged before proposal: response-side policies #7's regression, restored here)

Implements the OPA conformance harness (#9 design follow-through),
bumps the docs / changelog / roadmap to reflect everything merged
since v0.1.0, and fixes a backward-compat regression where the new
response phase would 503 every response on policies that only
defined an 'allow' rule.

OPA conformance harness (closes the design-only PR #9):

- tools/rego2ast.py converts 'opa parse --format json' output to
  zopa-shaped AST JSON. Covers default rules, comparator builtins
  (eq/neq/lt/lte/gt/gte), bare ref / value, not (negated flag),
  every iterator, and the four string/collection builtins
  (startswith / endswith / contains / count). Skips with a
  descriptive Unsupported error on user-defined functions, with
  overrides, partial eval, multi-statement every bodies, some-in
  bridging, comprehensions, and numeric ref segments.
- test/conformance/run.py drives each fixture end-to-end: opa parse
  -> rego2ast.py -> zopa.wasm.evaluate_addressed -> compare to the
  fixture's expected decision. PASS / SKIP / FAIL per case; non-
  zero exit on any FAIL.
- 6 starter fixtures cover bool comparators, builtins, every, not,
  count, missing-path semantics. 15 / 15 cases pass locally.
- New 'zig build test-conformance' step. New CI job
  'test (conformance)' installs opa via curl and runs the suite.

New wasm export evaluate_addressed(input, ast, package, target_rule)
so generic-ABI hosts (incl. the conformance runner) can dispatch
into a specific package within a Modules bundle.

Backward-compat fix:

- proxy_on_request_body and proxy_on_response_headers short-circuit
  at configure time when the policy doesn't define allow_body /
  allow_response. Without this gate, a v0.1 policy with only an
  'allow' rule upgraded to v0.2 would have 503'd every response.
  Detection is a substring match against the quoted rule name in
  the policy JSON; false positives only cost an extra eval. Unit
  test for the corresponding eval-side fix is added.

Eval-side correctness fix:

- resolveValue's .ref arm now catches PathNotFound / PathNotObject
  and returns Value.nil instead of propagating the error. This
  matches Rego's 'missing path is undefined' posture: 'input.user
  .role == "admin"' against {} now denies (0) instead of erroring
  (-1). EvalTooDeep and other helper errors still propagate. New
  unit test 'missing ref inside compare denies' added.

Doc / changelog / roadmap:

- README.md: size 50K -> 60K everywhere, lifecycle section explains
  the per-phase target rules, supported-nodes line lists call /
  modules / kind, comparison table updated.
- CHANGELOG.md: full 0.2.0 entry covering Added / Changed.
- ROADMAP.md: Done in v0.2.0 section with all merged work; near-
  term reframed around streaming runtime, conformance corpus
  expansion, structured response replacement, per-context request
  snapshot.
- docs/ast.md: documents 'modules' bundle, 'package' field on
  module, 'kind' field on some/every, and the new 'call' node with
  the v1 builtin table.
- docs/proxy-wasm.md: per-phase target / input shape / deny status
  table; explicit 'phase opt-in via rule name' section explaining
  the substring-match gate. evaluate_target and evaluate_addressed
  added to the generic-ABI table.

src/proxy_wasm.zig: stale 'Tracked in ROADMAP.md' comments updated
to point at the body-aware-policies design doc and v2 plan.
@coderabbitai

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

Two medium / high feedback items from gemini-code-assist:

(high) src/proxy_wasm.zig:146 -- substring match for allow_body /
allow_response can produce false 403s. Scenario: a literal string
'allow_body' sits in policy data (e.g. a rule value), the substring
scan flips has_allow_body to true, body callback fires, eval finds
no rule with that name and returns false, shim returns 403. My
'cost-only' comment was wrong.

Replaced with a real AST scan: scanPhaseRules() parses the policy
JSON via the request arena (empty at configure time), walks every
module's rule list, and only sets the flag when an actual rule with
the matching name exists. Substring-in-string-data therefore can't
trip the flag.

(medium) tools/rego2ast.py:202 -- walk_term didn't handle 'set' or
'object' term types. Policies using set / object literals errored
out with Unsupported even though zopa supports both. Added both
arms; object keys must be strings (zopa's Value.object requires
that). walk_term_as_jsonvalue gained the same arms for nested
literals inside arrays / objects.

Two new conformance fixtures cover the addition: 07_object_literal_eq
exercises {key: value} literal equality (3 cases all pass), and
08_set_literal_count uses count({a,b,c}) == 3 to verify set literal
construction without needing a set-typed input (JSON has no set, so
'input.tags == {a,b}' would intentionally never match).

19 / 19 conformance cases pass. Release build size unchanged at
~61KB.
@kanywst kanywst merged commit cf188f9 into main May 9, 2026
12 checks passed
@kanywst kanywst deleted the chore/v0-2-0-prep branch May 9, 2026 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant