Commit Graph

9 Commits

Author SHA1 Message Date
John Dvorak 2a7f4c315b feat: sources wired as recency-gated injectables; transitive closure, NOT scoping, recompile-scope uninstall; targeted parse errors
CI / test (push) Successful in 20s
CI / publish (push) Has been skipped
BEHAVES AS transitive now emits bounded multi_hop configs (direct checks and
evidence references), fixing a silent no-op. NOT builds keep _subjectAsObject
scoping so unary predicates negate the right node, and value-typed evidence
objects gate by exact edge value. Recompiling a scope uninstalls its stale
relation configs (compileMultiple coexistence preserved). Sources become
injectable relations honored by requiredFacts with a within-X recency gate.
Duplicate definition fields and three common declaration mistakes (within on a
fact, two BEHAVES clauses, limit on a non-pattern body) now produce targeted
errors. Provider edges referencing unknown nodes are warned and dropped.
2026-08-03 20:27:13 -07:00
John Dvorak 4d498b07e8 feat: bounded self-recursion (transitive closure) for evidence
CI / publish (push) Successful in 9s
CI / test (push) Successful in 18s
An evidence whose config contains a chain step referencing ITSELF is now
unrolled at compile time into a bounded transitive closure: a union of paths
— base, hop+base, hop²+base, …, hop^N+base — where `hop` is the recursive
chain's steps before the self-reference and the depth N comes from the
pattern's `limit N` (or the compiler's maxRecursionDepth default, 3). The
base (the evidence's non-recursive statements) is verified as a condition
step at each path's terminal node, so the engine needs no new machinery.

- Chain configs carry the pattern's `limit` as maxDepth.
- resolveEvidenceReferences detects a self-reference (_findSelfReference),
  extracts the base (_extractBase), and unrolls (_unrollRecursiveEvidence).
- Pure recursion with no base case is a compile-time error; mutual cycles
  between distinct evidence remain a compile-time error.

Example: can_access_via = can_access OR (reports_to + can_access_via) up to
the declared limit grants access inherited up a reporting chain.

Tests: Recursion (unroll shape, base + multi-hop grants, depth-limit
enforcement, default depth, pure-recursion error, mutual-cycle guard).
2026-08-03 16:35:19 -07:00
John Dvorak aa38fbfd8c feat: provider-cache bypass knob; reject non-final comparator chain steps
CI / publish (push) Successful in 9s
CI / test (push) Successful in 18s
- check() gains cacheProviderResults (per-check option + policy default): the
  provider cache is a STORE-RETRIEVAL cache (wall-clock), independent of the
  caller's decision { now }; callers who pin time or want fresh retrieval can
  opt out per-check or globally.
- The generator now rejects a comparator evidence referenced at a NON-final
  chain position (a comparator compares values at (src, candidate) but
  provides no candidate set, so it cannot enumerate intermediate nodes).
  Final-position comparators still lower to condition steps.

Tests: cache bypass (per-check + policy), comparator final OK / intermediate
error.
2026-08-03 15:43:59 -07:00
John Dvorak 3ace783a59 feat: intermediate chain condition steps + _subjectIsObject unary scoping
CI / publish (push) Successful in 10s
CI / test (push) Successful in 19s
- _expandChainSteps: a logical/defeasible evidence referenced by a chain step
  is now a condition step ({ rule, conditionStep }) at ANY position. As the
  FINAL step the engine verifies it at (intermediate, object); as an
  INTERMEDIATE step the engine expands it from the current node (rule-based
  reachability: base edges' destinations filtered by the rule's
  defeaters/requirements) and continues traversal from each discovered node.
- buildPredicateRule / buildDirectRule: unary predicate calls whose subject
  entity IS the evidence's object parameter (trusted(other) inside
  peer_trusted(user, other)) are marked _subjectIsObject (was: only subject-var
  calls got _subjectAsObject). Requires @arbiter/core@^1.0.4.

Tests: ChainConditionStep intermediate expansion; oracle campaign gains a
chain_intermediate_condition construct (oracle = min(peer*(1-trusted), read)).
2026-08-03 13:36:16 -07:00
John Dvorak 351551af0f feat: chain condition steps — defeasible/logical evidence as final chain hop
CI / publish (push) Successful in 9s
CI / test (push) Successful in 18s
A chain's FINAL (object-side) step may now reference a defeasible/logical
evidence. The compiler lowers it to a condition step
({ rule: <config>, conditionStep: true }) that the engine verifies at
(intermediate, object) instead of traversing an edge. Requires
@arbiter/core@^1.0.3 (ChainRule condition-step support).

- _expandChainSteps: a logical/defeasible/comparator evidence is expressible
  as a final condition step; non-final such steps remain a compile error
  (a condition cannot discover intermediate nodes).
- Dependency collection (generator + DSLRuntime) descends into condition-step
  rule configs, so partial-graph requirements reach through them.

Tests: ChainConditionStep (defeasible + ALWAYS steps, independent checkability,
parallel aggregation), oracle campaign chain_condition_step construct
(oracle = min(pm, pv*(1-pb))), DSLRuntime transitive required facts through a
condition step.
2026-08-03 12:08:54 -07:00
John Dvorak 2dc478f5a3 feat: chain-step evidence composition — expand evidence steps in chains
CI / test (push) Successful in 18s
CI / publish (push) Successful in 9s
A chain step that references a derived evidence is now expanded at compile
time, keeping the engine a flat edge-traversal evaluator:

- DIRECT evidence step -> renamed to its underlying relation
  (member_of(user,*g){ group_read(g,doc) } where group_read = can_view
  becomes step 'can_view').
- CHAIN evidence step -> its steps are spliced into the parent chain
  (a sub-path flattens into the linear source->...->object traversal).
- Any other evidence type (defeasible/logical/comparator) as a step is a
  compile-time error: it is a condition, not an edge traversal.
- Cycles and self-references through chain steps are compile-time errors
  (the existing composition cycle guard now covers steps).

Rigor: oracle campaign gains a chain_step_composition construct; illegal
mutations gain a non-lowerable-chain-step case. Fixture suites updated to
retarget the self-recursive 'canRead/canAccess/...' terminals (an unsupported
recursion pattern that now fails loudly) to an any-typed 'reachable' fact,
preserving the nested-pattern parsing intent.
2026-08-03 11:34:52 -07:00
John Dvorak 88f10f9db4 feat: evidence composition — compile-time reference resolution for evidence sub-rules
CI / publish (push) Successful in 11s
CI / test (push) Successful in 15s
An evidence may now reference another derived evidence as a sub-rule
(WHEN can_read(user, doc) where can_read is itself an evidence). Resolution
is a compile-time linker pass: after every evidence config is generated, each
direct reference to an evidence is inlined with that evidence's own (resolved)
config, so the engine evaluates a fully-resolved, acyclic config tree.

- resolveEvidenceReferences(): post-generation pass over evidence configs,
  recursing into logical/defeasible containers (when/unless/never/always/
  requires/union/intersection), always.direct nests, and comparator operands.
- Forward references resolve (all configs exist before the pass runs).
- Cycles and self-references are compile-time errors.
- _subjectAsObject scoping is preserved through inlining.
- dependsOn is recomputed after resolution, so partial-graph requirements
  reach transitively through composed evidence.
- buildDirectRule/buildPredicateRule now apply subject-scoping to top-level
  PredicateCall evidence bodies (latent gap, previously missed).
- validation: reject relation names shared across facts/sources/evidence/
  measures (a collision silently overwrote configs and read as a false cycle).

Tests: EvidenceComposition (9), DSLRuntime transitive requiredFacts, oracle
campaign composition construct, illegal-mutations cycle + cross-kind cases.
2026-08-03 11:17:30 -07:00
John Dvorak 0a744329e6 feat: fix lowering, DSLRuntime wrapper, rigor oracle + rejection campaigns
CI / publish (push) Successful in 10s
CI / test (push) Successful in 13s
Lowering fixes (validate/lower/compile into known-correct core structures):
- tuple_to_userset: structural classification by object-side predicate
  (owner(*g, doc) { member_of(user, g) } -> tuple_to_userset with direction
  'in'/'out'); the old heuristic routed every outer-wildcard to chain.
- relational_comparator: operands now lower to real direct-rule configs
  (evaluateFrom derived from evidence param positions; expectedValue for
  literal args) instead of raw AST nodes the engine could not evaluate.
- defeasible: multi-level bodies (NEVER/REQUIRES/ALWAYS/WHEN/UNLESS) merge
  into one five-level rule instead of ANDed level-only rules that always
  resolved 0; nested PatternMatches flatten to N-step chains; unary predicate
  calls mark _subjectAsObject (subject-as-object semantics).
- validation: reject duplicate fact/evidence definitions.

DSLRuntime (higher-order DSL+Core wrapper):
- typed addNode/updateNodeData/addRelation/updateRelation against the DSL
  schema (known types, relation params, field types, value-carrying facts);
- check() derives the evidence's injectable partial-graph requirements,
  retrieves missing facts through caller data callbacks, injects them, and
  delegates, returning requiredFacts/providedFacts/missingFacts.

js-rigor campaigns:
- generative oracle: generate legal DSL per construct and compare every
  verdict against an independent hand-computed oracle (8 constructs x P grid)
  plus an exhaustive deterministic sweep;
- illegal mutations: one-flaw perturbations of a valid program must be
  reliably rejected (duplicate evidence/fact, arity/type mismatches, reserved
  built-ins, malformed syntax), with a control that must compile.

Depends on @arbiter/core@^1.0.2 (reason codes + _subjectAsObject).
2026-08-03 10:58:29 -07:00
John Dvorak ae21605fb7 evidence-dsl: extract Evidence DSL v2 compiler from @arbiter/core
CI / test (push) Successful in 11s
CI / publish (push) Successful in 9s
The Evidence DSL (ADR-000) is a thin declarative layer that compiles to
engine rule types. It has zero runtime coupling to the core engine
(DSLCompiler takes an arbiter as a duck-typed argument; the only shared
code was the ip-utils helpers, now local). Extracting it into its own
package keeps the core artifact free of the DSL surface.

- @arbiter/evidence-dsl depends on @arbiter/core (config formats are the
  compilation target)
- deep-path exports for the compiler, parser, generator, validation,
  and built-in functions (the surface the core's DSL tests consume)
- tests moved alongside; generate-parser script + peggy devDep local
- CI: test on push, publish on v* tags
2026-08-03 08:48:39 -07:00