oxedyne/daimond/dev/verify_autonomous_posture.mjs
9.9 KiB, 1 run
created by r2519314175:247, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // verify_autonomous_posture.mjs — the per-computer "autonomous" posture, the |
| 2 | // device-local switch that lets THIS machine finish work dispatched to it |
| 3 | // without asking, reaching the web and running commands on its own. The bound is |
| 4 | // the account's provider and Daimond credit, not a dialog; there is no spend gate |
| 5 | // by the owner's decision, so the only thing to prove here is the SHAPE of the |
| 6 | // gate, not a cap on it. |
| 7 | // |
| 8 | // It runs entirely in one page against the static server — no gateway, no o3db, |
| 9 | // no mock provider. The egress gate `egressAllowed` is driven through the same |
| 10 | // `window.__daimondEgressAllowed` hook the wasm tools call, and the parked-vs- |
| 11 | // allowed outcome is read three ways that cannot lie: whether the promise |
| 12 | // resolved, whether a consent tile landed in `daimond-pending`, and whether a |
| 13 | // real `.modal.dlg` was raised. |
| 14 | // |
| 15 | // node dev/verify_autonomous_posture.mjs |
| 16 | // |
| 17 | // Needs dev/serve.mjs (DAIMOND_PORT, default 8777). No gateway, no mock LLM. |
| 18 | // |
| 19 | // The four claims, in the task's order: |
| 20 | // a. OFF — a dispatched worker's web act (alone, nobody able to answer) still |
| 21 | // PARKS on the Pending panel: unresolved, a consent tile written, no auto-allow. |
| 22 | // b. ON — the same act returns 'allow' at once: no park, no dialog, no tile. |
| 23 | // c. STRICT — a `strict:true` navigation is NEVER auto-allowed, even with the |
| 24 | // posture on: it still raises the dialog and does not resolve to 'allow'. |
| 25 | // d. NO SYNC — the posture key appears nowhere in `DaimondCore.collectSync()` |
| 26 | // nor in `DaimondSync.parcel()`, so arming one machine cannot arm another. |
| 27 | import { open } from './harness.mjs'; |
| 28 | |
| 29 | const KEY = 'daimond-autonomous-posture'; // must match daimond.js/handmode.js |
| 30 | |
| 31 | const ok = [], bad = []; |
| 32 | const check = (name, pass, detail) => { |
| 33 | (pass ? ok : bad).push(name); |
| 34 | console.log((pass ? ' ok ' : ' FAIL ') + name + (detail !== undefined && detail !== '' ? ' — ' + detail : '')); |
| 35 | }; |
| 36 | const note = (t) => console.log(' · ' + t); |
| 37 | |
| 38 | const s = await open({ name: 'autonomous', signIn: true, connect: false, defaults: false }); |
| 39 | const { page } = s; |
| 40 | |
| 41 | /// Drive the gate and watch it for `ms`. Returns whether the promise resolved in |
| 42 | /// that window and, if so, the verdict — plus the DELTA in consent tiles on the |
| 43 | /// Pending panel and in real `.modal.dlg` dialogs, measured across the call so |
| 44 | /// nothing an earlier phase left behind is counted. The tagged stand-in modal for |
| 45 | /// "nobody can answer" is never counted as a dialog. |
| 46 | async function drive(payload, opts, ms) { |
| 47 | return await page.evaluate(async (args) => { |
| 48 | const consentCount = () => { |
| 49 | try { return (JSON.parse(localStorage.getItem('daimond-pending') || '[]') || []) |
| 50 | .filter(it => it && it.kind === 'consent').length; } |
| 51 | catch (e) { return 0; } |
| 52 | }; |
| 53 | const dialogCount = () => document.querySelectorAll('.modal.dlg:not([data-fake])').length; |
| 54 | const consentBefore = consentCount(), dialogBefore = dialogCount(); |
| 55 | let done = false, verdict = null; |
| 56 | const p = Promise.resolve(window.__daimondEgressAllowed(JSON.stringify(args.payload), args.opts)) |
| 57 | .then(v => { done = true; verdict = v; }, e => { done = true; verdict = 'ERR:' + (e && e.message || e); }); |
| 58 | await Promise.race([p, new Promise(r => setTimeout(r, args.ms))]); |
| 59 | return { done, verdict, consent: consentCount() - consentBefore, dialogs: dialogCount() - dialogBefore }; |
| 60 | }, { payload, opts, ms }); |
| 61 | } |
| 62 | |
| 63 | /// Set the posture, and raise or clear the "nobody can answer" condition — a |
| 64 | /// tagged `.modal.dlg` makes `someoneCanAnswer()` false without being mistaken |
| 65 | /// for a dialog the gate itself put up. |
| 66 | async function setup(postureOn, blockAnswer) { |
| 67 | await page.evaluate((args) => { |
| 68 | if (args.postureOn) localStorage.setItem(args.KEY, '1'); |
| 69 | else localStorage.removeItem(args.KEY); |
| 70 | document.querySelectorAll('.modal.dlg[data-fake]').forEach(n => n.remove()); |
| 71 | if (args.blockAnswer) { |
| 72 | const m = document.createElement('div'); |
| 73 | m.className = 'modal dlg'; |
| 74 | m.setAttribute('data-fake', '1'); |
| 75 | document.body.appendChild(m); |
| 76 | } |
| 77 | }, { postureOn, blockAnswer, KEY }); |
| 78 | } |
| 79 | |
| 80 | try { |
| 81 | await page.waitForFunction(() => |
| 82 | !!(window.__daimondEgressAllowed && window.DaimondCore && DaimondCore.collectSync), |
| 83 | null, { timeout: 15000 }); |
| 84 | |
| 85 | const act = { tool: 'web_click', url: 'https://shop.example/checkout', alone: true }; |
| 86 | |
| 87 | // ═══════════════════════════════════════════════════════════════ |
| 88 | // a. POSTURE OFF — the dispatched worker still parks |
| 89 | // ═══════════════════════════════════════════════════════════════ |
| 90 | console.log('\n— a: posture OFF, a dispatched worker\'s web act still parks —'); |
| 91 | await setup(false, true); |
| 92 | const offPosture = await page.evaluate((k) => localStorage.getItem(k), KEY); |
| 93 | check('the posture reads OFF (silence, or explicitly cleared)', offPosture !== '1', `stored=${offPosture}`); |
| 94 | const a = await drive(act, undefined, 800); |
| 95 | note(`resolved=${a.done} verdict=${a.verdict} consentTiles=${a.consent} dialogs=${a.dialogs}`); |
| 96 | check('OFF: the worker\'s act does NOT auto-allow (it waits)', a.done === false && a.verdict !== 'allow'); |
| 97 | check('OFF: it PARKS — a consent tile is raised on the Pending panel', a.consent === 1, `${a.consent} tile(s)`); |
| 98 | check('OFF: and it does so by parking, not by a dialog into an empty room', a.dialogs === 0, `${a.dialogs} dialog(s)`); |
| 99 | |
| 100 | // ═══════════════════════════════════════════════════════════════ |
| 101 | // b. POSTURE ON — the same act is allowed at once |
| 102 | // ═══════════════════════════════════════════════════════════════ |
| 103 | console.log('\n— b: posture ON, the same act is allowed with no dialog and no park —'); |
| 104 | await setup(true, true); |
| 105 | const onPosture = await page.evaluate((k) => localStorage.getItem(k), KEY); |
| 106 | check('the posture reads ON for this computer', onPosture === '1', `stored=${onPosture}`); |
| 107 | const b = await drive(act, undefined, 800); |
| 108 | note(`resolved=${b.done} verdict=${b.verdict} consentTiles=${b.consent} dialogs=${b.dialogs}`); |
| 109 | check('ON: the worker\'s act resolves to \'allow\'', b.done === true && b.verdict === 'allow', String(b.verdict)); |
| 110 | check('ON: nothing is parked — no consent tile', b.consent === 0, `${b.consent} tile(s)`); |
| 111 | check('ON: and nothing is asked — no dialog', b.dialogs === 0, `${b.dialogs} dialog(s)`); |
| 112 | |
| 113 | // ═══════════════════════════════════════════════════════════════ |
| 114 | // c. STRICT is never auto-allowed, even with the posture on |
| 115 | // ═══════════════════════════════════════════════════════════════ |
| 116 | console.log('\n— c: strict:true is never auto-allowed, posture on or not —'); |
| 117 | await setup(true, false); // posture on, no stand-in modal: a real dialog is expected |
| 118 | const c = await drive({ url: 'https://elsewhere.example/' }, { strict: true }, 800); |
| 119 | note(`resolved=${c.done} verdict=${c.verdict} dialogs=${c.dialogs}`); |
| 120 | check('STRICT: the posture does NOT wave it through', !(c.done === true && c.verdict === 'allow'), String(c.verdict)); |
| 121 | check('STRICT: it still asks — a dialog is raised', c.dialogs >= 1, `${c.dialogs} dialog(s)`); |
| 122 | // Clear the dialog we raised, so it cannot bleed into the next phase's counts. |
| 123 | await page.evaluate(() => document.querySelectorAll('.modal.dlg').forEach(n => n.remove())); |
| 124 | |
| 125 | // ═══════════════════════════════════════════════════════════════ |
| 126 | // d. the posture key never enters the sync parcel |
| 127 | // ═══════════════════════════════════════════════════════════════ |
| 128 | console.log('\n— d: the posture is device-local — it never enters the sync parcel —'); |
| 129 | await page.evaluate((k) => { try { localStorage.setItem(k, '1'); } catch (e) {} }, KEY); |
| 130 | const sync = await page.evaluate(async () => { |
| 131 | const collectJson = JSON.stringify(await window.DaimondCore.collectSync()); |
| 132 | let parcelJson = '', parcelErr = ''; |
| 133 | try { |
| 134 | if (window.DaimondSync && DaimondSync.parcel) parcelJson = JSON.stringify(await DaimondSync.parcel()); |
| 135 | } catch (e) { parcelErr = String(e && e.message || e); } |
| 136 | return { collectJson, parcelJson, parcelErr }; |
| 137 | }); |
| 138 | const inCollect = sync.collectJson.includes('daimond-autonomous-posture') || sync.collectJson.includes('autonomousPosture'); |
| 139 | check('the posture key is ABSENT from DaimondCore.collectSync()', !inCollect, |
| 140 | `collect parcel is ${sync.collectJson.length} B`); |
| 141 | if (sync.parcelJson) { |
| 142 | const inParcel = sync.parcelJson.includes('daimond-autonomous-posture') || sync.parcelJson.includes('autonomousPosture'); |
| 143 | check('the posture key is ABSENT from DaimondSync.parcel() output', !inParcel, |
| 144 | `sealed parcel is ${sync.parcelJson.length} B`); |
| 145 | } else { |
| 146 | // The sealed parcel only ever wraps what collectSync() returns, so absence |
| 147 | // there is absence here; the offline seal did not run, which is noted, not failed. |
| 148 | note(`DaimondSync.parcel() did not run offline (${sync.parcelErr || 'no output'}); collectSync() is the binding evidence`); |
| 149 | check('the posture key is ABSENT from the sync parcel (via collectSync, its only source)', !inCollect); |
| 150 | } |
| 151 | |
| 152 | } catch (e) { |
| 153 | check('no exception during the run', false, String(e && e.stack || e).slice(0, 400)); |
| 154 | } finally { |
| 155 | await s.close(); |
| 156 | } |
| 157 | |
| 158 | console.log('\n' + ok.length + ' ok, ' + bad.length + ' failed'); |
| 159 | process.exit(bad.length ? 1 : 0); |