Support block-form nesting and unquoted assertions; compile doc examples in CI - #10
Merged
Merged
Conversation
…s in CI Compiling every ```facet block in the documentation turned up two more constructs the spec documents but the parser never accepted. Block-form nesting (§5.1–§5.2). Facet bodies only accepted inline literals, so a key followed by an indented map or list failed with `F003`. That is how the specification prints its own `@context` minimal schema (§12.2) and how docs/02-tutorial.md writes `@policy` rule lists — neither compiled. `block_body` now parses a nested block when a key has no inline value, and a list item may be a block map (`- id: ...` with further pairs indented under it). Inline and block spellings produce the same AST, and the same `policy_hash` for an equivalent policy. Unquoted `@test` assertions (§15.1). The specification writes assertions as bare expressions (`- canonical contains "hello"`), but the parser required a quoted string, so §15.1's own example failed. `assert:` items are now read as raw lines; quoted items are still accepted so existing documents (including examples/spec/08_test_suite.facet) are unaffected. To keep documentation from drifting back out of conformance, scripts/check_doc_examples.py builds every fenced facet block with the reference compiler, and a new CI job runs it. Blocks that are meant not to compile — negative examples, syntax fragments, imports of paths that do not exist — are listed in scripts/doc_examples_allowlist.json with a reason, keyed by content hash so an entry survives the block moving. An allowlisted block that starts compiling is reported as stale, so the list cannot rot. Also fixed while here: - docs/02-tutorial.md's `@policy` example omitted `name`, which §16.2.2 requires for `tool_*` rules, so the documented policy raised `F452`. - CHANGELOG.md carried a second, duplicated copy of the v2.0 changelog. Verified: 484 workspace tests pass (8 new), smoke and matrix suites green, doc-example check green over 43 blocks (13 allowlisted), fmt clean, clippy -D warnings clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #9. I compiled every ```facet block in the documentation of both repositories — 43 blocks — and two more documented constructs turned out never to have been implemented.
Block-form nesting (§5.1–§5.2)
Facet bodies accepted only inline literals, so a key followed by an indented map or list failed with
F003. That is how the specification prints its own@contextminimal schema:…and how
docs/02-tutorial.mdwrites@policyrule lists. Neither compiled.block_bodynow parses a nested block when a key carries no inline value, and a list item may itself be a block map (- id: ...with further pairs indented beneath). Verified equivalence: block and inline spellings produce the same AST, and an equivalent policy yields an identicalpolicy_hash(sha256:7397fb8c…both ways) whiledocument_hashdiffers as it should.Unquoted
@testassertions (§15.1)The spec writes assertions as bare expressions, but the parser demanded a quoted string, so §15.1's own example failed:
assert:items are now read as raw lines. Quoted items still work, soexamples/spec/08_test_suite.facetand any existing document are unaffected. A false assertion still fails the test — this is not a blanket "accept everything".Keeping docs conformant
scripts/check_doc_examples.pybuilds every fenced facet block with the reference compiler, wired into CI as Doc Examples Compile. Blocks that are meant not to compile — negative examples, syntax fragments, imports of paths that do not exist — live inscripts/doc_examples_allowlist.jsonwith a reason, keyed by content hash so entries survive a block moving within a file. An allowlisted block that starts compiling is reported as stale, so the list cannot rot. Verified both directions: an intentionally wrong entry is caught.This is the guard for the class of problem that started this whole thread: documentation that teaches syntax the compiler rejects is what newcomers and LLM assistants copy first.
Also fixed
docs/02-tutorial.md: the@policyexample omittedname, which §16.2.2 requires fortool_*rules — the documented policy raisedF452. (Found only after the parser stopped failing first.)CHANGELOG.mdcarried a second, duplicated copy of the v2.0 changelog.Verification
tests/spec_conformance_regressions.rs, including equality of block vs inline ASTs, equality of quoted vs unquoted assertions, and that a danglingkey:is still an errorcargo fmt --all -- --checkandcargo clippy --all-targets --all-features -- -D warningsclean🤖 Generated with Claude Code