Download dev/constraints/engine.js from Harp404/Space: direct link, hf CLI and curl.
- Browser
- Download file 16.8 kB
-
https://huggingface.co/spaces/Harp404/Space/resolve/main/dev/constraints/engine.js
- Command line
-
hf download hf://spaces/Harp404/Space/dev/constraints/engine.js
-
curl -L -o engine.js https://huggingface.co/spaces/Harp404/Space/resolve/main/dev/constraints/engine.js
16.8 kB
| /** | |
| * Constraint Awareness — the engine. dev/constraints/engine.js | |
| * ============================================================================= | |
| * A generic evaluator for rulebooks. Given a set of rules and a context object, | |
| * it returns ONE completion signal: | |
| * | |
| * COMPLETE every applicable rule evaluated and satisfied | |
| * PARTIAL hard rules pass; a soft rule is violated or a waiver is on record | |
| * BLOCKED a HARD rule is violated | |
| * UNRESOLVED a rule could not be evaluated — the evidence does not exist yet | |
| * | |
| * Precedence: BLOCKED > UNRESOLVED > PARTIAL > COMPLETE. | |
| * A hard violation is decisive even with unknowns still open: you already know | |
| * enough to say no. | |
| * | |
| * TWO INVARIANTS CARRY THE WHOLE IDEA | |
| * | |
| * 1. UNRESOLVED IS NOT A PASS. Most systems show a green tick for "no | |
| * violation found", silently conflating *checked and clean* with *never | |
| * checked*. Here an unevaluated rule holds the signal open and names | |
| * exactly what is missing. | |
| * | |
| * 2. YOU CANNOT WAIVE WHAT YOU NEVER MEASURED. A waiver only converts a rule | |
| * that genuinely evaluated to VIOLATED, on a rule that is waivable at all. | |
| * Applied to an unevaluated rule it is refused, with a reason. | |
| * | |
| * THIS FILE KNOWS NOTHING ABOUT ITS DOMAIN. | |
| * It has never heard of a satellite. It takes a rulebook and a context and | |
| * returns a signal. That is what makes the capability portable — the same engine | |
| * gates an orbital maneuver, a software release, or anything else with limits, | |
| * conditions and non-negotiable requirements. See ./rulebooks/. | |
| * | |
| * Zero dependencies. Pure functions. Deterministic: same input, same output, | |
| * every time. See engine.test.js. | |
| * ============================================================================= | |
| */ | |
| ; | |
| // --------------------------------------------------------------------------- | |
| // Vocabulary | |
| // --------------------------------------------------------------------------- | |
| /** The state of a single rule. */ | |
| const STATE = { | |
| SATISFIED: 'SATISFIED', // evaluated, limit met | |
| VIOLATED: 'VIOLATED', // evaluated, limit breached | |
| UNEVALUATED: 'UNEVALUATED', // could not be evaluated — the input does not exist | |
| NOT_APPLICABLE: 'NOT_APPLICABLE', // this rule does not govern this case | |
| WAIVED: 'WAIVED', // breached, but a named party accepted the exception | |
| }; | |
| /** The completion signal for the whole body of work. */ | |
| const SIGNAL = { | |
| COMPLETE: 'COMPLETE', | |
| PARTIAL: 'PARTIAL', | |
| BLOCKED: 'BLOCKED', | |
| UNRESOLVED: 'UNRESOLVED', | |
| }; | |
| /** Rule classes. HARD blocks; SOFT degrades to PARTIAL but never blocks. */ | |
| const CLASS = { HARD: 'HARD', SOFT: 'SOFT' }; | |
| // --------------------------------------------------------------------------- | |
| // Evaluator helpers, handed to every rule so rulebooks stay declarative. | |
| // Each returns the {state, actual, limit, detail} shape the engine expects. | |
| // --------------------------------------------------------------------------- | |
| const R = { | |
| /** Limit met. */ | |
| pass(detail, actual, limit) { return { state: STATE.SATISFIED, detail, actual, limit }; }, | |
| /** Limit breached. */ | |
| fail(detail, actual, limit) { return { state: STATE.VIOLATED, detail, actual, limit }; }, | |
| /** | |
| * The input does not exist, so we do not know. NEVER use pass() for this — | |
| * that is the exact mistake this whole module exists to prevent. | |
| */ | |
| unknown(detail, missing) { return { state: STATE.UNEVALUATED, detail, missing }; }, | |
| /** This rule does not govern this case. Excluded from the counts. */ | |
| na(detail, actual, limit) { return { state: STATE.NOT_APPLICABLE, detail, actual, limit }; }, | |
| /** Convenience: fail-safe numeric comparison that returns unknown() for a missing value. */ | |
| atMost(value, limit, fmt, unit) { | |
| const v = numOrNull(value); | |
| if (v === null) return R.unknown('value not available'); | |
| const f = fmt || ((x) => `${x}${unit ? ' ' + unit : ''}`); | |
| const shown = { actual: f(v), limit: `<= ${f(limit)}` }; | |
| return v <= limit ? R.pass('within limit', shown.actual, shown.limit) | |
| : R.fail('over limit', shown.actual, shown.limit); | |
| }, | |
| atLeast(value, limit, fmt, unit) { | |
| const v = numOrNull(value); | |
| if (v === null) return R.unknown('value not available'); | |
| const f = fmt || ((x) => `${x}${unit ? ' ' + unit : ''}`); | |
| const shown = { actual: f(v), limit: `>= ${f(limit)}` }; | |
| return v >= limit ? R.pass('within limit', shown.actual, shown.limit) | |
| : R.fail('under limit', shown.actual, shown.limit); | |
| }, | |
| }; | |
| function numOrNull(v) { const n = Number(v); return Number.isFinite(n) ? n : null; } | |
| // --------------------------------------------------------------------------- | |
| // Core evaluation | |
| // --------------------------------------------------------------------------- | |
| /** | |
| * Evaluate a rulebook against a context. | |
| * | |
| * @param {object} opts | |
| * @param {object} opts.rulebook { id, title, domain, rules: [...] } | |
| * @param {object} opts.context whatever the rules need; opaque to the engine | |
| * @param {Array} opts.waivers [{ rule_id, party, reason, ts }] | |
| * @param {number} opts.now epoch ms — injectable so tests are deterministic | |
| * @returns {object} the completion report | |
| */ | |
| function evaluate({ rulebook, context, waivers = [], now = Date.now() } = {}) { | |
| if (!rulebook || !Array.isArray(rulebook.rules)) { | |
| throw new TypeError('evaluate() needs a rulebook with a rules array'); | |
| } | |
| const ctx = { ...(context || {}), now }; | |
| const waiverList = Array.isArray(waivers) ? waivers : []; | |
| const rules = rulebook.rules.map((rule) => evaluateOne(rule, ctx, waiverList, now)); | |
| // NOT_APPLICABLE rules are excluded from every count — a rule that does not | |
| // govern this case is neither work done nor work outstanding. | |
| const applicable = rules.filter((r) => r.state !== STATE.NOT_APPLICABLE); | |
| const blocking = rules.filter((r) => r.class === CLASS.HARD && r.state === STATE.VIOLATED); | |
| const unevaluated = applicable.filter((r) => r.state === STATE.UNEVALUATED); | |
| const advisory = rules.filter((r) => r.class === CLASS.SOFT && r.state === STATE.VIOLATED); | |
| const waived = rules.filter((r) => r.state === STATE.WAIVED); | |
| const satisfied = applicable.filter((r) => r.state === STATE.SATISFIED); | |
| const counts = { | |
| total: rules.length, | |
| applicable: applicable.length, | |
| satisfied: satisfied.length, | |
| violated: blocking.length + advisory.length, | |
| blocking: blocking.length, | |
| advisory: advisory.length, | |
| waived: waived.length, | |
| unevaluated: unevaluated.length, | |
| not_applicable: rules.length - applicable.length, | |
| }; | |
| const { signal, headline } = deriveSignal({ counts, blocking, unevaluated, advisory, waived }); | |
| return { | |
| rulebook: { id: rulebook.id, title: rulebook.title, domain: rulebook.domain }, | |
| signal, | |
| headline, | |
| // Authorisation is the actionable read of the signal. UNRESOLVED is NOT | |
| // authorised — not knowing is not the same as being allowed. | |
| authorised: signal === SIGNAL.COMPLETE || signal === SIGNAL.PARTIAL, | |
| counts, | |
| // Closed work over applicable work. Waived counts as closed (it was decided), | |
| // unevaluated never does. | |
| progress: counts.applicable | |
| ? Math.round(((counts.satisfied + counts.waived) / counts.applicable) * 100) | |
| : 0, | |
| blocking: blocking.map(brief), | |
| unevaluated: unevaluated.map(brief), | |
| advisory: advisory.map(brief), | |
| waived: waived.map(brief), | |
| next_actions: nextActions({ signal, blocking, unevaluated, advisory }), | |
| // Earliest moment this signal degrades on its own if nobody acts. | |
| deadline: earliestDeadline(rules, now), | |
| // Evidence that, if obtained, would close currently-unevaluated rules. | |
| // Consumed by the value-of-information planner. | |
| evidence_needed: evidenceNeeded(unevaluated), | |
| rules, | |
| evaluated_at: new Date(now).toISOString(), | |
| }; | |
| } | |
| /** Evaluate one rule, apply waiver semantics, and never let a throw become a pass. */ | |
| function evaluateOne(rule, ctx, waiverList, now) { | |
| const row = { | |
| id: rule.id, | |
| key: rule.key, | |
| title: rule.title, | |
| class: rule.class === CLASS.SOFT ? CLASS.SOFT : CLASS.HARD, | |
| waivable: rule.waivable !== false, | |
| authority: rule.authority || null, | |
| requirement: rule.requirement || null, | |
| rationale: rule.rationale || null, | |
| state: STATE.UNEVALUATED, | |
| actual: null, | |
| limit: null, | |
| detail: null, | |
| missing: null, | |
| waiver: null, | |
| deadline: null, | |
| resolved_by: rule.resolvedBy || null, | |
| }; | |
| // An `applies` gate that itself cannot be decided must not silently exclude | |
| // the rule — an undecidable applicability question is its own unknown. | |
| if (typeof rule.applies === 'function') { | |
| let applicable; | |
| try { applicable = rule.applies(ctx); } catch (e) { | |
| row.state = STATE.UNEVALUATED; | |
| row.detail = 'cannot determine whether this rule applies: ' + e.message; | |
| return row; | |
| } | |
| if (applicable === false) { | |
| row.state = STATE.NOT_APPLICABLE; | |
| row.detail = rule.notApplicableDetail || 'does not govern this case'; | |
| return row; | |
| } | |
| if (applicable == null) { | |
| row.state = STATE.UNEVALUATED; | |
| row.detail = 'cannot determine whether this rule applies'; | |
| return row; | |
| } | |
| } | |
| let out; | |
| try { | |
| out = rule.evaluate(ctx); | |
| } catch (e) { | |
| // A rule that throws is an unknown, never a pass. Failing open here would | |
| // defeat the entire purpose of the module. | |
| out = { state: STATE.UNEVALUATED, detail: 'evaluator error: ' + e.message }; | |
| } | |
| if (!out || !out.state) { | |
| out = { state: STATE.UNEVALUATED, detail: 'evaluator returned no state' }; | |
| } | |
| row.state = out.state; | |
| row.actual = out.actual ?? null; | |
| row.limit = out.limit ?? null; | |
| row.detail = out.detail ?? null; | |
| row.missing = out.missing ?? null; | |
| // When this rule will degrade on its own, if it can. | |
| if (typeof rule.deadline === 'function') { | |
| try { | |
| const d = rule.deadline(ctx); | |
| if (Number.isFinite(d) && d > now) row.deadline = d; | |
| } catch { /* a deadline we cannot compute is simply absent */ } | |
| } | |
| applyWaiver(row, waiverList.find((w) => w && w.rule_id === rule.id) || null); | |
| return row; | |
| } | |
| /** | |
| * Waiver semantics — the second invariant lives here. | |
| * A waiver converts VIOLATED -> WAIVED, and only when the rule is waivable. | |
| * It can never touch UNEVALUATED, and never touches a non-negotiable rule. | |
| */ | |
| function applyWaiver(row, waiver) { | |
| if (!waiver) return; | |
| if (!row.waivable) { | |
| row.waiver_rejected = 'this rule is non-negotiable and cannot be waived'; | |
| return; | |
| } | |
| if (row.state === STATE.UNEVALUATED) { | |
| row.waiver_rejected = 'cannot waive a rule that has not been evaluated'; | |
| return; | |
| } | |
| if (row.state !== STATE.VIOLATED) { | |
| row.waiver_rejected = 'no violation to waive'; | |
| return; | |
| } | |
| row.state = STATE.WAIVED; | |
| row.waiver = { | |
| party: waiver.party || waiver.operator || 'unnamed', | |
| reason: waiver.reason || 'no reason given', | |
| ts: waiver.ts || null, | |
| }; | |
| } | |
| /** Precedence: BLOCKED > UNRESOLVED > PARTIAL > COMPLETE. */ | |
| function deriveSignal({ counts, blocking, unevaluated, advisory, waived }) { | |
| if (blocking.length) { | |
| const nonNegotiable = blocking.filter((r) => !r.waivable); | |
| return { | |
| signal: SIGNAL.BLOCKED, | |
| headline: `${blocking.length} hard rule${blocking.length > 1 ? 's' : ''} violated — authorisation refused` | |
| + (nonNegotiable.length ? ` · ${nonNegotiable.length} non-negotiable, no override path` : ''), | |
| }; | |
| } | |
| if (unevaluated.length) { | |
| return { | |
| signal: SIGNAL.UNRESOLVED, | |
| headline: `${counts.satisfied}/${counts.applicable} rules closed — ${unevaluated.length} cannot be evaluated yet`, | |
| }; | |
| } | |
| if (advisory.length || waived.length) { | |
| const bits = []; | |
| if (advisory.length) bits.push(`${advisory.length} advisory violation${advisory.length > 1 ? 's' : ''}`); | |
| if (waived.length) bits.push(`${waived.length} waived`); | |
| return { | |
| signal: SIGNAL.PARTIAL, | |
| headline: `all hard rules pass · ${bits.join(' · ')} — proceeding with exceptions on record`, | |
| }; | |
| } | |
| return { | |
| signal: SIGNAL.COMPLETE, | |
| headline: `all ${counts.applicable} applicable rules evaluated and satisfied — cleared`, | |
| }; | |
| } | |
| /** Ordered, specific list of what must happen to reach COMPLETE. */ | |
| function nextActions({ signal, blocking, unevaluated, advisory }) { | |
| const out = []; | |
| for (const r of blocking) { | |
| out.push({ | |
| rule_id: r.id, | |
| severity: 'BLOCKING', | |
| action: r.waivable | |
| ? `Resolve ${r.id} (${r.title}) or file a named waiver — ${r.detail}` | |
| : `Resolve ${r.id} (${r.title}) — NON-NEGOTIABLE, no waiver available: ${r.detail}`, | |
| }); | |
| } | |
| for (const r of unevaluated) { | |
| out.push({ | |
| rule_id: r.id, | |
| severity: 'UNKNOWN', | |
| action: `Obtain the evidence for ${r.id} (${r.title}) — ${r.detail}`, | |
| evidence: r.resolved_by || null, | |
| }); | |
| } | |
| for (const r of advisory) { | |
| out.push({ rule_id: r.id, severity: 'ADVISORY', action: `Acknowledge ${r.id} (${r.title}) — ${r.detail}` }); | |
| } | |
| if (!out.length && signal === SIGNAL.COMPLETE) { | |
| out.push({ rule_id: null, severity: 'NONE', action: 'No outstanding constraint work.' }); | |
| } | |
| return out; | |
| } | |
| /** | |
| * The earliest moment the signal degrades with no action taken. Only rules that | |
| * currently pass can have a live deadline — a rule already violated has nothing | |
| * left to lose. | |
| */ | |
| function earliestDeadline(rules, now) { | |
| let best = null; | |
| for (const r of rules) { | |
| if (!r.deadline) continue; | |
| if (r.state !== STATE.SATISFIED && r.state !== STATE.WAIVED) continue; | |
| if (best === null || r.deadline < best.at) { | |
| best = { at: r.deadline, rule_id: r.id, title: r.title, class: r.class }; | |
| } | |
| } | |
| if (!best) return null; | |
| return { ...best, at_iso: new Date(best.at).toISOString(), in_ms: best.at - now }; | |
| } | |
| /** Distinct evidence items that would close currently-unevaluated rules. */ | |
| function evidenceNeeded(unevaluatedRules) { | |
| const byKey = new Map(); | |
| for (const r of unevaluatedRules) { | |
| for (const ev of r.resolved_by || []) { | |
| const key = typeof ev === 'string' ? ev : ev.id; | |
| if (!key) continue; | |
| const entry = byKey.get(key) || { | |
| evidence: key, | |
| label: (typeof ev === 'object' && ev.label) || key, | |
| cost: (typeof ev === 'object' && ev.cost) || null, | |
| closes: [], | |
| }; | |
| entry.closes.push(r.id); | |
| byKey.set(key, entry); | |
| } | |
| } | |
| return [...byKey.values()].sort((a, b) => b.closes.length - a.closes.length); | |
| } | |
| function brief(r) { | |
| return { | |
| id: r.id, key: r.key, title: r.title, class: r.class, waivable: r.waivable, | |
| actual: r.actual, limit: r.limit, detail: r.detail, | |
| authority: r.authority, resolved_by: r.resolved_by, | |
| }; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Aggregate — "show whether THE RELATED WORK is complete, partial, blocked or | |
| // unresolved" applies to the whole body of work, not just one item. | |
| // --------------------------------------------------------------------------- | |
| /** | |
| * Roll many reports into one fleet-level completion signal. | |
| * @param {Array} reports | |
| */ | |
| function rollup(reports) { | |
| const list = (reports || []).filter(Boolean); | |
| const by = { COMPLETE: 0, PARTIAL: 0, BLOCKED: 0, UNRESOLVED: 0 }; | |
| let applicable = 0, closed = 0; | |
| for (const r of list) { | |
| if (by[r.signal] !== undefined) by[r.signal]++; | |
| applicable += r.counts.applicable; | |
| closed += r.counts.satisfied + r.counts.waived; | |
| } | |
| // The overall signal takes the worst state present — an aggregate is only as | |
| // resolved as its least-resolved member. | |
| let signal = SIGNAL.COMPLETE; | |
| if (by.BLOCKED) signal = SIGNAL.BLOCKED; | |
| else if (by.UNRESOLVED) signal = SIGNAL.UNRESOLVED; | |
| else if (by.PARTIAL) signal = SIGNAL.PARTIAL; | |
| const soonest = list | |
| .map((r) => r.deadline) | |
| .filter(Boolean) | |
| .sort((a, b) => a.at - b.at)[0] || null; | |
| return { | |
| signal, | |
| items: list.length, | |
| by, | |
| rules_applicable: applicable, | |
| rules_closed: closed, | |
| progress: applicable ? Math.round((closed / applicable) * 100) : 0, | |
| headline: list.length | |
| ? `${list.length} items · ${by.COMPLETE} complete · ${by.PARTIAL} partial · ${by.BLOCKED} blocked · ${by.UNRESOLVED} unresolved` | |
| : 'no items', | |
| next_deadline: soonest, | |
| }; | |
| } | |
| // --------------------------------------------------------------------------- | |
| module.exports = { | |
| evaluate, | |
| rollup, | |
| STATE, | |
| SIGNAL, | |
| CLASS, | |
| R, | |
| // exported for rulebooks and tests | |
| _internal: { deriveSignal, applyWaiver, earliestDeadline, evidenceNeeded }, | |
| }; | |