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).
This commit is contained in:
John Dvorak
2026-08-03 10:58:29 -07:00
parent 0c2ddc282b
commit 0a744329e6
11 changed files with 1530 additions and 89 deletions
+22
View File
@@ -144,6 +144,7 @@ function validateDefinitions(program, tables, errors, warnings, source) {
}
function validateFacts(program, tables, errors, warnings, source) {
const seen = new Map();
for (const fact of program.facts || []) {
if (tables.builtins?.facts?.has(fact.name)) {
errors.push(createError({
@@ -154,6 +155,16 @@ function validateFacts(program, tables, errors, warnings, source) {
context: formatContext(source, findLocation(source, fact.name))
}));
}
if (seen.has(fact.name)) {
errors.push(createError({
message: `Duplicate fact definition '${fact.name}'.`,
rule: 'Each fact name must be unique within a program.',
fix: 'Rename one of the fact definitions to a unique name.',
location: findLocation(source, `fact ${fact.name}`),
context: formatContext(source, findLocation(source, fact.name))
}));
}
seen.set(fact.name, fact);
const arity = fact.params ? fact.params.length : 0;
if (!fact.params || arity === 0) {
warnings.push(createError({
@@ -285,6 +296,7 @@ function validateMeasures(program, tables, errors, warnings, source) {
}
function validateEvidence(program, tables, errors, warnings, source) {
const seen = new Map();
for (const ev of program.evidence || []) {
if (tables.builtins?.evidence?.has(ev.name)) {
errors.push(createError({
@@ -295,6 +307,16 @@ function validateEvidence(program, tables, errors, warnings, source) {
context: formatContext(source, findLocation(source, ev.name))
}));
}
if (seen.has(ev.name)) {
errors.push(createError({
message: `Duplicate evidence definition '${ev.name}'.`,
rule: 'Each evidence name must be unique within a program.',
fix: 'Rename one of the evidence definitions to a unique name.',
location: findLocation(source, `evidence ${ev.name}`),
context: formatContext(source, findLocation(source, ev.name))
}));
}
seen.set(ev.name, ev);
const returnType = ev.provides || DEFAULT_EVIDENCE_RETURN;
if (returnType !== DEFAULT_EVIDENCE_RETURN && !isTypeKnown(returnType, tables)) {
errors.push(createError({