-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDelta-Atlas-ContinuityAudit.html
More file actions
368 lines (345 loc) · 33.3 KB
/
Copy pathDelta-Atlas-ContinuityAudit.html
File metadata and controls
368 lines (345 loc) · 33.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Delta Atlas - Continuity Audit</title>
<style>
:root{--bg:#16301f;--panel:#2c4a38;--line:#4a6b56;--txt:#fafcf7;--dim:#d3decf;--accent:#e3c258;--green:#8fe0a5;--amber:#f2cd55;--red:#ff9d80;}
*{box-sizing:border-box;} body{margin:0;background:var(--bg);color:var(--txt);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;font-size:14px;line-height:1.55;}
header{padding:15px 22px;border-bottom:1px solid var(--line);}
header h1{margin:0;font-size:18px;} header p{margin:4px 0 0;color:var(--dim);font-size:12.5px;max-width:900px;}
.main{max-width:1040px;margin:0 auto;padding:18px 20px 100px;}
label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--dim);margin:14px 0 5px;}
input,textarea{width:100%;background:var(--panel);color:var(--txt);border:1px solid var(--line);border-radius:9px;padding:10px 12px;font-size:14px;font-family:inherit;}
textarea{min-height:170px;resize:vertical;line-height:1.5;} input:focus,textarea:focus{outline:none;border-color:var(--accent);}
.row{display:flex;gap:10px;margin-top:12px;flex-wrap:wrap;}
button{cursor:pointer;border:1px solid var(--accent);background:rgba(227,194,88,0.09);color:var(--accent);border-radius:9px;padding:10px 18px;font-size:14px;}
button.ghost{border-color:var(--line);color:var(--txt);background:var(--panel);} button:hover{border-color:var(--accent);}
.res{margin-top:22px;display:none;} .res.show{display:block;}
.big{font-size:34px;font-weight:700;font-family:ui-monospace,Menlo,Consolas,monospace;}
.verdict{font-size:14px;font-weight:600;} .slop{color:var(--dim);font-size:13px;}
h2{font-size:14px;text-transform:uppercase;letter-spacing:.5px;color:var(--dim);margin:24px 0 8px;border-bottom:1px solid var(--line);padding-bottom:5px;}
.dim{display:flex;align-items:center;gap:10px;margin:7px 0;font-size:13px;}
.dlbl{width:190px;flex:none;} .dtrk{flex:1;height:9px;background:#22402e;border-radius:5px;overflow:hidden;}
.dfill{height:100%;border-radius:5px;} .dval{width:34px;text-align:right;font-family:ui-monospace,monospace;font-size:12px;color:var(--dim);}
.gap{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--red);border-radius:10px;padding:12px 15px;margin:9px 0;}
.gap.warn{border-left-color:var(--amber);} .gap.info{border-left-color:var(--accent);}
.gap .t{font-weight:650;font-size:14px;margin-bottom:3px;} .gap .dt{font-size:13px;} .gap .dt b{color:var(--txt);}
.sugg{margin-top:8px;} .chip{display:inline-block;border:1px solid var(--line);border-radius:20px;padding:3px 10px;font-size:12px;margin:3px 5px 0 0;background:rgba(227,194,88,0.05);}
.part{border:1px solid var(--line);border-radius:10px;padding:11px 14px;margin:8px 0;background:var(--panel);}
.ptxt{font-size:13.5px;margin-bottom:6px;} .pscore{font-family:ui-monospace,monospace;font-size:12px;float:right;}
.flag{display:inline-block;font-size:11px;padding:1px 8px;border-radius:20px;border:1px solid var(--line);margin:2px 5px 2px 0;}
.flag.bad{color:var(--red);border-color:#e3c1b8;} .flag.mid{color:var(--amber);border-color:#e6d3a3;} .flag.ok{color:var(--green);border-color:#4a6b56;}
.note{color:var(--dim);font-size:12px;background:rgba(95,125,80,0.07);border:1px solid #4a6b56;border-radius:9px;padding:11px 14px;margin-top:20px;}
.overlaynote{font-size:11px;color:var(--dim);margin-top:6px;}
.overlayloader{margin-top:12px;padding:10px 12px;border:1px solid var(--line);border-radius:9px;background:var(--panel);}
.overlayloader label{margin:0 0 6px;text-transform:none;font-size:12px;letter-spacing:0;}
.overlayloader input{padding:7px;font-size:12px;}
/* canonical typography: one baseline across every page (added 2026-07-11) */
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;font-size:14.5px;font-weight:450;line-height:1.6;}
textarea,input,select{font-family:inherit;font-size:14px;line-height:1.5;}
button{font-family:inherit;}
::placeholder{color:var(--dim);opacity:1;}
</style></head><body>
<header>
<h1>Delta Atlas - Continuity Audit</h1>
<p>Paste an operations manual, SOP, or workflow — one procedure per line — and the one-sentence goal it serves. Get three readings from one input: a <b>resilience</b> reading (can the manual keep the goal on track when a key person is out, a vendor fails, or an emergency hits — does it absorb, stay anchored, and recover), the <b>gaps</b> (a risk named with no stated control, single points of failure with no documented backup), and a <b>coherence score</b> (is each procedure a clear, testable action, or a vague label). Same engines as Gap Check and Framework Audit, pointed at a new domain. In the atlas grammar: the manual is the <b>claim</b>; its named backups and controls are the <b>witness</b>; brittleness is the difference. Heuristic, deterministic, entirely on your device — nothing you paste here ever leaves your browser.</p>
</header>
<div class="main">
<label>The goal (one sentence: what is this manual for?)</label>
<input id="goal" placeholder="e.g. Keep the service running whether or not any one team member is available."/>
<label>The procedures (one per line: steps, controls, responsibilities)</label>
<textarea id="parts" placeholder="One procedure per line. For example:
Only one administrator knows the admin credentials and access keys.
The operations lead reconciles the operation accounts against receipts every month.
A new vendor is granted building access after a background check and a signed confidentiality agreement.
If the on-call schedule changes, only one person is informed."></textarea>
<div class="row">
<button id="run">Run audit</button>
<button class="ghost" id="ex1">Strong example</button>
<button class="ghost" id="ex2">Weak example</button>
<button class="ghost" id="clr">Clear</button>
</div>
<div class="overlayloader">
<label for="overlayfile">Optional private rules: choose a data-only <code>continuity-overlay.local.json</code> from this device. The file is read only in this browser and is not uploaded.</label>
<input id="overlayfile" type="file" accept="application/json,.json"/>
<div class="overlaynote" id="overlaynote" role="status" aria-live="polite">No private overlay loaded.</div>
</div>
<div class="res" id="res"></div>
<div class="note">Honest limit: this reads words and patterns, not meaning or your operation's actual arrangements. It flags likely gaps to point you where to look; it does not deliver a verdict, and it cannot see anything you haven't written down. The judgment stays yours. Atlas does not submit the text you enter to a model or API. A private overlay is optional and data-only; invalid or unsupported files are rejected.</div>
</div>
<script src="lexicon-engine.js"></script>
<script src="coherence-score-engine.js"></script>
<script>
function esc(s){return String(s==null?'':s).replace(/[&<>]/g,function(c){return {'&':'&','<':'<','>':'>'}[c];});}
// --- knowledge base: generic, non-client-specific operations-manual risk vocabulary.
// Craft generalizes; a specific operation's real procedures, vendor names, or staff never
// belong here — that's exactly what the private overlay below is for.
const BASE_KB = {
noControlNoun: 'knowledge base',
clusters: ['Succession & Continuity','Personnel & Site Security','Access & Vendor Control','Confidentiality','Financial Controls','Emergency Procedures'],
terms: [
{id:'single-point-of-failure', name:'Single point of failure', purpose:'risk', cluster:'Succession & Continuity', acct:'', aliases:['single point of failure','only one person knows','sole point of contact','nobody else can','no one else knows','no one else can']},
{id:'staff-turnover-risk', name:'Undocumented staff turnover', purpose:'risk', cluster:'Succession & Continuity', acct:'', aliases:['staff turnover','without notice','sudden departure','no transition plan']},
{id:'undocumented-procedure', name:'Undocumented procedure', purpose:'risk', cluster:'Succession & Continuity', acct:'', aliases:['not written down','tribal knowledge','never documented','no written procedure']},
{id:'cross-training', name:'Cross-training', purpose:'control', cluster:'Succession & Continuity', acct:'human', aliases:['cross trained','cross training','backup person trained','second person knows']},
{id:'documented-procedure', name:'Documented procedure', purpose:'control', cluster:'Succession & Continuity', acct:'human', aliases:['written procedure','documented in','standard operating procedure','sop','procedures manual']},
{id:'succession-plan', name:'Succession plan', purpose:'control', cluster:'Succession & Continuity', acct:'human', aliases:['succession plan','transition plan','continuity plan']},
{id:'knowledge-handoff', name:'Knowledge handoff', purpose:'control', cluster:'Succession & Continuity', acct:'human', aliases:['handoff checklist','transition checklist','knowledge transfer']},
{id:'key-person-exposure', name:'Key-person exposure', purpose:'risk', cluster:'Personnel & Site Security', acct:'', aliases:['schedule shared broadly','location shared','travel plans posted','publicly known schedule']},
{id:'unvetted-access', name:'Unvetted access granted', purpose:'risk', cluster:'Personnel & Site Security', acct:'', aliases:['no background check','without a background check','unvetted','not screened']},
{id:'travel-security-protocol', name:'Travel security protocol', purpose:'control', cluster:'Personnel & Site Security', acct:'human', aliases:['travel security protocol','need to know schedule','advance security check']},
{id:'background-check-policy', name:'Background check policy', purpose:'control', cluster:'Personnel & Site Security', acct:'human', aliases:['background check','background screening','vetted before']},
{id:'vendor-overreach', name:'Vendor access overreach', purpose:'risk', cluster:'Access & Vendor Control', acct:'', aliases:['full access to all systems','full access to all facilities','full access to all servers','full access to all accounts','full access to everything','full access to everything','all areas access','unrestricted access','run of the place']},
{id:'unattended-visitor', name:'Unattended visitor or vendor', purpose:'risk', cluster:'Access & Vendor Control', acct:'', aliases:['left unattended','unsupervised','no escort','without supervision']},
{id:'least-privilege-access', name:'Least-privilege access', purpose:'control', cluster:'Access & Vendor Control', acct:'human', aliases:['least privilege','only the access needed','scoped access','access limited to','access is limited to']},
{id:'escort-policy', name:'Escort policy', purpose:'control', cluster:'Access & Vendor Control', acct:'human', aliases:['escorted at all times','always accompanied','staff escort']},
{id:'vendor-access-log', name:'Vendor access log', purpose:'control', cluster:'Access & Vendor Control', acct:'human', aliases:['visitor access log','vendor access log','sign in log','sign-in sheet','visitor log']},
{id:'information-leakage', name:'Information leakage', purpose:'risk', cluster:'Confidentiality', acct:'', aliases:['discussed outside','shared with outsiders','posted about the organization','talked about publicly']},
{id:'device-exposure', name:'Personal device exposure', purpose:'risk', cluster:'Confidentiality', acct:'', aliases:['personal phone','personal device','unencrypted device','personal email for company data']},
{id:'confidentiality-agreement', name:'Confidentiality agreement', purpose:'control', cluster:'Confidentiality', acct:'human', aliases:['confidentiality agreement','signed nda','non disclosure agreement']},
{id:'secure-communication-channel', name:'Secure communication channel', purpose:'control', cluster:'Confidentiality', acct:'human', aliases:['secure channel','encrypted communication','company-issued device']},
{id:'unauthorized-expenditure', name:'Unauthorized expenditure', purpose:'risk', cluster:'Financial Controls', acct:'', aliases:['spends without approval','no approval needed','without sign off','discretionary spending']},
{id:'reconciliation-gap', name:'Financial reconciliation gap', purpose:'risk', cluster:'Financial Controls', acct:'', aliases:['not reconciled','no reconciliation','accounts unchecked']},
{id:'dual-approval', name:'Dual approval', purpose:'control', cluster:'Financial Controls', acct:'human', aliases:['dual approval','two signatures','second sign off','requires approval from']},
{id:'monthly-reconciliation', name:'Regular reconciliation', purpose:'control', cluster:'Financial Controls', acct:'human', aliases:['reconciles monthly','reconciled every month','regular reconciliation','audited quarterly']},
{id:'unclear-emergency-chain', name:'Unclear emergency chain', purpose:'risk', cluster:'Emergency Procedures', acct:'', aliases:['no emergency plan','unclear who to call','no defined chain','not sure who decides']},
{id:'single-key-holder', name:'Single key/code holder', purpose:'risk', cluster:'Emergency Procedures', acct:'', aliases:['only one key','one person has the code','single keyholder','only one person knows']},
{id:'emergency-contact-protocol', name:'Emergency contact protocol', purpose:'control', cluster:'Emergency Procedures', acct:'human', aliases:['emergency contact list','emergency protocol','crisis contact chain']},
{id:'redundant-access', name:'Redundant emergency access', purpose:'control', cluster:'Emergency Procedures', acct:'human', aliases:['sealed envelope','backup key holder','redundant access','second key holder'] },
{id:'accountability', name:'Named accountable owner', purpose:'concept', cluster:'Financial Controls', acct:'human', aliases:['accountable owner','responsible party','who is responsible']},
],
relations: [
['cross-training','single-point-of-failure','mitigates'], ['documented-procedure','single-point-of-failure','mitigates'], ['succession-plan','single-point-of-failure','governs'],
['succession-plan','staff-turnover-risk','mitigates'], ['knowledge-handoff','staff-turnover-risk','checks'],
['documented-procedure','undocumented-procedure','mitigates'],
['travel-security-protocol','key-person-exposure','mitigates'],
['background-check-policy','unvetted-access','mitigates'],
['least-privilege-access','vendor-overreach','mitigates'],
['escort-policy','unattended-visitor','mitigates'], ['vendor-access-log','unattended-visitor','detects'],
['confidentiality-agreement','information-leakage','mitigates'],
['secure-communication-channel','device-exposure','mitigates'],
['dual-approval','unauthorized-expenditure','mitigates'],
['monthly-reconciliation','reconciliation-gap','mitigates'],
['emergency-contact-protocol','unclear-emergency-chain','mitigates'],
['redundant-access','single-key-holder','mitigates'],
],
defendRelTypes: new Set(['mitigates','checks','detects','governs']),
bridge: {
'single-point-of-failure': ['only she knows','only he knows','no one else is trained','if she is out','if he is out'],
'least-privilege-access': ['need to know basis','need-to-know basis'],
},
redFlags: [
{t:'Single keyholder for emergency access', d:'Only one person can open the safe, disarm the alarm, or reach emergency medical information. If that person is unreachable, so is the operation.',
re:/\bonly\s+(?:the\s+)?(?:[a-z]+\s+){0,3}(knows|has|holds)\b[^.]{0,40}\b(safe|combination|alarm|password|passcode|key ?code|credential|credentials|access keys?)\b|\b(held by|known to)\s+(?:exactly |just |only )?one person\b/i},
{t:'Vendor granted unrestricted access', d:'A vendor or contractor is described with access broader than the job requires, with no stated limit or expiry.',
re:/\bmaster key\b|\b(all[- ]areas|unrestricted|unfettered|unlimited)\s+access\b|\bfull\s+access\s+to\s+(the\s+|all\s+)?(systems|servers|facilities|accounts|the network|everything)\b|\bkeys?\s+to\s+(every|all|the whole|the entire)\b/i},
{t:'Key-person schedule or location broadly known', d:'A key person\'s whereabouts or schedule is shared beyond the people who need it to do their job.',
re:/\b(schedule|itinerary|location|whereabouts|travel plans?|address)\b[^.]{0,50}\b(posted|shared|visible|known|tagged|geotag\w*)\b[^.]{0,40}\b(publicly|broadly|with everyone|with all staff|online|on social|on instagram|on facebook|to the public)\b|\bb(executive|leader|key person|manager|director)('s|s')?\b[^.]{0,40}\b(schedule|itinerary|location|whereabouts|travel)\b[^.]{0,40}\b(posted|shared|public|online|social media|instagram)\b/i},
],
groupChecks: [{
ifGroup: { ids: new Set(['single-point-of-failure','staff-turnover-risk','undocumented-procedure']) },
unlessGroup: { ids: new Set(['cross-training','documented-procedure','succession-plan','knowledge-handoff']) },
sev: 'gap',
title: () => 'Institutional knowledge has no backup',
detail: () => 'A task or piece of knowledge is named as resting on one person, and nothing in the text says it is cross-trained, written down, or covered by a succession plan. If that person is unavailable, so is the operation.',
sugg: ['cross-training','documented-procedure','succession-plan','knowledge-handoff'],
}],
};
// Private overlay (gitignored *.local.json, never published): additive data only.
// Code overlays are intentionally unsupported: this page never evaluates fetched text.
// BASE_KB contains Sets and functions, so copy it shallowly and concatenate only the
// validated additive data; structural controls remain the public floor.
function mergeOverlay(base, ov){
if(!ov) return base;
var kb = {};
for (var k in base) kb[k] = base[k]; // shallow copy — keeps Set + functions intact
if(ov.terms) kb.terms = base.terms.concat(ov.terms);
if(ov.relations) kb.relations = base.relations.concat(ov.relations);
if(ov.redFlags) kb.redFlags = base.redFlags.concat(ov.redFlags);
if(ov.bridge){
kb.bridge = {};
for (var b in base.bridge) kb.bridge[b] = base.bridge[b];
Object.keys(ov.bridge).forEach(function(id){ kb.bridge[id] = (kb.bridge[id]||[]).concat(ov.bridge[id]); });
}
return kb;
}
const OVERLAY_FILE_LABEL='continuity-overlay.local.json';
const OVERLAY_VERSION=1;
const OVERLAY_MAX_TERMS=80, OVERLAY_MAX_RELATIONS=160, OVERLAY_MAX_PHRASES=24;
const OVERLAY_REL_TYPES=new Set(['mitigates','checks','detects','governs']);
function own(o,k){ return Object.prototype.hasOwnProperty.call(o,k); }
function obj(v){ return !!v && typeof v==='object' && !Array.isArray(v); }
function text(v,max){ return typeof v==='string' && v.length>0 && v.length<=max; }
function id(v){ return typeof v==='string' && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(v) && v.length<=80; }
function only(o,keys){ return Object.keys(o).every(function(k){ return keys.indexOf(k)!==-1; }); }
function literalPattern(phrases){
return new RegExp(phrases.map(function(p){ return p.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'); }).join('|'),'i');
}
function parseOverlay(raw){
var ov;
try { ov=JSON.parse(raw); } catch(e) { return null; }
if(!obj(ov) || ov.type!=='delta-atlas-continuity-overlay' || ov.version!==OVERLAY_VERSION || !only(ov,['type','version','terms','relations','redFlags','bridge'])) return null;
var terms=Array.isArray(ov.terms)?ov.terms:[], relations=Array.isArray(ov.relations)?ov.relations:[], redFlags=Array.isArray(ov.redFlags)?ov.redFlags:[], bridge=obj(ov.bridge)?ov.bridge:{};
if(terms.length>OVERLAY_MAX_TERMS || relations.length>OVERLAY_MAX_RELATIONS || redFlags.length>OVERLAY_MAX_TERMS) return null;
var known={}; BASE_KB.terms.forEach(function(t){ known[t.id]=true; });
var cleanTerms=[];
for(var i=0;i<terms.length;i++){
var t=terms[i];
if(!obj(t) || !only(t,['id','name','purpose','cluster','acct','aliases']) || !id(t.id) || known[t.id] || !text(t.name,160) || ['risk','control','concept'].indexOf(t.purpose)===-1 || !text(t.cluster,100) || typeof t.acct!=='string' || !Array.isArray(t.aliases) || t.aliases.length>OVERLAY_MAX_PHRASES || !t.aliases.every(function(a){return text(a,160);} )) return null;
known[t.id]=true; cleanTerms.push({id:t.id,name:t.name,purpose:t.purpose,cluster:t.cluster,acct:t.acct,aliases:t.aliases.slice()});
}
var cleanRelations=[];
for(var j=0;j<relations.length;j++){
var r=relations[j];
if(!Array.isArray(r) || r.length!==3 || !id(r[0]) || !id(r[1]) || !known[r[0]] || !known[r[1]] || !OVERLAY_REL_TYPES.has(r[2])) return null;
cleanRelations.push([r[0],r[1],r[2]]);
}
var cleanFlags=[];
for(var k=0;k<redFlags.length;k++){
var f=redFlags[k];
if(!obj(f) || !only(f,['t','d','phrases']) || !text(f.t,160) || !text(f.d,800) || !Array.isArray(f.phrases) || f.phrases.length<1 || f.phrases.length>OVERLAY_MAX_PHRASES || !f.phrases.every(function(p){return text(p,160);} )) return null;
cleanFlags.push({t:esc(f.t),d:esc(f.d),re:literalPattern(f.phrases)});
}
var cleanBridge={};
for(var b in bridge){ if(!own(bridge,b) || !id(b) || !known[b] || !Array.isArray(bridge[b]) || bridge[b].length>OVERLAY_MAX_PHRASES || !bridge[b].every(function(p){return text(p,160);} )) return null; cleanBridge[b]=bridge[b].slice(); }
return {terms:cleanTerms,relations:cleanRelations,redFlags:cleanFlags,bridge:cleanBridge};
}
// --- coherence scoring: base 5 dimensions + one new one this domain needed: continuity ---
const CONTINUITY_PHRASE = /\bonly\s+(?:the\s+)?(?:[a-z]+\s+){0,3}(knows|can|has access|is trained|handles)\b|\bsole(ly)?\s+(responsible|point of contact)\b|\bno\s?one\s+else\s+(knows|can)\b/i;
const BACKUP_PHRASE = /\bcross[- ]?train|\bbackup\b|\bsecond\s+person\b|\bwritten\s+(down|procedure)\b|\bdocumented\b|\bsuccession\b|\bhandoff\b/i;
const SCORER = CoherenceScoreEngine.makeScorer({
weights: { action:0.24, testable:0.24, plain:0.14, goal:0.10, backup:0.08, continuity:0.20 },
extraDims: [{
k:'continuity', label:'Institutional continuity', help:'not resting on one person',
score: function(ctx){
if(CONTINUITY_PHRASE.test(ctx.text) && !BACKUP_PHRASE.test(ctx.text)) return 25;
if(CONTINUITY_PHRASE.test(ctx.text) && BACKUP_PHRASE.test(ctx.text)) return 80;
return 85;
},
flagIf: function(v){ return v < 50; }, flagSev:'bad', flagLabel:'no backup named',
fixText: function(){ return 'Name who else can do this, or where the procedure is written down, so one person\'s absence does not stop the operation.'; },
}],
});
let KB = BASE_KB;
function analyze(text){ return LexiconEngine.analyze(text, KB); }
// --- Resilience of the goal: a whole-manual reading (system-level, not per-line — resilience
// is a property of the whole, per the Resilience Ledger and Holling 1973). It asks whether the
// manual can keep the operation on its stated goal when disrupted, across three functions
// (Resilience Ledger absorb/check/reset ~ MITRE 2011 withstand/anchor/recover).
//
// HARDENED after a red-team found the earlier version certified a maximally BRITTLE manual as
// "resilient" (84, beating the strong example) by evading the risk regexes and stuffing
// reassuring vocabulary. The fixes, all execution-verified: (1) resilience is DEMONSTRATED, not
// asserted — every sub-score scales on real STRUCTURE (distinct real controls, defended risks,
// recovery controls minus open gaps), never on a constant or on gameable coherence dimensions;
// (2) Anchor counts only lines that are themselves clear AND testable, so goal-keyword-stuffed
// filler can't anchor; (3) a HARD GATE keeps the top band out of reach unless the manual shows
// real structure (>=4 distinct controls, or a control wired to a named risk) and has no open
// single-point gap. Honest residual limit, stated in the UI: this reads words, and a determined
// author stuffing control vocabulary can still inflate a middling score — a clean read is a
// pointer to verify, never a certificate.
function resilienceReading(A, roll, parts){
parts = parts || [];
var byId = {}; KB.terms.forEach(function(t){ byId[t.id]=t; });
var controls = A.M.filter(function(id){ return byId[id] && byId[id].purpose==='control'; });
var RECOVERY_CONTROLS = ['emergency-contact-protocol','redundant-access','succession-plan','knowledge-handoff','cross-training','documented-procedure'];
var recoveryControls = controls.filter(function(id){ return RECOVERY_CONTROLS.indexOf(id) >= 0; });
// open structural gaps straight from the gap scan (which the red-team confirmed sound)
var openGaps = A.findings.filter(function(f){ return f.sev==='gap' && /no control|no backup|Single keyholder|unrestricted access/i.test(f.t); }).length;
// a control that actually defends a named, present risk — the strongest structural evidence
var wired = controls.some(function(id){ return (KB.relations||[]).some(function(r){ return r[0]===id && A.M.indexOf(r[1])>=0 && KB.defendRelTypes.has(r[2]); }); });
// WITHSTAND — demonstrated absorption. Named risks that are actually defended, and/or real
// distinct controls present. Silence (no risks, no controls) is NOT safety; it scores ~0.
var ctrlScore = Math.min(1, controls.length / 5);
var withstand = A.risks.length
? Math.round(100 * (0.65*(A.defended/A.risks.length) + 0.35*ctrlScore))
: Math.round(100 * ctrlScore);
// RECOVER — real recovery structure (needs ~2 recovery controls), dragged down hard by any open gap.
var recover = Math.max(0, Math.min(100, Math.round(100 * Math.min(1, recoveryControls.length / 2)) - 30*openGaps));
// ANCHOR — trajectory holds to the goal, but only substantive lines count (clear AND testable);
// goal-keyword-stuffed filler is excluded, and a mostly-vague manual can't anchor.
var solid = parts.filter(function(p){ return p.scores.action>=60 && p.scores.testable>=60; });
var anchor;
if (!solid.length) anchor = Math.min(40, roll.dimAvg.goal);
else { anchor = Math.round(solid.reduce(function(a,p){ return a+p.scores.goal; },0)/solid.length);
if (solid.length < Math.ceil(parts.length/2)) anchor = Math.round(anchor*0.6); }
var overall = Math.round((withstand + anchor + recover)/3);
// HARD GATE — the top "resilient" band requires real structure and no open single-point gap.
var realStructure = controls.length >= 4 || wired || A.defended > 0;
if (overall >= 72 && (!realStructure || openGaps > 0)) overall = 60; // best a no-structure manual gets is mid-band
return { withstand: withstand, anchor: anchor, recover: recover, overall: overall };
}
function run(){
const goal=document.getElementById('goal').value.trim();
const raw=document.getElementById('parts').value.split('\n').map(function(x){return x.trim();}).filter(function(x){return x.length>1;});
const res=document.getElementById('res');
if(!raw.length){res.className='res show';res.innerHTML='<div class="slop">Add at least one procedure to audit.</div>';return;}
// 1) gap scan over the whole pasted text
const A = analyze(raw.join('\n'));
// 2) coherence scoring per line
const goalTokens=CoherenceScoreEngine.content(goal);
const parts=raw.map(function(t,i){return SCORER.scorePart(t,goalTokens,raw,i);});
const roll=SCORER.rollup(parts);
function col(v){return v>=68?'var(--green)':v>=45?'var(--accent)':v>=28?'var(--amber)':'var(--red)';}
const vtxt = roll.overall>=75?['Solid manual','var(--green)'] : roll.overall>=55?['Mostly solid, a few soft spots','var(--accent)'] : roll.overall>=38?['Half-formed, needs work','var(--amber)'] : ['Mostly vague','var(--red)'];
let h='';
// Resilience headline — frames the whole audit: does the goal stay resilient throughout?
const R = resilienceReading(A, roll, parts);
const rv = R.overall>=72?['Reads resilient — verify against real events','var(--green)'] : R.overall>=52?['Holds, with soft spots','var(--accent)'] : R.overall>=34?['Brittle in places','var(--amber)'] : ['Not yet resilient','var(--red)'];
h+='<h2>Resilience of the goal</h2>';
h+='<div class="slop" style="margin-bottom:8px">Can this manual keep the operation on its stated goal when disrupted — a key person out, a vendor failing, an emergency? <b style="color:'+rv[1]+'">'+R.overall+' · '+rv[0]+'</b></div>';
[['Withstand','absorbs the hit — named risks have a control',R.withstand],['Anchor','stays true to the goal under stress',R.anchor],['Recover','resets to normal after a disruption',R.recover]].forEach(function(row){
h+='<div class="dim"><span class="dlbl">'+row[0]+' <span style="color:var(--dim);font-weight:400;font-size:11.5px">'+row[1]+'</span></span><span class="dtrk"><span class="dfill" style="width:'+row[2]+'%;background:'+col(row[2])+'"></span></span><span class="dval">'+row[2]+'</span></div>';
});
h+='<div class="overlaynote">Resilience here follows the <b>Resilience Ledger</b>: absorb load, stay anchored to a fixed reference, recover to baseline — the withstand/recover pattern from ecological resilience (Holling 1973) and cyber-resiliency engineering (MITRE 2011; NIST SP 800-160 v2). A whole-manual reading, not a per-line score, and it credits real structure (controls that defend named risks, recovery procedures) — not reassuring vocabulary. Honest limit: it still reads words, so a determined author can inflate a middling read by naming controls that aren\'t really there. A clean read points you to verify; it does not certify.</div>';
h+='<h2>Gaps & coverage</h2>';
h+='<div class="slop">'+A.M.length+' governance terms found · '+A.areas+'/'+KB.clusters.length+' areas touched · '+A.defended+'/'+A.risks.length+' risks defended</div>';
if(!A.findings.length){ h+='<div class="gap info"><div class="t">No gaps found</div><div class="dt">Every risk this scan recognizes has a stated control alongside it.</div></div>'; }
A.findings.forEach(function(f){
h+='<div class="gap '+(f.sev==='warn'?'warn':f.sev==='info'?'info':'')+'"><div class="t">'+f.t+'</div><div class="dt">'+f.dt+'</div>';
if(f.sugg&&f.sugg.length){ h+='<div class="sugg">Consider: '+f.sugg.map(function(id){var t=KB.terms.find(function(x){return x.id===id;}); return t?'<span class="chip">'+esc(t.name)+'</span>':'';}).join('')+'</div>'; }
h+='</div>';
});
h+='<h2>How the manual holds up</h2><div class="slop" style="margin-bottom:8px">'+roll.overall+' — <b style="color:'+vtxt[1]+'">'+vtxt[0]+'</b> · '+roll.clearN+' of '+parts.length+' procedures are clear and testable'+(roll.vagueN?'; '+roll.vagueN+' need work.':'.')+'</div>';
SCORER.DIMS.forEach(function(d){ var v=roll.dimAvg[d.k];
h+='<div class="dim"><span class="dlbl">'+d.label+' <span style="color:var(--dim);font-weight:400;font-size:11.5px">'+d.help+'</span></span><span class="dtrk"><span class="dfill" style="width:'+v+'%;background:'+col(v)+'"></span></span><span class="dval">'+v+'</span></div>';
});
h+='<h2>Every procedure, scored</h2>';
parts.forEach(function(p){
h+='<div class="part"><span class="pscore" style="color:'+col(p.avg)+'">'+p.avg+'</span><div class="ptxt">'+esc(p.text)+'</div>';
p.flags.forEach(function(f){h+='<span class="flag '+f[0]+'">'+f[1]+'</span>';});
if(p.fix.length){ h+='<div class="sugg" style="font-size:12px;color:var(--dim)"><b style="color:var(--amber)">Tighten:</b> '+p.fix[0]+'</div>'; }
h+='</div>';
});
res.className='res show'; res.innerHTML=h;
res.scrollIntoView({behavior:'smooth',block:'start'});
}
document.getElementById('run').onclick=run;
document.getElementById('clr').onclick=function(){document.getElementById('goal').value='';document.getElementById('parts').value='';document.getElementById('res').className='res';};
function loadEx(goal, parts){ document.getElementById('goal').value=goal; document.getElementById('parts').value=parts.join('\n'); run(); }
document.getElementById('ex1').onclick=function(){ loadEx("Keep the service running whether or not any one team member is available.",[
"The operations lead and a backup engineer are both cross-trained on the admin credentials and access keys, documented in the recovery runbook.",
"New vendors are granted access only after a background check and a signed confidentiality agreement, and that access is time-limited and logged.",
"The financial accounts are reconciled every month by finance and reviewed independently each quarter.",
"Any expenditure over the threshold requires dual approval from management and the operations lead.",
"The incident escalation protocol names three people, in order, for every category of incident."]); };
document.getElementById('ex2').onclick=function(){ loadEx("Run the operation smoothly.",[
"Only one administrator knows the admin credentials and access keys.",
"The new contractor has full access to all systems with no supervision.",
"The accounts are not reconciled on any regular schedule.",
"Anyone can approve purchases without any sign-off from management.",
"In an incident, only one person knows who to call."]); };
// RUNTIME_BINDING_START: explicit local file selection; no network fetch and no code evaluation.
document.getElementById('overlayfile').addEventListener('change',function(event){
var file=event.target.files&&event.target.files[0], note=document.getElementById('overlaynote');
KB=BASE_KB;
if(!file){note.textContent='No private overlay loaded.';return;}
if(file.size>256*1024){note.textContent='Overlay rejected: file exceeds 256 KiB.';event.target.value='';return;}
file.text().then(function(raw){
var overlay=parseOverlay(raw);
if(!overlay){note.textContent='Overlay rejected: invalid type, version, field, identifier, or relation.';return;}
KB=mergeOverlay(BASE_KB,overlay); note.textContent='Private data overlay loaded for this page session.';
}).catch(function(){note.textContent='Overlay rejected: the file could not be read.';});
});
</script>
</body></html>