Document how a rule checks a host-owned, mutable list - #9
Merged
Conversation
The engine had no written answer to one of the first questions asked of any rule engine: can a rule check whether a value is in a list the application owns, when the list changes on its own cadence, a rule's own decision may add to it, and every node in a cluster must see the addition. Readers were left to guess between a callFunction, a CEL binding, or something the engine does not have, and two of those guesses are wrong in ways that only show up later. The reason there is no lookup operator is structural rather than deferred, and nothing in the tree said so. Everything that decides which activation fires assumes that during one session a match's answer changes only because a fact moved through working memory: refraction is cleared for exactly the rules testing a changed path, the streaming matcher drops a condition- rejected match on the argument that an update will re-derive it, truth maintenance re-asks a tuple expecting the same answer, and replay supplies the same facts and expects the same decision. A structure consulted live changes its answer with nothing moving, and all four are blind to it, whatever thread-safety the read had. The answer chosen is the one the Clock fact already takes for time: the membership enters as a fact. The host looks each (list, entity) up before the session and inserts a ListMembership fact with member true OR false, so that a failed lookup is an absent fact rather than a false one; a rule that adds to the list pairs setField on that fact, an update on a tested path that makes the decline rule fire in the same session, with emit, which the host persists after the fire call. Across a cluster nothing is cached in the engine beyond the session's own snapshot, so the store is the only durable copy and there is nothing to invalidate. Where the list is itself a stream, entries are facts in a long-lived session and notExists asks the question. The guide gains the recipe as a complete rule file, including a fail-closed rule for the case where nothing answered; embedding.md gains the host half, the dual-write gap and the cluster note; the spec records the non-goal in section 1, a threat-table row in 7.3, a section 9.1 line, and a section 11.3 amendment noting setField-plus-emit as a first observed candidate shape for an extensible action. DocExamplesTest runs the guide's file through all three matchers and asserts what fired: the addition first and the decline because of it, an absent fact reaching only the fail-closed rule, and a fact with no member field deciding nothing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EcVJKNAXbPbyho6Gj9MdEv
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.
Summary
Documents how a rule checks membership in a list the application owns: a blocklist or allowlist that changes on its own cadence, that a rule's own decision may add to, and that every node in a cluster must see. Documentation and one test; no engine code changes.
callFunctionor a CEL binding.ListMembershipfact withmembertrue or false, so a failed lookup is an absent fact and never a false one. A rule that adds to the list pairssetField(same-session visibility via the tested-path update) withemit(host persists afterfireAllRules()). Across a cluster the store is the only durable copy; the engine caches nothing beyond the session's snapshot.docs/dsl-guide.mdgains "Checking a list your application owns" with a complete three-rule file (decline, add, and fail-closed when nothing answered).docs/embedding.mdgains the host half: read-through versus entries-as-facts, the failed-lookup rule, the dual-write gap, the cluster note, and the read-never-mutate warning for walkingCompiledRule.source().setField+emitas a first observed candidate shape for an extensible action, counted only once authoring confirms it recurs.choosing-this-engine.md,dsl-reference.mdandCHANGELOG.mdpoint at the recipe.Test plan
DocExamplesTestcompiles the new guide fixture and the newembedding.mdfact document.DocExamplesTest.ListMembershipReciperuns the guide's file through all three matchers viaMatcherEquivalenceand asserts what fired: the add rule first and the decline rule because of it in one fire call (effects and payloads pinned), an already-listed card declined once, an absent membership fact reaching only the fail-closed rule, and a membership fact with nomemberfield deciding nothing.testandstrictTest(28 cases).stringValue()in a snippet) and eight should-fixes addressed../gradlew build javadoc).🤖 Generated with Claude Code
https://claude.ai/code/session_01EcVJKNAXbPbyho6Gj9MdEv