Download dev/constraints/voting.js from Harp404/Space: direct link, hf CLI and curl.
- Browser
- Download file 11 kB
-
https://huggingface.co/spaces/Harp404/Space/resolve/main/dev/constraints/voting.js
- Command line
-
hf download hf://spaces/Harp404/Space/dev/constraints/voting.js
-
curl -L -o voting.js https://huggingface.co/spaces/Harp404/Space/resolve/main/dev/constraints/voting.js
11 kB
| /** | |
| * Deterministic operator voting. dev/constraints/voting.js | |
| * ============================================================================= | |
| * WHAT THIS REPLACES | |
| * | |
| * Before this file, the cluster voted like this: | |
| * | |
| * const voteYes = conj.risk_index > 40 || Math.random() < 0.70; | |
| * | |
| * A coin flip. The same event could be approved and denied on consecutive runs, | |
| * no operator could explain its own vote, and the TLA+ safety proof described a | |
| * protocol the implementation did not actually follow. | |
| * | |
| * Now every vote is a pure function of the constraint report. Same event, same | |
| * answer, every time β and each vote carries the rule id that drove it. | |
| * | |
| * HOW A NODE DECIDES | |
| * | |
| * 1. Report BLOCKED -> NO. A hard rule is violated; nobody authorises that. | |
| * 2. Report UNRESOLVED -> NO. "We do not vote on what we have not evaluated." | |
| * 3. Otherwise, check this operator's own published sensitivities. | |
| * A rule that is VIOLATED, or WAIVED by somebody else, is a NO for an | |
| * operator that holds it as a red line. | |
| * 4. Otherwise YES. | |
| * | |
| * WHY A WAIVER DOES NOT BIND EVERY OPERATOR | |
| * | |
| * A waiver is one party's acceptance of a risk. It is not consent from the | |
| * others. An operator sensitive to a waived rule still votes NO β which is what | |
| * makes this a negotiation between independent parties rather than a rubber | |
| * stamp, and is the point of having a quorum at all. | |
| * | |
| * Zero dependencies. Pure functions. | |
| * ============================================================================= | |
| */ | |
| ; | |
| const { STATE, SIGNAL } = require('./engine'); | |
| // --------------------------------------------------------------------------- | |
| // Operator postures. | |
| // | |
| // Real operators do not share risk tolerances β SSC 8.f asks crewed-vehicle | |
| // operators to publish theirs precisely because they differ. These are each | |
| // node's published red lines: the rules it will not proceed past, even when the | |
| // aggregate signal permits it. | |
| // --------------------------------------------------------------------------- | |
| // NAMING β READ THIS BEFORE QUOTING ANY OF IT | |
| // | |
| // These are NOT agencies casting votes. No space agency has any involvement in | |
| // this system, and presenting one as having voted would be a false claim about | |
| // a real organisation. | |
| // | |
| // They are four REVIEW PROFILES: independent stances that weight different | |
| // rules in our own rulebook, modelled on the published risk priorities of the | |
| // operator types named in each profile's `modelled_on` field. Their purpose is | |
| // to test whether a decision is FRAGILE β if a maneuver is approved under one | |
| // weighting and refused under another, the disagreement is the finding, and it | |
| // is surfaced rather than averaged away. | |
| // | |
| // Each profile's sensitivity is declared in advance and served over the API | |
| // alongside the rulebook, so a reviewer can check that the profile voted the | |
| // way its published stance says it should. | |
| const POSTURES = { | |
| 1: { | |
| name: 'ISRO (modelled)', | |
| modelled_on: 'Earth-observation operators with long revisit cycles (e.g. ISRO)', | |
| stance: 'data currency and decision timeline', | |
| sensitive_to: ['FR-10', 'FR-09'], | |
| reason: 'operates Earth-observation assets with long revisit cycles; will not act on stale orbit determination or inside a compressed decision window', | |
| }, | |
| 2: { | |
| name: 'ESA (modelled)', | |
| modelled_on: 'operators requiring covariance-bearing data before accepting a Pc (e.g. ESA)', | |
| stance: 'covariance basis and probability dilution', | |
| sensitive_to: ['FR-11', 'FR-12'], | |
| reason: 'requires covariance-bearing data before accepting a Pc figure; treats the dilution region as disqualifying', | |
| }, | |
| 3: { | |
| name: 'JAXA (modelled)', | |
| modelled_on: 'operators treating element-set currency and dilution as coupled (e.g. JAXA)', | |
| stance: 'orbit-determination quality', | |
| sensitive_to: ['FR-10', 'FR-12'], | |
| reason: 'holds element-set currency and dilution as coupled indicators of whether the estimate can be trusted at all', | |
| }, | |
| 4: { | |
| name: 'SpaceX (modelled)', | |
| modelled_on: 'large-constellation operators where per-maneuver cost compounds (e.g. SpaceX)', | |
| stance: 'propellant cost and execution timeline', | |
| sensitive_to: ['FR-04', 'FR-09'], | |
| reason: 'operates a large constellation where per-maneuver propellant compounds across thousands of assets', | |
| }, | |
| }; | |
| /** | |
| * Published review profiles β served alongside the rulebook so a vote can be | |
| * checked against its declared stance before it is cast, not rationalised | |
| * after. | |
| */ | |
| function postures() { | |
| return Object.entries(POSTURES).map(([id, p]) => ({ | |
| node_id: Number(id), name: p.name, stance: p.stance, | |
| sensitive_to: p.sensitive_to, reason: p.reason, | |
| })); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // One node's vote | |
| // --------------------------------------------------------------------------- | |
| /** | |
| * @param {number} nodeId | |
| * @param {object} report a constraint report from engine.evaluate() | |
| * @returns {{vote:'YES'|'NO', rule_id:string|null, reason:string, basis:string}} | |
| */ | |
| function nodeVote(nodeId, report) { | |
| const posture = POSTURES[nodeId] || { name: `node ${nodeId}`, stance: 'general safety', sensitive_to: [] }; | |
| if (report.signal === SIGNAL.BLOCKED) { | |
| const b = report.blocking[0]; | |
| return { | |
| vote: 'NO', rule_id: b.id, basis: 'HARD_VIOLATION', | |
| reason: `${b.id} ${b.title} β hard rule violated${b.waivable === false ? ' (non-negotiable)' : ''}`, | |
| }; | |
| } | |
| if (report.signal === SIGNAL.UNRESOLVED) { | |
| const u = report.unevaluated[0]; | |
| return { | |
| vote: 'NO', rule_id: u.id, basis: 'UNEVALUATED', | |
| reason: `${u.id} ${u.title} β not evaluated; this operator does not vote on an unknown`, | |
| }; | |
| } | |
| const byId = new Map(report.rules.map((r) => [r.id, r])); | |
| for (const id of posture.sensitive_to) { | |
| const r = byId.get(id); | |
| if (!r) continue; | |
| if (r.state === STATE.VIOLATED) { | |
| return { | |
| vote: 'NO', rule_id: id, basis: 'OPERATOR_RED_LINE', | |
| reason: `${id} ${r.title} β ${posture.name} holds this as a red line (${posture.stance})`, | |
| }; | |
| } | |
| if (r.state === STATE.WAIVED) { | |
| const who = (r.waiver && r.waiver.party) || 'another operator'; | |
| return { | |
| vote: 'NO', rule_id: id, basis: 'WAIVER_NOT_ACCEPTED', | |
| reason: `${id} ${r.title} β waived by ${who}; ${posture.name} does not accept another party's exception`, | |
| }; | |
| } | |
| } | |
| return { | |
| vote: 'YES', rule_id: null, basis: report.signal === SIGNAL.COMPLETE ? 'ALL_RULES_SATISFIED' : 'NO_RED_LINE_TOUCHED', | |
| reason: report.signal === SIGNAL.COMPLETE | |
| ? 'all applicable flight rules satisfied' | |
| : `no rule in ${posture.name}'s published sensitivities is violated`, | |
| }; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // The poll | |
| // --------------------------------------------------------------------------- | |
| /** | |
| * Run the go/no-go poll across the online nodes. | |
| * | |
| * @param {Array} onlineNodes [{ id, name, online }] | |
| * @param {object} report | |
| * @param {number} quorum | |
| */ | |
| function poll(onlineNodes, report, quorum = 3) { | |
| const votes = (onlineNodes || []).filter((n) => n && n.online !== false).map((n) => { | |
| const v = nodeVote(n.id, report); | |
| return { | |
| node_id: n.id, | |
| node_name: n.name || (POSTURES[n.id] && POSTURES[n.id].name) || `node ${n.id}`, | |
| vote: v.vote, | |
| rule_id: v.rule_id, | |
| basis: v.basis, | |
| reason: v.reason, | |
| }; | |
| }); | |
| const yes = votes.filter((v) => v.vote === 'YES').length; | |
| const approved = yes >= quorum; | |
| return { | |
| status: approved ? 'APPROVED' : 'DENIED', | |
| approved, | |
| votes, | |
| yes_count: yes, | |
| no_count: votes.length - yes, | |
| quorum, | |
| // Everything a reviewer needs to reproduce this outcome without running it. | |
| basis: { | |
| signal: report.signal, | |
| headline: report.headline, | |
| deterministic: true, | |
| note: 'Each vote is a pure function of the constraint report and the operator\'s published sensitivities. Re-running with the same inputs yields the identical result.', | |
| }, | |
| }; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Emergency path | |
| // --------------------------------------------------------------------------- | |
| /** | |
| * Emergency override β the honest version. | |
| * | |
| * The old implementation set status APPROVED directly, with zero real votes, | |
| * and fabricated unanimous YES from whichever nodes happened to be online. That | |
| * contradicted the invariant formal/AstroMesh.tla proves: | |
| * | |
| * APPROVED => Cardinality(votes) >= Quorum | |
| * | |
| * An emergency is a reason to accept known risk faster. It is not a reason to | |
| * skip the poll, and it is not a reason to cross a non-negotiable line. | |
| * | |
| * So: emergency generates automatic named waivers for the *waivable* hard rules | |
| * that are currently violated, then re-evaluates and runs a real poll. If a | |
| * non-negotiable rule is violated, it refuses β no override path exists. | |
| * | |
| * @returns {{allowed:boolean, waivers:Array, refused_by:Array, reason:string}} | |
| */ | |
| function emergencyWaivers(report, { party = 'EMERGENCY', reason = 'declared emergency', ts = null } = {}) { | |
| const nonNegotiable = report.blocking.filter((r) => r.waivable === false); | |
| if (nonNegotiable.length) { | |
| return { | |
| allowed: false, | |
| waivers: [], | |
| refused_by: nonNegotiable.map((r) => ({ id: r.id, title: r.title, detail: r.detail })), | |
| reason: `Emergency override refused. ${nonNegotiable.length} non-negotiable rule${nonNegotiable.length > 1 ? 's are' : ' is'} violated: ` | |
| + nonNegotiable.map((r) => `${r.id} ${r.title}`).join('; ') | |
| + '. These rules have no override path β not for an emergency, not for the leader, not for anyone.', | |
| }; | |
| } | |
| // Unevaluated rules cannot be waived either. An emergency does not create evidence. | |
| if (report.unevaluated.length) { | |
| return { | |
| allowed: false, | |
| waivers: [], | |
| refused_by: report.unevaluated.map((r) => ({ id: r.id, title: r.title, detail: r.detail })), | |
| reason: `Emergency override refused. ${report.unevaluated.length} rule${report.unevaluated.length > 1 ? 's have' : ' has'} not been evaluated: ` | |
| + report.unevaluated.map((r) => `${r.id} ${r.title}`).join('; ') | |
| + '. An emergency is not evidence β obtain the missing inputs first.', | |
| }; | |
| } | |
| const waivers = report.blocking.map((r) => ({ | |
| rule_id: r.id, party, reason: `${reason} β auto-waived under emergency declaration`, ts, | |
| })); | |
| return { | |
| allowed: true, | |
| waivers, | |
| refused_by: [], | |
| reason: waivers.length | |
| ? `Emergency declared. ${waivers.length} waivable hard rule${waivers.length > 1 ? 's' : ''} auto-waived and recorded against ${party}. Quorum is still required.` | |
| : 'Emergency declared. No hard rules were violated; the poll proceeds normally.', | |
| }; | |
| } | |
| module.exports = { nodeVote, poll, postures, emergencyWaivers, POSTURES }; | |