Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ For the smallest deterministic reproduction, start with:
- [Repair a cold storage handoff](docs/EVIDENCE_SHARDS.md) — reconstruct canonical handoff bytes from any two of three separately stored shares, then recheck external trust.
- [Rehearse every recovery pair](docs/EVIDENCE_DRILL.md) — get a machine-readable readiness report before a damaged share becomes a loss.
- [Simulate storage failures](docs/EVIDENCE_PLACEMENT.md) — reveal declared shared sites, infrastructure, and custodians that can defeat two-of-three recovery.
- [Explore the visual Resilience Lab](https://aruintelligence.github.io/aml-core/resilience-lab.html) — select a failure to see its blast radius, reopen a local placement manifest, and take that manifest to the verified CLI drill.
- [Explore the visual Resilience Lab](https://aruintelligence.github.io/aml-core/resilience-lab.html) — select a failure, inspect the shared dependencies to change, reopen a local placement manifest, and take it to the verified CLI drill.
- [Undeniable proof demo](demos/undeniable-proof/)
- [Benchmarking protocol](BENCHMARKING.md)
- [Interoperability challenge](INTEROPERABILITY.md)
Expand Down
2 changes: 2 additions & 0 deletions docs/EVIDENCE_PLACEMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ The three-pair [recovery drill](EVIDENCE_DRILL.md) checks the files. It cannot t

Try the [visual Resilience Lab](resilience-lab.html) to explore shared dependencies. Select a failure row to highlight lost and surviving shares. Download a placement manifest and reopen it locally in the lab to review the same declarations later. The browser page runs a topology-only simulation with no share files or policy; the manifest is read in the browser and is not uploaded. Use the commands below for the full verified assessment.

The lab's dependency review lists each declared label shared by multiple shares. For a two-of-three scheme, each distinct site, infrastructure, and custodian label must affect at most one share to survive every modeled single-domain loss. Its minimum label-change count is the sum of `(number of shares with a conflicting label − 1)` across those groups. It counts label assignments, not physical moves; a real move can affect several labels, and changing text without changing storage does nothing. Confirm real independence before updating a manifest.

Create a local JSON manifest with exactly three indexed entries:

```json
Expand Down
22 changes: 22 additions & 0 deletions docs/resilience-lab.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}.card{border:1px solid var(--line);border-radius:22px;background:linear-gradient(155deg,var(--panel2),var(--panel));padding:22px;min-width:0;transition:border-color .2s,background .2s}.card[data-outcome=lost]{border-color:var(--red);background:linear-gradient(155deg,#382532,#231e2a)}.card[data-outcome=survives]{border-color:var(--mint);background:linear-gradient(155deg,#173d3b,#112e34)}.card-head{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);padding-bottom:19px;margin-bottom:17px}.card-number{font-size:39px;line-height:1;font-weight:900;letter-spacing:-.06em}.card-head .tag{color:var(--mint)}.card[data-outcome=lost] .tag{color:var(--red)}.card[data-outcome=survives] .tag{color:var(--mint)}label{display:block;margin:13px 0 5px;color:var(--muted);font-size:12px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}input{width:100%;min-height:43px;border:1px solid var(--line);border-radius:9px;background:#0a1b26;color:var(--ink);padding:10px 12px}input:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}.hint{margin:17px 0 0;color:var(--muted);font-size:12px}.share-state{display:block;margin-top:14px;min-height:20px;font:800 11px/1.4 ui-monospace,monospace;letter-spacing:.1em;text-transform:uppercase}.card[data-outcome=lost] .share-state{color:var(--red)}.card[data-outcome=survives] .share-state{color:var(--mint)}
.result{display:grid;grid-template-columns:minmax(250px,.7fr) 1.3fr;gap:15px;margin-top:24px}.verdict,.scenarios{border:1px solid var(--line);border-radius:22px;background:var(--panel);padding:25px}.verdict h2{font-size:clamp(26px,3.2vw,42px);line-height:1.1;letter-spacing:-.04em;margin:18px 0}.verdict p{color:var(--muted)}.verdict[data-state=pass] h2{color:var(--mint)}.verdict[data-state=fail] h2{color:var(--red)}.verdict[data-state=incomplete] h2{color:var(--amber)}.count{display:block;color:var(--ink);font-size:13px;font-weight:800;margin:25px 0 8px}.progress{height:9px;background:#2b4350;border-radius:20px;overflow:hidden}.progress span{display:block;height:100%;background:var(--mint);transition:width .25s}.verdict[data-state=fail] .progress span{background:var(--red)}.verdict[data-state=incomplete] .progress span{background:var(--amber)}
.scenarios h2{margin:0 0 4px;font-size:23px;letter-spacing:-.03em}.scenarios .intro{margin:0 0 16px;color:var(--muted);font-size:13px}.rows{display:grid;gap:8px}.row{display:grid;width:100%;text-align:left;color:var(--ink);grid-template-columns:130px minmax(0,1fr) auto;gap:10px;align-items:center;border:1px solid var(--line);border-radius:11px;padding:11px 13px;background:#0b1d28;font-size:13px;cursor:pointer}.row:hover,.row:focus-visible,.row[aria-pressed=true]{outline:2px solid var(--cyan);outline-offset:1px}.row .dimension{text-transform:uppercase;letter-spacing:.08em;color:var(--muted);font-size:10px;font-weight:900}.row .detail{min-width:0;overflow-wrap:anywhere}.row .detail small{display:block;color:var(--muted)}.row strong{color:var(--mint);font-size:11px;letter-spacing:.08em}.row.fail{border-color:#875456;background:#2d2028}.row.fail strong{color:var(--red)}
.repair{margin-top:18px;border:1px solid var(--line);border-radius:22px;background:#102633;padding:25px}.repair h2{margin:7px 0;font-size:clamp(22px,3vw,30px);letter-spacing:-.035em}.repair p{max-width:800px;margin:4px 0 17px;color:var(--muted);font-size:14px}.repair-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:10px}.repair-item{border:1px solid #875456;background:#2d2028;border-radius:12px;padding:16px}.repair-item strong{display:block;color:var(--red);overflow-wrap:anywhere}.repair-item span{display:block;color:var(--ink);margin-top:6px;font-size:13px}.repair[data-state=ready] .repair-list{display:none}.repair[data-state=ready] h2{color:var(--mint)}
.handoff{margin-top:24px;padding:24px;border:1px solid var(--line);border-radius:22px;background:#102633;display:flex;justify-content:space-between;align-items:center;gap:20px}.handoff h2{margin:0 0 6px;font-size:20px}.handoff p{margin:0;color:var(--muted);max-width:700px;font-size:14px}.handoff-actions{display:flex;gap:9px;flex-wrap:wrap}.download{white-space:nowrap;background:var(--cyan);color:#06232b}.download:disabled{opacity:.5;cursor:not-allowed}.file-input{position:absolute;width:1px;height:1px;opacity:0;overflow:hidden}.import-status{font-size:12px;color:var(--amber);margin-top:10px!important}.scope{padding:28px 0 0;color:var(--muted);font-size:13px;max-width:900px}footer{border-top:1px solid var(--line);margin-top:45px;padding-top:20px;color:var(--muted);font-size:12px}
@media(max-width:800px){.shell{padding:18px 15px 55px}.hero{padding:48px 0 30px}.cards,.result{grid-template-columns:1fr}.handoff{align-items:flex-start;flex-direction:column}.row{grid-template-columns:95px minmax(0,1fr) auto}.navlinks{gap:8px}}
@media(prefers-reduced-motion:reduce){.progress span{transition:none}}
Expand All @@ -31,6 +32,7 @@
<article class="card"><div class="card-head"><span class="card-number">02</span><span class="tag">Parity share</span></div><label for="site-2">Site</label><input id="site-2" maxlength="128" autocomplete="off"><label for="infrastructure-2">Infrastructure</label><input id="infrastructure-2" maxlength="128" autocomplete="off"><label for="custodian-2">Custodian</label><input id="custodian-2" maxlength="128" autocomplete="off"><p class="hint">Any two distinct healthy shares reconstruct the handoff.</p><output class="share-state" aria-live="polite"></output></article>
</section>
<section class="result" aria-label="Simulation results"><div class="verdict" id="verdict" data-state="pass" aria-live="polite"><div class="eyebrow">Declared topology</div><h2 id="status">All single-domain losses leave two shares</h2><p id="explanation"></p><span class="count" id="count"></span><div class="progress" aria-hidden="true"><span id="bar"></span></div></div><div class="scenarios"><h2>Failure rehearsal</h2><p class="intro">Select a failure to see its impact on all three shares. Each row removes every share with the same declared label. A recovery path needs at least two survivors.</p><div id="rows" class="rows"></div></div></section>
<section class="repair" id="repair" aria-label="Dependency review"><div class="eyebrow">Dependency review</div><h2 id="repair-title"></h2><p id="repair-summary"></p><div id="repair-list" class="repair-list"></div></section>
<section class="handoff"><div><h2>Take the plan to the actual copies</h2><p>Import or download a placement manifest locally, then run the JavaScript or independent Python assessment against three retrieved share files and your separately held trusted policy. The lab alone cannot mark storage ready.</p><p class="import-status" id="import-status" role="status" aria-live="polite"></p></div><div class="handoff-actions"><input class="file-input" type="file" id="import-file" accept=".json,application/json" aria-label="Import placement manifest"><button type="button" id="import" class="download">Open manifest ↑</button><button type="button" id="download" class="download">Download manifest ↓</button></div></section>
<p class="scope">The full assessment recomputes the three-pair drill under an external accepted head and tests recovery after each declared loss. Keep the trust policy outside the shares. A passing result remains a project-authored local check, not independent custody proof.</p>
<footer>ĀML™ · ĀRU Intelligence Inc. · Open-source research prototype. No information is sent from this page.</footer>
Expand All @@ -49,6 +51,15 @@
}
return {valid:true,ready:scenarios.every(scenario=>scenario.recoveryPossible),scenarios};
}
function analyzePlacementChanges(shares){
const report=simulatePlacementTopology(shares);
if(!report.valid)return {valid:false,minimumLabelChanges:0,conflicts:[]};
const conflicts=report.scenarios.filter(scenario=>!scenario.recoveryPossible).map(scenario=>({
dimension:scenario.dimension,value:scenario.value,shares:scenario.lost,
keep:scenario.lost[0],separate:scenario.lost.slice(1)
}));
return {valid:true,minimumLabelChanges:conflicts.reduce((count,item)=>count+item.separate.length,0),conflicts};
}
function parsePlacementManifest(text){
if(typeof text!=="string"||text.length>65536)throw new Error("Manifest is too large (64 KB maximum).");
let manifest;
Expand Down Expand Up @@ -93,6 +104,17 @@
document.getElementById("explanation").textContent=!result.valid?"Every share needs a site, infrastructure, and custodian label.":active?`${active.dimension}: ${active.value} takes ${active.lost.length} share${active.lost.length===1?"":"s"} offline; ${active.survivors.length} remain. This is a declared scenario, not a physical verification.`:result.ready?"The declared topology leaves at least two copies after each modeled loss. Retrieve the real files and run the full verifier.":"At least one modeled loss removes two or more shares. Separate that dependency and test again.";
document.getElementById("count").textContent=`${passing} / ${result.scenarios.length} declared failure cases retain two shares`;
document.getElementById("bar").style.width=`${result.scenarios.length?100*passing/result.scenarios.length:0}%`;
const changes=analyzePlacementChanges(read());
const repair=document.getElementById("repair");repair.dataset.state=!changes.valid?"incomplete":changes.conflicts.length?"conflict":"ready";
document.getElementById("repair-title").textContent=!changes.valid?"Complete the placement map":changes.conflicts.length?`${changes.conflicts.length} shared ${changes.conflicts.length===1?"dependency needs":"dependencies need"} review`:"No shared single-domain dependency declared";
document.getElementById("repair-summary").textContent=!changes.valid?"Add all nine labels to calculate the dependency review.":changes.conflicts.length?`At least ${changes.minimumLabelChanges} label assignment${changes.minimumLabelChanges===1?"":"s"} must represent genuinely independent domains to clear these modeled failures. Move or independently provision the real copies before changing the declarations. A label edit alone does not repair storage.`:"All declared single-domain losses leave a pair. Retrieve the real copies and verify recovery under the external trust policy.";
const repairList=document.getElementById("repair-list");repairList.replaceChildren();
for(const conflict of changes.conflicts){
const item=document.createElement("div");item.className="repair-item";
const title=document.createElement("strong");title.textContent=`${conflict.dimension}: ${conflict.value}`;
const detail=document.createElement("span");detail.textContent=`Shares ${conflict.shares.map(n=>`0${n}`).join(" + ")} fail together. Keep 0${conflict.keep} in this domain; place ${conflict.separate.map(n=>`0${n}`).join(" and ")} in ${conflict.separate.length===1?"an independent domain":"separate independent domains"}.`;
item.append(title,detail);repairList.appendChild(item);
}
document.getElementById("download").disabled=!result.valid;
}
function preset(kind){
Expand Down
25 changes: 25 additions & 0 deletions test/resilience-lab.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const context = vm.createContext({});
vm.runInContext(script, context, { timeout: 1000 });
const simulate = context.simulatePlacementTopology;
const parseManifest = context.parsePlacementManifest;
const analyzeChanges = context.analyzePlacementChanges;
const separated = [0, 1, 2].map(index => ({ index, site: `site-${index}`,
infrastructure: `system-${index}`, custodian: `keeper-${index}` }));

Expand Down Expand Up @@ -56,3 +57,27 @@ test("manifest import rejects malformed, oversized, or incomplete declarations",
assert.throws(() => parseManifest(JSON.stringify({ ...manifest, shares: [{ ...separated[0], site: " " }, separated[1], separated[2]] })), /nonempty/);
assert.throws(() => parseManifest(JSON.stringify({ ...manifest, shares: [{ ...separated[0], site: "x".repeat(129) }, separated[1], separated[2]] })), /128/);
});

test("dependency review identifies the minimum label changes for separate single-domain survival", () => {
assert.equal(analyzeChanges(separated).minimumLabelChanges, 0);
const shared = structuredClone(separated);
shared[1].site = shared[0].site;
shared[2].custodian = shared[0].custodian;
const result = analyzeChanges(shared);
assert.equal(result.minimumLabelChanges, 2);
assert.deepEqual(JSON.parse(JSON.stringify(result.conflicts)), [
{ dimension: "site", value: "site-0", shares: [0, 1], keep: 0, separate: [1] },
{ dimension: "custodian", value: "keeper-0", shares: [0, 2], keep: 0, separate: [2] }
]);
assert.match(html, /A label edit alone does not repair storage/);
});

test("three shares in one domain require two independent assignments", () => {
const shared = structuredClone(separated);
shared[1].infrastructure = shared[0].infrastructure;
shared[2].infrastructure = shared[0].infrastructure;
const result = analyzeChanges(shared);
assert.equal(result.minimumLabelChanges, 2);
assert.deepEqual(Array.from(result.conflicts[0].separate), [1, 2]);
assert.equal(analyzeChanges([{ ...separated[0], site: "" }, separated[1], separated[2]]).valid, false);
});
Loading