deadbyte-mcp 0.15.41 → 0.15.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEADBYTE-MCP.cmd +8 -4
- package/MANIFEST.SHA256 +84 -41
- package/PROVENANCE.json +53 -15
- package/README.txt +27 -18
- package/RELEASE.json +43 -15
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +106 -14
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +132 -12
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R65-AUTHORITY-CONTINUITY-DESIGN.json +27 -0
- package/docs/R65-REFERENCE-GAP-MATRIX.json +22 -0
- package/package.json +5 -4
- package/scripts/deploy-r48.mjs +123 -7
- package/scripts/gate-windows-r65-prepublish.ps1 +65 -0
- package/scripts/npm-publication-r65.mjs +139 -0
- package/scripts/npm-release-r48.mjs +56 -37
- package/scripts/r65-canonical-full.mjs +45 -0
- package/scripts/r65-focused.mjs +37 -0
- package/scripts/verify-r65-parent-source.mjs +75 -0
- package/src/activation-continuity-engine-r65.mjs +225 -0
- package/src/activation-lock-r65.mjs +155 -0
- package/src/activation-preflight-r65.mjs +197 -0
- package/src/authority-continuity-r65.mjs +541 -0
- package/src/authority-epoch-cli-r65.mjs +10 -0
- package/src/autonomous-policy.mjs +7 -3
- package/src/coding-plane.mjs +12 -3
- package/src/desktop-policy-r40.mjs +6 -3
- package/src/doctor-transactions-r58.mjs +1 -1
- package/src/host-plane.mjs +9 -3
- package/src/process-policy.mjs +7 -4
- package/src/remote-authority.mjs +10 -1
- package/src/runtime-update.mjs +15 -5
- package/src/version.mjs +1 -1
- package/test/audit-context.test.mjs +15 -4
- package/test/autonomous-local-supervisor.test.mjs +7 -6
- package/test/autonomous-supervisor-integration.test.mjs +8 -7
- package/test/capability-resolver.test.mjs +11 -5
- package/test/codex-autonomous-r44.test.mjs +7 -6
- package/test/coding-observation.test.mjs +4 -3
- package/test/commander-real-parity-r47.test.mjs +5 -4
- package/test/commander-torture-r47.test.mjs +5 -4
- package/test/desktop-policy-r40.test.mjs +6 -4
- package/test/desktop-runtime-r40.test.mjs +8 -3
- package/test/host-inline-r45.test.mjs +4 -3
- package/test/machine-fs-runtime.test.mjs +4 -3
- package/test/process-longrun-r36.test.mjs +3 -2
- package/test/process-policy.test.mjs +10 -6
- package/test/process-runtime.test.mjs +9 -6
- package/test/r52-host-revocation.test.mjs +1 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r64-release-contract.test.mjs +2 -1
- package/test/r65-activation-lock-recovery.test.mjs +72 -0
- package/test/r65-activation-lock.test.mjs +59 -0
- package/test/r65-activation-orchestration.test.mjs +179 -0
- package/test/r65-activation-preflight.test.mjs +35 -0
- package/test/r65-authority-continuity-decision.test.mjs +58 -0
- package/test/r65-authority-durable-policy-binding.test.mjs +34 -0
- package/test/r65-authority-epoch-cli.test.mjs +125 -0
- package/test/r65-authority-epoch-races.test.mjs +87 -0
- package/test/r65-authority-epoch-recovery.test.mjs +82 -0
- package/test/r65-authority-epoch-store.test.mjs +94 -0
- package/test/r65-authority-fixture.mjs +41 -0
- package/test/r65-authority-grant-capture.test.mjs +37 -0
- package/test/r65-authority-observation-epoch.test.mjs +25 -0
- package/test/r65-authority-policy-envelope.test.mjs +46 -0
- package/test/r65-authority-restored-safe.test.mjs +75 -0
- package/test/r65-authority-runtime-epoch.test.mjs +115 -0
- package/test/r65-authority-runtime-policy-binding.test.mjs +29 -0
- package/test/r65-authority-test-trust.mjs +20 -0
- package/test/r65-cli-invocation.test.mjs +38 -0
- package/test/r65-controller-authority-epoch-live.test.mjs +100 -0
- package/test/r65-controller-authority-epoch.test.mjs +37 -0
- package/test/r65-deploy-preflight.test.mjs +55 -0
- package/test/r65-epoch-native-activation-integration.test.mjs +141 -0
- package/test/r65-epoch-native-continuity-engine.test.mjs +112 -0
- package/test/r65-epoch-native-cross-plane.test.mjs +95 -0
- package/test/r65-legacy-authority-migration.test.mjs +63 -0
- package/test/r65-legacy-auto-restore-rejection.test.mjs +17 -0
- package/test/r65-npm-publication.test.mjs +114 -0
- package/test/r65-parent-source-verifier.test.mjs +63 -0
- package/test/r65-release-contract.test.mjs +74 -0
- package/test/r65-transaction-precondition.test.mjs +36 -0
- package/test/remote-session-cli.test.mjs +6 -6
- package/test/standing-grants.test.mjs +20 -4
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import {
|
|
4
|
-
import { mkdtemp, readFile, rm, unlink } from 'node:fs/promises';
|
|
3
|
+
import { reconcileNpmPublication } from './npm-publication-r65.mjs';
|
|
4
|
+
import { mkdtemp, readFile, rm, unlink, mkdir, rename } from 'node:fs/promises';
|
|
5
|
+
import { existsSync } from 'node:fs';
|
|
6
|
+
import { resolveStateRoot } from '../src/state-root.mjs';
|
|
5
7
|
import path from 'node:path';
|
|
6
8
|
import os from 'node:os';
|
|
7
9
|
import { fileURLToPath } from 'node:url';
|
|
@@ -11,7 +13,7 @@ const node = process.execPath;
|
|
|
11
13
|
const npmCli = process.env.npm_execpath || path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
12
14
|
|
|
13
15
|
function run(command, args, options = {}) {
|
|
14
|
-
const { cwd = root, timeout = 180000 } = options;
|
|
16
|
+
const { cwd = root, timeout = 180000, env=process.env } = options;
|
|
15
17
|
const r = spawnSync(command, args, {
|
|
16
18
|
cwd,
|
|
17
19
|
encoding: 'utf8',
|
|
@@ -19,6 +21,7 @@ function run(command, args, options = {}) {
|
|
|
19
21
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
20
22
|
shell: false,
|
|
21
23
|
windowsHide: true,
|
|
24
|
+
env,
|
|
22
25
|
});
|
|
23
26
|
if (r.error) throw r.error;
|
|
24
27
|
if (r.status !== 0) {
|
|
@@ -95,30 +98,34 @@ async function main() {
|
|
|
95
98
|
const existingText = (existing.stdout || '').trim();
|
|
96
99
|
const isAlreadyPublished = existing.status === 0 && existingText && existingText !== 'null';
|
|
97
100
|
|
|
98
|
-
if (isAlreadyPublished) {
|
|
99
|
-
if (!isVerifyOnly) {
|
|
100
|
-
throw new Error(`deadbyte-mcp@${version} is already published on registry: ${existingText}`);
|
|
101
|
-
} else {
|
|
102
|
-
console.log(`Notice: deadbyte-mcp@${version} is already published on registry (${existingText}), proceeding with pre-publish verification only.`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
101
|
+
if (isAlreadyPublished) console.log(`Notice: deadbyte-mcp@${version} exists on registry (${existingText}); exact-byte reconciliation is required.`);
|
|
105
102
|
|
|
106
103
|
console.log('[2/7] Pre-pack gates: verifying working tree manifest and doctor...');
|
|
107
104
|
run(node, [path.join(root, 'scripts', 'release-manifest.mjs'), 'verify']);
|
|
108
|
-
|
|
105
|
+
const doctorState=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-release-doctor-'));
|
|
106
|
+
try{run(node,[path.join(root,'src','deadbyte-cli.mjs'),'doctor'],{env:{...process.env,DEADBYTE_STATE_ROOT:doctorState}});}
|
|
107
|
+
finally{await rm(doctorState,{recursive:true,force:true});}
|
|
109
108
|
|
|
110
109
|
console.log('[3/7] Packing artifact with npm pack...');
|
|
111
|
-
const
|
|
112
|
-
const
|
|
110
|
+
const publicationDir=isVerifyOnly?null:path.join(resolveStateRoot(),'evidence','npm-publication',version);
|
|
111
|
+
const preservedTarball=publicationDir?path.join(publicationDir,`deadbyte-mcp-${version}.tgz`):null;
|
|
112
|
+
const resuming=Boolean(preservedTarball&&existsSync(path.join(publicationDir,'claim.json')));
|
|
113
|
+
if(publicationDir)await mkdir(publicationDir,{recursive:true});
|
|
114
|
+
if(resuming&&!existsSync(preservedTarball))throw new Error('immutable npm publication claim exists but exact local tarball is missing; manual artifact reconciliation required');
|
|
115
|
+
if(!resuming&&preservedTarball&&existsSync(preservedTarball))throw new Error('unclaimed preserved npm tarball exists; inspect artifact evidence before another pack');
|
|
116
|
+
const packed=resuming?null:parseJson(runNpm(['pack', '--json'], { timeout: 180000 }));
|
|
117
|
+
const packRecord=resuming?{filename:path.basename(preservedTarball)}:(Array.isArray(packed) ? packed[0] : packed?.[pkg.name]);
|
|
113
118
|
const filename = packRecord?.filename;
|
|
114
119
|
if (!filename) throw new Error('npm pack did not return a tarball filename');
|
|
115
|
-
const tarball = path.join(root, filename);
|
|
120
|
+
const tarball = resuming?preservedTarball:path.join(root, filename);
|
|
116
121
|
let releaseSucceeded = false;
|
|
117
122
|
let publishAttempted = false;
|
|
123
|
+
let releasePending = false;
|
|
118
124
|
|
|
119
125
|
try {
|
|
120
126
|
console.log('[4/7] Verifying packed artifact file surface against MANIFEST.SHA256 and packaging contract...');
|
|
121
127
|
const manifestText = await readFile(path.join(root, 'MANIFEST.SHA256'), 'utf8');
|
|
128
|
+
const manifestSha256=createHash('sha256').update(manifestText).digest('hex');
|
|
122
129
|
const manifestEntries = new Set();
|
|
123
130
|
for (const line of manifestText.split(/\r?\n/)) {
|
|
124
131
|
if (!line) continue;
|
|
@@ -169,7 +176,7 @@ async function main() {
|
|
|
169
176
|
}
|
|
170
177
|
|
|
171
178
|
run(node, [path.join(extractedPackage, 'src', 'deadbyte-cli.mjs'), 'doctor'], {
|
|
172
|
-
cwd: extractedPackage,
|
|
179
|
+
cwd: extractedPackage,env:{...process.env,DEADBYTE_STATE_ROOT:path.join(sandbox,'state')},
|
|
173
180
|
});
|
|
174
181
|
|
|
175
182
|
console.log('Artifact isolation gate PASS (clean doctor and version check on unpacked tarball in sandbox)');
|
|
@@ -189,30 +196,37 @@ async function main() {
|
|
|
189
196
|
return;
|
|
190
197
|
}
|
|
191
198
|
|
|
192
|
-
|
|
199
|
+
if(!resuming)await rename(tarball,preservedTarball);
|
|
200
|
+
const publishTarball=preservedTarball;
|
|
201
|
+
const localBytes = await readFile(publishTarball);
|
|
202
|
+
const localIntegrity=`sha512-${createHash('sha512').update(localBytes).digest('base64')}`;
|
|
203
|
+
if(packRecord.integrity&&packRecord.integrity!==localIntegrity)throw new Error('npm packed artifact integrity changed before publication');
|
|
193
204
|
|
|
194
205
|
console.log('[6/7] Publishing artifact to npm registry...');
|
|
195
206
|
publishAttempted = true;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
207
|
+
const reconciliation=await reconcileNpmPublication({recordPath:publicationDir,version,manifestSha256,localBytes,
|
|
208
|
+
publish:async()=>runNpmInteractive(['publish',publishTarball,'--access','public','--auth-type=web'],{timeout:300000}),
|
|
209
|
+
lookup:async()=>{
|
|
210
|
+
try{
|
|
211
|
+
const viewed=parseJson(runNpm(['view',`deadbyte-mcp@${version}`,'version','dist.integrity','dist.shasum','dist.tarball','--json'],{timeout:120000}));
|
|
212
|
+
return Array.isArray(viewed)?viewed[0]:viewed;
|
|
213
|
+
}catch(error){if(/E404|404 Not Found/.test(String(error?.message)))return null;throw error;}
|
|
214
|
+
},
|
|
215
|
+
tags:async()=>parseJson(runNpm(['view','deadbyte-mcp','dist-tags','--json'],{timeout:120000})),
|
|
216
|
+
fetchTarball:async published=>{
|
|
217
|
+
const response=await fetch(published?.dist?.tarball,{signal:AbortSignal.timeout(120000)});
|
|
218
|
+
if(!response.ok)throw Object.assign(new Error(`registry tarball fetch failed status=${response.status}`),{status:response.status});
|
|
219
|
+
return Buffer.from(await response.arrayBuffer());
|
|
220
|
+
}});
|
|
221
|
+
if(reconciliation.state!=='REGISTRY_IDENTITY_VERIFIED'){
|
|
222
|
+
console.log('RELEASE_PENDING '+JSON.stringify({state:reconciliation.state,version,tarball:publishTarball,record_path:publicationDir,reason:reconciliation.reason??null}));
|
|
223
|
+
releasePending=true;
|
|
224
|
+
process.exitCode=2;return;
|
|
206
225
|
}
|
|
207
|
-
const
|
|
208
|
-
const published
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
const registryResponse = await fetch(dist.tarball,{signal:AbortSignal.timeout(120000)});
|
|
212
|
-
if(!registryResponse.ok) throw new Error(`registry tarball fetch failed status=${registryResponse.status}`);
|
|
213
|
-
const registryBytes = Buffer.from(await registryResponse.arrayBuffer());
|
|
214
|
-
const tags = parseJson(runNpm(['view', 'deadbyte-mcp', 'dist-tags', '--json'], { timeout: 120000 }));
|
|
215
|
-
const identity=verifyPublishedTarballIdentity({version,localBytes,registryBytes,published,tags});
|
|
226
|
+
const identity=reconciliation.identity;
|
|
227
|
+
const published={version,dist:{integrity:identity.npm_integrity,shasum:identity.shasum}};
|
|
228
|
+
const tags={latest:version};
|
|
229
|
+
const publishOut='registry identity verified by durable reconciliation';
|
|
216
230
|
|
|
217
231
|
console.log('[7/7] Post-publish external registry smoke test (executed outside repo in temp)...');
|
|
218
232
|
const tempDir = os.tmpdir();
|
|
@@ -227,6 +241,7 @@ async function main() {
|
|
|
227
241
|
const externalDoctor = runNpm(['exec', '--yes', `--package=deadbyte-mcp@${version}`, '--', 'deadbyte-mcp', 'doctor'], {
|
|
228
242
|
cwd: tempDir,
|
|
229
243
|
timeout: 300000,
|
|
244
|
+
env:{...process.env,DEADBYTE_STATE_ROOT:path.join(publicationDir,'external-doctor-state')},
|
|
230
245
|
});
|
|
231
246
|
|
|
232
247
|
if(!publishOnly){
|
|
@@ -253,10 +268,14 @@ async function main() {
|
|
|
253
268
|
}, null, 2));
|
|
254
269
|
|
|
255
270
|
} finally {
|
|
256
|
-
if (
|
|
271
|
+
if (isVerifyOnly || (!publishAttempted&&!publicationDir)) {
|
|
257
272
|
await unlink(tarball).catch(() => {});
|
|
273
|
+
} else if(releasePending){
|
|
274
|
+
console.log('Publication processing or reconciliation pending; exact tarball preserved: '+preservedTarball);
|
|
275
|
+
} else if(releaseSucceeded){
|
|
276
|
+
console.log('Verified release tarball retained for audit: '+preservedTarball);
|
|
258
277
|
} else {
|
|
259
|
-
console.error('Release artifact preserved
|
|
278
|
+
console.error('Release artifact preserved for reconciliation: ' + preservedTarball);
|
|
260
279
|
}
|
|
261
280
|
}
|
|
262
281
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {mkdirSync,readdirSync,writeFileSync} from 'node:fs';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const runId=process.argv[2]??'manual';
|
|
7
|
+
if(!/^[A-Za-z0-9._-]{1,96}$/.test(runId))throw new Error('invalid run id');
|
|
8
|
+
const testDir=path.join(process.cwd(),'test');
|
|
9
|
+
const tests=readdirSync(testDir,{withFileTypes:true})
|
|
10
|
+
.filter(entry=>entry.isFile()&&entry.name.endsWith('.test.mjs'))
|
|
11
|
+
.map(entry=>path.join('test',entry.name))
|
|
12
|
+
.sort((a,b)=>a.localeCompare(b,'en'));
|
|
13
|
+
const startedAt=new Date().toISOString();
|
|
14
|
+
const run=spawnSync(process.execPath,['--test','--test-concurrency=4',...tests],{
|
|
15
|
+
cwd:process.cwd(),
|
|
16
|
+
encoding:'utf8',
|
|
17
|
+
maxBuffer:128*1024*1024,
|
|
18
|
+
windowsHide:true,
|
|
19
|
+
shell:false
|
|
20
|
+
});
|
|
21
|
+
const stdout=run.stdout||'',stderr=run.stderr||'';
|
|
22
|
+
const evidenceDir=path.resolve(process.cwd(),'..','..','testing','0.15.42-r65','full-runs',runId);
|
|
23
|
+
mkdirSync(evidenceDir,{recursive:true});
|
|
24
|
+
writeFileSync(path.join(evidenceDir,'stdout.log'),stdout,'utf8');
|
|
25
|
+
writeFileSync(path.join(evidenceDir,'stderr.log'),stderr,'utf8');
|
|
26
|
+
const grab=(name)=>{const m=stdout.match(new RegExp('(?:ℹ|#)\\s*'+name+'\\s+(\\d+)','i'));return m?Number(m[1]):null;};
|
|
27
|
+
const result={
|
|
28
|
+
schema:'deadbyte.r65-full-run.v1',
|
|
29
|
+
run_id:runId,
|
|
30
|
+
started_at_utc:startedAt,
|
|
31
|
+
finished_at_utc:new Date().toISOString(),
|
|
32
|
+
command:[process.execPath,'--test','--test-concurrency=4',...tests],
|
|
33
|
+
test_files:tests.length,
|
|
34
|
+
exit_code:run.status??null,
|
|
35
|
+
signal:run.signal??null,
|
|
36
|
+
error:run.error?String(run.error.stack||run.error):null,
|
|
37
|
+
tests:grab('tests'),pass:grab('pass'),fail:grab('fail'),skipped:grab('skipped'),
|
|
38
|
+
stdout_sha256:createHash('sha256').update(stdout).digest('hex'),
|
|
39
|
+
stderr_sha256:createHash('sha256').update(stderr).digest('hex')
|
|
40
|
+
};
|
|
41
|
+
writeFileSync(path.join(evidenceDir,'result.json'),JSON.stringify(result,null,2)+'\n','utf8');
|
|
42
|
+
process.stdout.write(stdout);
|
|
43
|
+
process.stderr.write(stderr);
|
|
44
|
+
if(run.error)process.stderr.write(String(run.error.stack||run.error)+'\n');
|
|
45
|
+
process.exit(run.status??1);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
const tests=[
|
|
4
|
+
'test/r65-activation-lock.test.mjs',
|
|
5
|
+
'test/r65-activation-lock-recovery.test.mjs',
|
|
6
|
+
'test/r65-activation-orchestration.test.mjs',
|
|
7
|
+
'test/r65-activation-preflight.test.mjs',
|
|
8
|
+
'test/r65-authority-continuity-decision.test.mjs',
|
|
9
|
+
'test/r65-authority-durable-policy-binding.test.mjs',
|
|
10
|
+
'test/r65-authority-epoch-cli.test.mjs',
|
|
11
|
+
'test/r65-authority-epoch-races.test.mjs',
|
|
12
|
+
'test/r65-authority-epoch-recovery.test.mjs',
|
|
13
|
+
'test/r65-authority-epoch-store.test.mjs',
|
|
14
|
+
'test/r65-authority-grant-capture.test.mjs',
|
|
15
|
+
'test/r65-authority-observation-epoch.test.mjs',
|
|
16
|
+
'test/r65-authority-policy-envelope.test.mjs',
|
|
17
|
+
'test/r65-authority-runtime-epoch.test.mjs',
|
|
18
|
+
'test/r65-authority-runtime-policy-binding.test.mjs',
|
|
19
|
+
'test/r65-authority-restored-safe.test.mjs',
|
|
20
|
+
'test/r65-controller-authority-epoch.test.mjs',
|
|
21
|
+
'test/r65-controller-authority-epoch-live.test.mjs',
|
|
22
|
+
'test/r65-deploy-preflight.test.mjs',
|
|
23
|
+
'test/r65-epoch-native-activation-integration.test.mjs',
|
|
24
|
+
'test/r65-epoch-native-continuity-engine.test.mjs',
|
|
25
|
+
'test/r65-epoch-native-cross-plane.test.mjs',
|
|
26
|
+
'test/r65-legacy-authority-migration.test.mjs',
|
|
27
|
+
'test/r65-legacy-auto-restore-rejection.test.mjs',
|
|
28
|
+
'test/r65-parent-source-verifier.test.mjs',
|
|
29
|
+
'test/r65-release-contract.test.mjs',
|
|
30
|
+
'test/r65-transaction-precondition.test.mjs'
|
|
31
|
+
];
|
|
32
|
+
const run=spawnSync(process.execPath,['--test','--test-concurrency=2',...tests],{
|
|
33
|
+
cwd:process.cwd(),encoding:'utf8',maxBuffer:64*1024*1024,windowsHide:true
|
|
34
|
+
});
|
|
35
|
+
process.stdout.write(run.stdout||'');
|
|
36
|
+
process.stderr.write(run.stderr||'');
|
|
37
|
+
process.exit(run.status??1);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {lstat,readFile,realpath} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
|
|
6
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
7
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
8
|
+
|
|
9
|
+
function parseArgs(argv){
|
|
10
|
+
const out={root:null,expectedManifestSha256:null};
|
|
11
|
+
for(let i=0;i<argv.length;i++){
|
|
12
|
+
const arg=argv[i];
|
|
13
|
+
if(arg==='--root')out.root=argv[++i]??null;
|
|
14
|
+
else if(arg==='--expected-manifest-sha256')out.expectedManifestSha256=argv[++i]??null;
|
|
15
|
+
else throw new Error('unknown argument '+arg);
|
|
16
|
+
}
|
|
17
|
+
if(typeof out.root!=='string'||out.root.length<1)throw new Error('--root required');
|
|
18
|
+
if(!HEX64.test(String(out.expectedManifestSha256)))throw new Error('--expected-manifest-sha256 must be lowercase hex');
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseManifest(text){
|
|
23
|
+
const entries=[];
|
|
24
|
+
const seen=new Set();
|
|
25
|
+
for(const raw of text.split(/\r?\n/)){
|
|
26
|
+
if(raw==='')continue;
|
|
27
|
+
const match=/^([0-9a-f]{64}) (.+)$/.exec(raw);
|
|
28
|
+
if(!match)throw new Error('manifest line invalid');
|
|
29
|
+
const rel=match[2];
|
|
30
|
+
if(rel.includes('\\')||path.posix.isAbsolute(rel)||rel==='.'||rel.split('/').includes('..')||path.posix.normalize(rel)!==rel)
|
|
31
|
+
throw new Error('manifest path invalid: '+rel);
|
|
32
|
+
if(seen.has(rel))throw new Error('manifest duplicate path: '+rel);
|
|
33
|
+
seen.add(rel);
|
|
34
|
+
entries.push({sha256:match[1],rel});
|
|
35
|
+
}
|
|
36
|
+
if(entries.length<1)throw new Error('manifest has no entries');
|
|
37
|
+
return entries;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function verifyDeclaredParentSource({root,expectedManifestSha256}){
|
|
41
|
+
const absolute=path.resolve(root);
|
|
42
|
+
const rootReal=await realpath(absolute);
|
|
43
|
+
const manifestPath=path.join(absolute,'MANIFEST.SHA256');
|
|
44
|
+
const manifestBytes=await readFile(manifestPath);
|
|
45
|
+
const actualManifestSha=sha(manifestBytes);
|
|
46
|
+
if(actualManifestSha!==expectedManifestSha256)
|
|
47
|
+
throw new Error(`manifest SHA-256 mismatch expected=${expectedManifestSha256} actual=${actualManifestSha}`);
|
|
48
|
+
const entries=parseManifest(manifestBytes.toString('utf8'));
|
|
49
|
+
for(const entry of entries){
|
|
50
|
+
const target=path.resolve(absolute,...entry.rel.split('/'));
|
|
51
|
+
const info=await lstat(target).catch(error=>{
|
|
52
|
+
if(error?.code==='ENOENT')throw new Error('listed file missing: '+entry.rel);
|
|
53
|
+
throw error;
|
|
54
|
+
});
|
|
55
|
+
if(!info.isFile()||info.isSymbolicLink())throw new Error('listed path is not a regular file: '+entry.rel);
|
|
56
|
+
const targetReal=await realpath(target);
|
|
57
|
+
const prefix=rootReal.endsWith(path.sep)?rootReal:rootReal+path.sep;
|
|
58
|
+
if(targetReal!==rootReal&&!targetReal.startsWith(prefix))throw new Error('listed path escapes root: '+entry.rel);
|
|
59
|
+
const actual=sha(await readFile(target));
|
|
60
|
+
if(actual!==entry.sha256)throw new Error(`hash mismatch ${entry.rel} expected=${entry.sha256} actual=${actual}`);
|
|
61
|
+
}
|
|
62
|
+
return Object.freeze({files:entries.length,manifest_sha256:actualManifestSha,root:rootReal});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===path.resolve(fileURLToPath(import.meta.url));
|
|
66
|
+
if(invoked){
|
|
67
|
+
try{
|
|
68
|
+
const opts=parseArgs(process.argv.slice(2));
|
|
69
|
+
const result=await verifyDeclaredParentSource(opts);
|
|
70
|
+
console.log(`R65_PARENT_SOURCE_VERIFY_OK files=${result.files} manifest_sha256=${result.manifest_sha256}`);
|
|
71
|
+
}catch(error){
|
|
72
|
+
console.error('R65_PARENT_SOURCE_VERIFY_FAIL '+String(error?.stack??error));
|
|
73
|
+
process.exitCode=1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {createHash,randomBytes} from 'node:crypto';
|
|
2
|
+
import {open,readFile,rm} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {canonicalJson} from './canonical-json.mjs';
|
|
5
|
+
import {
|
|
6
|
+
captureStandingGrant,compareSecurityEnvelopes,createAuthorityEpochStore,deriveSecurityEnvelope
|
|
7
|
+
} from './authority-continuity-r65.mjs';
|
|
8
|
+
|
|
9
|
+
const PLANES=Object.freeze(['process','coding','autonomous','desktop','host']);
|
|
10
|
+
const REVOKE_ORDER=Object.freeze(['host','desktop','autonomous','coding','process']);
|
|
11
|
+
const RESTORE_ORDER=Object.freeze(['process','coding','autonomous','desktop','host']);
|
|
12
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
13
|
+
const GRANT_PATH_FIELD=Object.freeze({
|
|
14
|
+
process:'grant_file',coding:'lease_file',autonomous:'lease_file',desktop:'grant_file',host:'arm_file'
|
|
15
|
+
});
|
|
16
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
17
|
+
const plain=value=>value&&typeof value==='object'&&!Array.isArray(value);
|
|
18
|
+
|
|
19
|
+
function parseJson(bytes,label){
|
|
20
|
+
try{return JSON.parse(bytes.toString('utf8'));}
|
|
21
|
+
catch{throw new Error(`${label} JSON invalid`);}
|
|
22
|
+
}
|
|
23
|
+
function inside(root,target){
|
|
24
|
+
const rel=path.relative(path.resolve(root),path.resolve(target));
|
|
25
|
+
return rel===''||(!rel.startsWith('..'+path.sep)&&rel!=='..'&&!path.isAbsolute(rel));
|
|
26
|
+
}
|
|
27
|
+
function grantPath(policy,plane,stateRoot){
|
|
28
|
+
const field=GRANT_PATH_FIELD[plane],raw=policy?.[field];
|
|
29
|
+
if(typeof raw!=='string'||!path.isAbsolute(raw))throw new Error(`${plane} grant path missing`);
|
|
30
|
+
const resolved=path.resolve(raw),runtimeRoot=path.join(path.resolve(stateRoot),'runtime');
|
|
31
|
+
if(!inside(runtimeRoot,resolved))throw new Error(`${plane} grant path escapes runtime state`);
|
|
32
|
+
return resolved;
|
|
33
|
+
}
|
|
34
|
+
async function readOptional(file){
|
|
35
|
+
try{return await readFile(file);}catch(error){if(error?.code==='ENOENT')return null;throw error;}
|
|
36
|
+
}
|
|
37
|
+
async function writeExclusive(file,bytes){
|
|
38
|
+
const handle=await open(file,'wx',0o600);
|
|
39
|
+
try{await handle.writeFile(bytes);await handle.sync();}
|
|
40
|
+
finally{await handle.close();}
|
|
41
|
+
}
|
|
42
|
+
async function removeIfExact(file,expectedSha256){
|
|
43
|
+
const current=await readOptional(file);
|
|
44
|
+
if(current===null)return {removed:false,reason:'absent'};
|
|
45
|
+
if(sha(current)!==expectedSha256)return {removed:false,reason:'replacement'};
|
|
46
|
+
await rm(file,{force:true});
|
|
47
|
+
return {removed:true,reason:'matched'};
|
|
48
|
+
}
|
|
49
|
+
function validateCapture(capture){
|
|
50
|
+
if(!plain(capture)||typeof capture.update_id!=='string'||!Array.isArray(capture.planes)||!plain(capture.records))throw new Error('authority continuity capture invalid');
|
|
51
|
+
for(const plane of capture.planes)if(!PLANES.includes(plane)||!capture.records[plane])throw new Error('authority continuity capture plane invalid');
|
|
52
|
+
}
|
|
53
|
+
function scopePermitted(plane,scope,policy){
|
|
54
|
+
if(plane==='process')return Array.isArray(scope.root_ids)&&Array.isArray(scope.profile_ids)&&
|
|
55
|
+
scope.root_ids.every(id=>Object.hasOwn(policy.roots??{},id))&&
|
|
56
|
+
scope.profile_ids.every(id=>Object.hasOwn(policy.profiles??{},id)&&
|
|
57
|
+
Array.isArray(policy.profiles[id]?.root_ids)&&policy.profiles[id].root_ids.every(rootId=>scope.root_ids.includes(rootId)));
|
|
58
|
+
if(plane==='coding')return Array.isArray(scope.root_ids)&&scope.root_ids.every(id=>Object.hasOwn(policy.roots??{},id));
|
|
59
|
+
if(plane==='autonomous')return Array.isArray(scope.root_ids)&&scope.root_ids.every(id=>(policy.root_ids??[]).includes(id));
|
|
60
|
+
if(plane==='desktop')return Array.isArray(scope.action_kinds)&&scope.action_kinds.every(id=>(policy.allowed_actions??[]).includes(id));
|
|
61
|
+
return plane==='host';
|
|
62
|
+
}
|
|
63
|
+
function buildSuccessorGrant({plane,record,successorPolicyBytes,successorPolicy}){
|
|
64
|
+
const policySha256=sha(successorPolicyBytes),instanceNonce=randomBytes(32).toString('hex'),grantedAt=new Date().toISOString();
|
|
65
|
+
let grant;
|
|
66
|
+
if(plane==='process')grant={schema:record.grant_schema,policy_sha256:policySha256,instance_nonce:instanceNonce,
|
|
67
|
+
profile_ids:[...record.granted_scope.profile_ids],root_ids:[...record.granted_scope.root_ids],granted_at_utc:grantedAt};
|
|
68
|
+
else if(plane==='coding')grant={schema:record.grant_schema,policy_sha256:policySha256,instance_nonce:instanceNonce,
|
|
69
|
+
root_ids:[...record.granted_scope.root_ids],granted_at_utc:grantedAt};
|
|
70
|
+
else if(plane==='autonomous')grant={schema:record.grant_schema,policy_sha256:policySha256,
|
|
71
|
+
coding_policy_sha256:successorPolicy.coding_policy_sha256,instance_nonce:instanceNonce,
|
|
72
|
+
root_ids:[...record.granted_scope.root_ids],granted_at_utc:grantedAt};
|
|
73
|
+
else if(plane==='desktop')grant={schema:record.grant_schema,policy_sha256:policySha256,instance_nonce:instanceNonce,
|
|
74
|
+
action_kinds:[...record.granted_scope.action_kinds],granted_at_utc:grantedAt};
|
|
75
|
+
else if(plane==='host')grant={schema:record.grant_schema,policy_sha256:policySha256,instance_nonce:instanceNonce,granted_at_utc:grantedAt};
|
|
76
|
+
else throw new Error('authority continuity plane invalid');
|
|
77
|
+
return {grant,bytes:Buffer.from(canonicalJson(grant),'utf8'),instanceNonce};
|
|
78
|
+
}
|
|
79
|
+
function classifyConflict(head,record,updateId){
|
|
80
|
+
if(!head)return 'REAUTH_REQUIRED';
|
|
81
|
+
if(head.action==='user_revoke')return 'REVOKED_DURING_UPDATE';
|
|
82
|
+
if((head.action==='grant'||head.action==='transaction_restore')&&
|
|
83
|
+
(head.grant_sha256!==record.grant_sha256||head.grant_instance_nonce!==record.grant_instance_nonce))return 'REPLACED';
|
|
84
|
+
if(head.action==='transaction_revoke'&&head.update_id===updateId&&
|
|
85
|
+
head.grant_sha256===record.grant_sha256&&head.grant_instance_nonce===record.grant_instance_nonce)return null;
|
|
86
|
+
return 'REAUTH_REQUIRED';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createEpochNativeContinuityEngine({stateRoot,predecessorPolicyPaths,successorPolicyPaths,updateId}={}){
|
|
90
|
+
const state=path.resolve(String(stateRoot??''));
|
|
91
|
+
if(!stateRoot||!plain(predecessorPolicyPaths)||typeof successorPolicyPaths!=='function'||
|
|
92
|
+
typeof updateId!=='string'||!/^[A-Za-z0-9._-]{1,96}$/.test(updateId))
|
|
93
|
+
throw new Error('authority continuity engine configuration invalid');
|
|
94
|
+
|
|
95
|
+
return Object.freeze({
|
|
96
|
+
async capture({observation,subject_sha256,pointer,expectedParent,candidate,expectedCandidate}){
|
|
97
|
+
if(!HEX64.test(String(subject_sha256))||!plain(observation?.authority))throw new Error('authority continuity capture subject invalid');
|
|
98
|
+
if(expectedParent?.release_id!==pointer?.release_id||expectedParent?.generation_manifest_sha256!==pointer?.generation_manifest_sha256||
|
|
99
|
+
expectedCandidate?.version!==candidate?.version||expectedCandidate?.manifest_sha256!==candidate?.manifest_sha256)
|
|
100
|
+
throw new Error('authority continuity lineage mismatch');
|
|
101
|
+
const records={},planes=[];
|
|
102
|
+
for(const plane of PLANES){
|
|
103
|
+
const authority=observation.authority[plane];
|
|
104
|
+
if(!authority?.armed)continue;
|
|
105
|
+
if(!Number.isSafeInteger(authority.epoch_seq)||authority.epoch_seq<1||!HEX64.test(String(authority.epoch_sha256)))
|
|
106
|
+
throw new Error(`${plane} standing authority is legacy or epoch identity is incomplete`);
|
|
107
|
+
const policyPath=predecessorPolicyPaths[plane];
|
|
108
|
+
if(typeof policyPath!=='string'||!path.isAbsolute(policyPath))throw new Error(`${plane} predecessor policy path unavailable`);
|
|
109
|
+
const policyBytes=await readFile(policyPath),policy=parseJson(policyBytes,`${plane} predecessor policy`);
|
|
110
|
+
const file=grantPath(policy,plane,state),grantBytes=await readFile(file),grant=parseJson(grantBytes,`${plane} predecessor grant`);
|
|
111
|
+
const store=createAuthorityEpochStore({runtimeDir:path.dirname(file),policyBytes});
|
|
112
|
+
const verified=await store.verifyGrant({plane,grantBytes,grantInstanceNonce:grant.instance_nonce,policyBytes});
|
|
113
|
+
const head=await store.readHead({plane});
|
|
114
|
+
if(!verified.trusted||!head||head.action!=='grant'&&head.action!=='transaction_restore'||
|
|
115
|
+
head.seq!==authority.epoch_seq||head.event_sha256!==authority.epoch_sha256)
|
|
116
|
+
throw new Error(`${plane} observed standing authority does not match durable epoch`);
|
|
117
|
+
const record=captureStandingGrant({plane,policyBytes,grantBytes,predecessor:expectedParent,candidate:expectedCandidate,updateId});
|
|
118
|
+
records[plane]=Object.freeze({record,policy_path:policyPath,grant_file:file,prior_policy_bytes:policyBytes,
|
|
119
|
+
prior_grant_bytes:grantBytes,captured_head:Object.freeze({...head})});
|
|
120
|
+
planes.push(plane);
|
|
121
|
+
}
|
|
122
|
+
if(planes.length===0)throw new Error('epoch-native continuity capture has no armed planes');
|
|
123
|
+
return Object.freeze({schema:'deadbyte.activation-authority-capture.r65.v1',update_id:updateId,subject_sha256,
|
|
124
|
+
planes:Object.freeze(planes),records:Object.freeze(records)});
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
async revoke(capture){
|
|
128
|
+
validateCapture(capture);
|
|
129
|
+
const outcomes={};
|
|
130
|
+
for(const plane of REVOKE_ORDER){
|
|
131
|
+
const item=capture.records[plane];
|
|
132
|
+
if(!item)continue;
|
|
133
|
+
const {record,prior_policy_bytes:policyBytes,prior_grant_bytes:grantBytes,grant_file:file,captured_head:capturedHead}=item;
|
|
134
|
+
const store=createAuthorityEpochStore({runtimeDir:path.dirname(file),policyBytes});
|
|
135
|
+
const current=await store.readHead({plane});
|
|
136
|
+
if(!current||current.event_sha256!==capturedHead.event_sha256||current.seq!==capturedHead.seq)
|
|
137
|
+
throw new Error(`${plane} authority changed before transaction revoke`);
|
|
138
|
+
const revoked=await store.append({plane,action:'transaction_revoke',grantBytes,
|
|
139
|
+
grantInstanceNonce:record.grant_instance_nonce,updateId:capture.update_id,expectedHeadSha256:capturedHead.event_sha256});
|
|
140
|
+
let observed=await store.readHead({plane});
|
|
141
|
+
if(!observed||observed.event_sha256!==revoked.event_sha256)
|
|
142
|
+
throw new Error(`${plane} authority changed during transaction revoke`);
|
|
143
|
+
const sentinel=await readOptional(file);
|
|
144
|
+
if(sentinel===null)throw new Error(`${plane} grant disappeared during transaction revoke`);
|
|
145
|
+
if(sha(sentinel)!==record.grant_sha256)throw new Error(`${plane} replacement grant appeared during transaction revoke`);
|
|
146
|
+
observed=await store.readHead({plane});
|
|
147
|
+
if(!observed||observed.event_sha256!==revoked.event_sha256){
|
|
148
|
+
const conflict=classifyConflict(observed,record,capture.update_id);
|
|
149
|
+
throw new Error(`${plane} authority changed after transaction revoke: ${conflict??'unknown'}`);
|
|
150
|
+
}
|
|
151
|
+
const verification=await store.verifyGrant({plane,grantBytes,grantInstanceNonce:record.grant_instance_nonce,policyBytes});
|
|
152
|
+
if(verification.trusted)throw new Error(`${plane} remained trusted after transaction revoke`);
|
|
153
|
+
outcomes[plane]='TRANSACTION_REVOKED';
|
|
154
|
+
}
|
|
155
|
+
return Object.freeze(outcomes);
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
async restore({capture,successor_observation,activation,candidate,expectedCandidate,subject_sha256}){
|
|
159
|
+
validateCapture(capture);
|
|
160
|
+
if(capture.subject_sha256!==subject_sha256||expectedCandidate?.version!==candidate?.version||
|
|
161
|
+
expectedCandidate?.manifest_sha256!==candidate?.manifest_sha256||activation?.verified!==true)
|
|
162
|
+
throw new Error('authority continuity restore subject invalid');
|
|
163
|
+
const paths=await successorPolicyPaths();
|
|
164
|
+
if(!plain(paths))throw new Error('successor authority policy paths unavailable');
|
|
165
|
+
const outcomes=Object.fromEntries(PLANES.map(plane=>[plane,capture.records[plane]?'REAUTH_REQUIRED':'NOT_APPLICABLE']));
|
|
166
|
+
const reauth=[],restoredNonces={};
|
|
167
|
+
let codingRestored=!capture.records.coding;
|
|
168
|
+
for(const plane of RESTORE_ORDER){
|
|
169
|
+
const item=capture.records[plane];
|
|
170
|
+
if(!item)continue;
|
|
171
|
+
const {record,prior_policy_bytes:priorPolicyBytes,prior_grant_bytes:priorGrantBytes,grant_file:file}=item;
|
|
172
|
+
if(plane==='autonomous'&&!codingRestored){outcomes[plane]='REAUTH_REQUIRED';reauth.push(plane);continue;}
|
|
173
|
+
const policyPath=paths[plane];
|
|
174
|
+
if(typeof policyPath!=='string'||!path.isAbsolute(policyPath)){outcomes[plane]='REAUTH_REQUIRED';reauth.push(plane);continue;}
|
|
175
|
+
let successorPolicyBytes,successorPolicy,priorPolicy;
|
|
176
|
+
try{
|
|
177
|
+
successorPolicyBytes=await readFile(policyPath);
|
|
178
|
+
successorPolicy=parseJson(successorPolicyBytes,`${plane} successor policy`);
|
|
179
|
+
priorPolicy=parseJson(priorPolicyBytes,`${plane} predecessor policy`);
|
|
180
|
+
}catch{outcomes[plane]='REAUTH_REQUIRED';reauth.push(plane);continue;}
|
|
181
|
+
const priorEnvelope=deriveSecurityEnvelope(plane,priorPolicy),successorEnvelope=deriveSecurityEnvelope(plane,successorPolicy);
|
|
182
|
+
const relation=compareSecurityEnvelopes(plane,priorEnvelope,successorEnvelope).relation;
|
|
183
|
+
if(relation==='WIDER'){outcomes[plane]='POLICY_WIDENED';reauth.push(plane);continue;}
|
|
184
|
+
if(relation==='UNVERIFIED'||!scopePermitted(plane,record.granted_scope,successorPolicy)){
|
|
185
|
+
outcomes[plane]='REAUTH_REQUIRED';reauth.push(plane);continue;
|
|
186
|
+
}
|
|
187
|
+
const successorGrantPath=grantPath(successorPolicy,plane,state);
|
|
188
|
+
if(successorGrantPath!==file){outcomes[plane]='REAUTH_REQUIRED';reauth.push(plane);continue;}
|
|
189
|
+
const store=createAuthorityEpochStore({runtimeDir:path.dirname(file),policyBytes:successorPolicyBytes});
|
|
190
|
+
const head=await store.readHead({plane}),conflict=classifyConflict(head,record,capture.update_id);
|
|
191
|
+
if(conflict){
|
|
192
|
+
if(conflict==='REVOKED_DURING_UPDATE')await removeIfExact(file,record.grant_sha256).catch(()=>{});
|
|
193
|
+
outcomes[plane]=conflict;reauth.push(plane);continue;
|
|
194
|
+
}
|
|
195
|
+
const existing=await readOptional(file);
|
|
196
|
+
if(existing===null){outcomes[plane]='REVOKED_DURING_UPDATE';reauth.push(plane);continue;}
|
|
197
|
+
if(sha(existing)!==record.grant_sha256){outcomes[plane]='REPLACED';reauth.push(plane);continue;}
|
|
198
|
+
await rm(file,{force:true});
|
|
199
|
+
const prepared=buildSuccessorGrant({plane,record,successorPolicyBytes,successorPolicy});
|
|
200
|
+
try{await writeExclusive(file,prepared.bytes);}
|
|
201
|
+
catch(error){if(error?.code==='EEXIST'){outcomes[plane]='REPLACED';reauth.push(plane);continue;}throw error;}
|
|
202
|
+
try{
|
|
203
|
+
const restoreBinding={prior_grant_sha256:record.grant_sha256,prior_grant_instance_nonce:record.grant_instance_nonce,
|
|
204
|
+
predecessor_policy_envelope_sha256:record.prior_policy_envelope_sha256,successor_policy_envelope_sha256:successorEnvelope.sha256,
|
|
205
|
+
candidate_manifest_sha256:expectedCandidate.manifest_sha256,continuity_subject_sha256:capture.subject_sha256};
|
|
206
|
+
const restored=await store.append({plane,action:'transaction_restore',grantBytes:prepared.bytes,
|
|
207
|
+
grantInstanceNonce:prepared.instanceNonce,updateId:capture.update_id,expectedHeadSha256:head.event_sha256,restoreBinding});
|
|
208
|
+
const verified=await store.verifyGrant({plane,grantBytes:prepared.bytes,grantInstanceNonce:prepared.instanceNonce,policyBytes:successorPolicyBytes});
|
|
209
|
+
if(!verified.trusted||verified.event_sha256!==restored.event_sha256)throw new Error(`${plane} restored grant did not verify`);
|
|
210
|
+
outcomes[plane]=relation==='NARROWER'?'RESTORED_SAFE':'RESTORED_EXACT';restoredNonces[plane]=prepared.instanceNonce;
|
|
211
|
+
if(plane==='coding')codingRestored=true;
|
|
212
|
+
}catch(error){
|
|
213
|
+
const current=await store.readHead({plane}).catch(()=>null);
|
|
214
|
+
const currentConflict=classifyConflict(current,record,capture.update_id);
|
|
215
|
+
const cleanup=await removeIfExact(file,sha(prepared.bytes)).catch(()=>({reason:'cleanup-failed'}));
|
|
216
|
+
if(currentConflict){outcomes[plane]=currentConflict;reauth.push(plane);continue;}
|
|
217
|
+
if(cleanup.reason==='replacement'){outcomes[plane]='REPLACED';reauth.push(plane);continue;}
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return Object.freeze({authority_outcomes:Object.freeze(outcomes),restored_nonces:Object.freeze(restoredNonces),
|
|
222
|
+
reauth_planes:Object.freeze([...new Set(reauth)])});
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|