deadbyte-mcp 0.15.74 → 0.15.76

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.
Files changed (109) hide show
  1. package/MANIFEST.SHA256 +108 -42
  2. package/PROVENANCE.json +20 -22
  3. package/README.txt +23 -18
  4. package/RELEASE.json +69 -18
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +32 -2
  7. package/bin/deadbyte-desktop-uia-r199-fixed.exe +0 -0
  8. package/bin/deadbyte-desktop-uia-r199-fixed.obj +0 -0
  9. package/bin/deadbyte-desktop-uia-r199.exe +0 -0
  10. package/bin/deadbyte-desktop-uia-r199.obj +0 -0
  11. package/bin/deadbyte-fs-read-r197-fixed.exe +0 -0
  12. package/bin/deadbyte-fs-read-r198.exe +0 -0
  13. package/bin/deadbyte-fs-read-r198.obj +0 -0
  14. package/bin/deadbyte-root-relative-probe-r197.exe +0 -0
  15. package/bridge/package.json +1 -1
  16. package/controller/deadbyte-process-policy.json +4 -4
  17. package/docs/R184-package-lock.json +2 -8
  18. package/native/deadbyte-desktop-uia-r199.cpp +345 -0
  19. package/native/deadbyte-fs-read-r197.cpp +174 -0
  20. package/native/deadbyte-fs-read-r198.cpp +181 -0
  21. package/native/deadbyte-root-relative-probe-r197.cpp +97 -0
  22. package/package.json +5 -2
  23. package/scripts/build-native-desktop-uia-r199-fixed.ps1 +28 -0
  24. package/scripts/build-native-desktop-uia-r199.ps1 +28 -0
  25. package/scripts/build-native-fs-read-r197-fixed.ps1 +28 -0
  26. package/scripts/build-native-fs-read-r197.ps1 +28 -0
  27. package/scripts/build-native-fs-read-r198.ps1 +28 -0
  28. package/scripts/build-native-root-probe-r197.ps1 +28 -0
  29. package/scripts/gate-windows-r195.ps1 +114 -0
  30. package/scripts/gate-windows-r206.ps1 +116 -0
  31. package/scripts/gate-windows-r207.ps1 +104 -0
  32. package/scripts/init-desktop-policy-r199.ps1 +85 -0
  33. package/scripts/npm-release-r48.mjs +27 -0
  34. package/scripts/npm-release-readiness-r195.mjs +72 -0
  35. package/scripts/npm-release-readiness-r206.mjs +76 -0
  36. package/scripts/npm-release-readiness-r207.mjs +76 -0
  37. package/scripts/r193-owner-request-live.mjs +8 -7
  38. package/scripts/r193-owner-uac-broker.mjs +6 -5
  39. package/scripts/r205-candidate-selection.mjs +22 -0
  40. package/src/candidate-selection-evidence-r206.mjs +71 -0
  41. package/src/candidate-selection-r205.mjs +96 -0
  42. package/src/desktop-postcondition-r196.mjs +30 -0
  43. package/src/desktop-runtime-r40.mjs +44 -7
  44. package/src/desktop-uia-identity-r199.mjs +51 -0
  45. package/src/desktop-uia-r40.mjs +9 -1
  46. package/src/machine-fs-handle-read-r196.mjs +43 -0
  47. package/src/machine-fs-native-read-r197.mjs +73 -0
  48. package/src/machine-fs-native-read-r198.mjs +76 -0
  49. package/src/machine-fs-read-worker.mjs +5 -25
  50. package/src/machine-fs.mjs +31 -16
  51. package/src/r195-canonical-owner-observation.mjs +15 -0
  52. package/src/r207-owner-enrollment-io.mjs +30 -0
  53. package/src/remote-destination-journal-r202.mjs +120 -0
  54. package/src/remote-device-r44.mjs +89 -14
  55. package/src/remote-payload-boundary-r201.mjs +71 -0
  56. package/src/remote-terminal-evidence-r200.mjs +60 -0
  57. package/src/version.mjs +1 -1
  58. package/test/desktop-runtime-r40.test.mjs +111 -0
  59. package/test/desktop-uia-r40.test.mjs +15 -0
  60. package/test/fixtures/r197-junction-swap-worker.mjs +21 -0
  61. package/test/fixtures/r203-journal-writer.mjs +6 -0
  62. package/test/process-policy-refresh.test.mjs +4 -4
  63. package/test/r130-release-security-contract.test.mjs +8 -8
  64. package/test/r132-release-identity.test.mjs +7 -7
  65. package/test/r153-prepublish-identity.test.mjs +1 -1
  66. package/test/r160-release-contract.test.mjs +3 -3
  67. package/test/r165-live-cli-skew.test.mjs +1 -1
  68. package/test/r167-release-contract.test.mjs +11 -11
  69. package/test/r168-release-contract.test.mjs +4 -4
  70. package/test/r172-release-contract.test.mjs +6 -6
  71. package/test/r173-release-contract.test.mjs +16 -16
  72. package/test/r179-release-contract.test.mjs +12 -12
  73. package/test/r190-release-contract.test.mjs +10 -10
  74. package/test/r191-release-contract.test.mjs +12 -12
  75. package/test/r192-release-identity.test.mjs +9 -9
  76. package/test/r193-release-identity.test.mjs +24 -22
  77. package/test/r193-release-readiness.test.mjs +2 -2
  78. package/test/r193-windows-gate.test.mjs +10 -10
  79. package/test/r194-release-identity.test.mjs +19 -15
  80. package/test/r194-release-readiness.test.mjs +3 -3
  81. package/test/r194-windows-gate.test.mjs +9 -8
  82. package/test/r195-canonical-owner-observation.test.mjs +32 -0
  83. package/test/r195-release-readiness.test.mjs +22 -0
  84. package/test/r196-desktop-postcondition.test.mjs +53 -0
  85. package/test/r196-handle-bound-read.test.mjs +99 -0
  86. package/test/r196-remote-outcome.test.mjs +94 -0
  87. package/test/r197-namespace-race.test.mjs +41 -0
  88. package/test/r197-native-dispatch-race.test.mjs +49 -0
  89. package/test/r197-native-file-read.test.mjs +60 -0
  90. package/test/r197-native-root-open.test.mjs +61 -0
  91. package/test/r198-root-identity.test.mjs +88 -0
  92. package/test/r199-native-uia-worker.test.mjs +28 -0
  93. package/test/r199-uia-identity.test.mjs +43 -0
  94. package/test/r200-remote-reconcile.test.mjs +106 -0
  95. package/test/r201-payload-boundary.test.mjs +78 -0
  96. package/test/r201-payload-fuzz.test.mjs +37 -0
  97. package/test/r201-tool-boundary-fuzz.test.mjs +120 -0
  98. package/test/r202-destination-journal.test.mjs +92 -0
  99. package/test/r203-atomic-journal.test.mjs +86 -0
  100. package/test/r205-candidate-selection-cli.test.mjs +31 -0
  101. package/test/r205-candidate-selection.test.mjs +83 -0
  102. package/test/r206-candidate-evidence.test.mjs +64 -0
  103. package/test/r206-release-contract.test.mjs +42 -0
  104. package/test/r206-release-readiness.test.mjs +23 -0
  105. package/test/r207-owner-enrollment-io.test.mjs +37 -0
  106. package/test/r207-release-contract.test.mjs +51 -0
  107. package/test/r207-release-readiness.test.mjs +23 -0
  108. package/test/r59-tool-surface-compat.test.mjs +1 -1
  109. package/test/r64-machine-fs-large-read.test.mjs +26 -12
@@ -0,0 +1,76 @@
1
+ import {createHash} from 'node:crypto';
2
+ import {lstat,readFile} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
6
+ const hex64=value=>typeof value==='string'&&/^[0-9a-f]{64}$/.test(value);
7
+ const fail=message=>{throw new Error(`PUBLISH_BLOCKED R206 ${message}`);};
8
+ const required=(value,message)=>{if(!value)fail(message);};
9
+
10
+ async function checkedFile(file,expected,{maxBytes=64*1024*1024}={}){
11
+ required(hex64(expected),'evidence digest missing');
12
+ let info;try{info=await lstat(file);}catch{fail('evidence file absent');}
13
+ required(info.isFile()&&!info.isSymbolicLink()&&info.size>0&&info.size<=maxBytes,'evidence file invalid');
14
+ const bytes=await readFile(file);
15
+ required(sha256(bytes)===expected,'evidence digest differs');
16
+ return bytes;
17
+ }
18
+
19
+ function tapSummary(text,label){
20
+ const summaries=[...text.matchAll(/^# tests (\d+)\s*\r?\n# suites (\d+)\s*\r?\n# pass (\d+)\s*\r?\n# fail (\d+)\s*\r?\n# cancelled (\d+)\s*\r?\n# skipped (\d+)/gm)];
21
+ required(summaries.length>0,`${label} TAP summary absent`);
22
+ const last=summaries.at(-1);
23
+ return {tests:Number(last[1]),passes:Number(last[3]),fails:Number(last[4]),cancelled:Number(last[5]),skips:Number(last[6])};
24
+ }
25
+
26
+ export function assertR206PackedIdentity({tarball_sha256,tarball_integrity,tarball_shasum,observedSha256,observedIntegrity,observedShasum}={}){
27
+ required(hex64(tarball_sha256)&&typeof tarball_integrity==='string'&&tarball_integrity.startsWith('sha512-')&&
28
+ typeof tarball_shasum==='string'&&/^[0-9a-f]{40}$/.test(tarball_shasum),'verified tarball identity invalid');
29
+ required(observedSha256===tarball_sha256&&observedIntegrity===tarball_integrity&&observedShasum===tarball_shasum,
30
+ 'repacked tarball differs from verified closure bytes');
31
+ return true;
32
+ }
33
+
34
+ export async function assertR206NpmPublishReady(release,{root,closureRoot=process.env.DEADBYTE_R206_CLOSURE_ROOT}={}){
35
+ required(release?.version==='0.15.75'&&release?.train==='R206'&&
36
+ release?.r206?.scope==='EVIDENCE_BOUND_CANDIDATE_SELECTION_RELEASE_CLOSURE','candidate metadata invalid');
37
+ required(release?.r206?.public_tool_delta===0&&release?.r206?.authority_scope_delta===0&&
38
+ release?.r206?.selector_release_authority===false,'public authority surface widened');
39
+ required(release?.source_predecessor?.train==='R205'&&
40
+ release?.source_predecessor?.manifest_sha256==='166eebde18d53a9ec46f738383c289733f9cd37918b3572b7515d1ab9b122c92',
41
+ 'source predecessor mismatch');
42
+ required(release?.parent?.version==='0.15.74'&&release?.parent?.release_id==='v0.15.74-e84bf4eed16e9443'&&
43
+ release?.parent?.manifest_sha256==='e84bf4eed16e9443fbd0bf9f0f562e26b01db778647aefcc8b12fd7f48636b47'&&
44
+ release?.parent?.generation_manifest_sha256==='598f896be9ce65e6b0c618e8c8f6eaa2a9d91617a76a146bf3deb385a56d19e7','lineage mismatch');
45
+ required(root&&path.isAbsolute(root)&&closureRoot&&path.isAbsolute(closureRoot),'closure root absent');
46
+ const expected=path.resolve(root,'..','..','testing','r206-final-closure');
47
+ required(path.resolve(closureRoot).toLowerCase()===expected.toLowerCase(),'closure root outside scoped testing folder');
48
+ let closure;try{closure=JSON.parse(await readFile(path.join(closureRoot,'closure.json'),'utf8'));}catch{fail('closure unreadable');}
49
+ required(closure?.schema==='deadbyte.r206-release-closure.v1'&&closure.status==='RELEASE_READY'&&
50
+ closure.version==='0.15.75'&&closure.train==='R206','closure status mismatch');
51
+ required(closure.production_release_id===release.parent.release_id&&closure.production_manifest_sha256===release.parent.manifest_sha256&&
52
+ closure.production_generation_manifest_sha256===release.parent.generation_manifest_sha256,'production predecessor mismatch');
53
+ required(hex64(closure.manifest_sha256)&&closure.manifest_sha256===sha256(await readFile(path.join(root,'MANIFEST.SHA256'))),'final manifest drift');
54
+ required(closure.compact_tools===43&&closure.full_tools===117,'catalog count mismatch');
55
+ required(closure.owner_enrollment==='NOT_RUN'&&closure.production_activation==='NOT_RUN','prepublication closure crossed authority boundary');
56
+ const receipt=JSON.parse((await checkedFile(path.join(closureRoot,'protected-install-receipt.json'),closure.protected_receipt_sha256)).toString('utf8'));
57
+ required(receipt?.status==='PASS'&&receipt.package_version==='0.15.75'&&receipt.source_manifest_sha256===closure.manifest_sha256&&
58
+ receipt.protected_manifest_sha256===closure.manifest_sha256,'protected exact-byte receipt mismatch');
59
+ const focused=(await checkedFile(path.join(closureRoot,'windows-focused.stdout.log'),closure.focused_stdout_sha256)).toString('utf8');
60
+ const full=(await checkedFile(path.join(closureRoot,'windows-full.stdout.log'),closure.full_stdout_sha256)).toString('utf8');
61
+ const verify=(await checkedFile(path.join(closureRoot,'npm-verify.stdout.log'),closure.verify_stdout_sha256)).toString('utf8');
62
+ required(focused.includes('R206_STAGE_PASS=focused'),'focused protected gate incomplete');
63
+ const focusedSummary=tapSummary(focused,'focused');
64
+ required(focusedSummary.fails===0&&focusedSummary.cancelled===0&&focusedSummary.passes>=70&&focusedSummary.skips<=3,'focused regression count mismatch');
65
+ required(full.includes('R206_STAGE_PASS=full'),'full protected gate incomplete');
66
+ const summary=tapSummary(full,'full');
67
+ required(summary.tests>=2200&&summary.passes>=2100&&summary.fails===0&&summary.cancelled===0&&summary.skips<=70&&summary.passes+summary.skips===summary.tests,'full regression count mismatch');
68
+ required(verify.includes('VERIFY-ONLY PIPELINE PASS')&&verify.includes('Artifact isolation gate PASS')&&verify.includes('VERIFIED_TARBALL '),'npm exact-artifact verification incomplete');
69
+ const line=verify.split(/\r?\n/).find(value=>value.startsWith('VERIFIED_TARBALL '));
70
+ let artifact;try{artifact=JSON.parse(line.slice('VERIFIED_TARBALL '.length));}catch{fail('tarball evidence malformed');}
71
+ required(artifact.filename==='deadbyte-mcp-0.15.75.tgz'&&artifact.bytes>0&&artifact.sha256===closure.tarball_sha256&&
72
+ artifact.integrity===closure.tarball_integrity&&artifact.shasum===closure.tarball_shasum,'tarball identity mismatch');
73
+ return Object.freeze({status:'R206_RELEASE_READY_EVIDENCE_BOUND',manifest_sha256:closure.manifest_sha256,
74
+ tarball_sha256:artifact.sha256,tarball_integrity:artifact.integrity,tarball_shasum:artifact.shasum,
75
+ tests:summary.tests,passes:summary.passes,skips:summary.skips});
76
+ }
@@ -0,0 +1,76 @@
1
+ import {createHash} from 'node:crypto';
2
+ import {lstat,readFile} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
6
+ const hex64=value=>typeof value==='string'&&/^[0-9a-f]{64}$/.test(value);
7
+ const fail=message=>{throw new Error(`PUBLISH_BLOCKED R207 ${message}`);};
8
+ const required=(value,message)=>{if(!value)fail(message);};
9
+
10
+ async function checkedFile(file,expected,{maxBytes=64*1024*1024}={}){
11
+ required(hex64(expected),'evidence digest missing');
12
+ let info;try{info=await lstat(file);}catch{fail('evidence file absent');}
13
+ required(info.isFile()&&!info.isSymbolicLink()&&info.size>0&&info.size<=maxBytes,'evidence file invalid');
14
+ const bytes=await readFile(file);
15
+ required(sha256(bytes)===expected,'evidence digest differs');
16
+ return bytes;
17
+ }
18
+
19
+ function tapSummary(text,label){
20
+ const summaries=[...text.matchAll(/^# tests (\d+)\s*\r?\n# suites (\d+)\s*\r?\n# pass (\d+)\s*\r?\n# fail (\d+)\s*\r?\n# cancelled (\d+)\s*\r?\n# skipped (\d+)/gm)];
21
+ required(summaries.length>0,`${label} TAP summary absent`);
22
+ const last=summaries.at(-1);
23
+ return {tests:Number(last[1]),passes:Number(last[3]),fails:Number(last[4]),cancelled:Number(last[5]),skips:Number(last[6])};
24
+ }
25
+
26
+ export function assertR207PackedIdentity({tarball_sha256,tarball_integrity,tarball_shasum,observedSha256,observedIntegrity,observedShasum}={}){
27
+ required(hex64(tarball_sha256)&&typeof tarball_integrity==='string'&&tarball_integrity.startsWith('sha512-')&&
28
+ typeof tarball_shasum==='string'&&/^[0-9a-f]{40}$/.test(tarball_shasum),'verified tarball identity invalid');
29
+ required(observedSha256===tarball_sha256&&observedIntegrity===tarball_integrity&&observedShasum===tarball_shasum,
30
+ 'repacked tarball differs from verified closure bytes');
31
+ return true;
32
+ }
33
+
34
+ export async function assertR207NpmPublishReady(release,{root,closureRoot=process.env.DEADBYTE_R207_CLOSURE_ROOT}={}){
35
+ required(release?.version==='0.15.76'&&release?.train==='R207'&&
36
+ release?.r207?.scope==='PROTECTED_OWNER_ENROLLMENT_IO_RECOVERY','candidate metadata invalid');
37
+ required(release?.r207?.public_tool_delta===0&&release?.r207?.authority_scope_delta===0&&
38
+ release?.r207?.owner_private_key_access===false&&release?.r207?.r70_weakened===false&&release?.r207?.r89_weakened===false,'public authority surface widened');
39
+ required(release?.source_predecessor?.train==='R206'&&
40
+ release?.source_predecessor?.manifest_sha256==='9039fff265696091f9217ba4f19526c6c74a967f9ff48b75ac6e340b5f431248',
41
+ 'source predecessor mismatch');
42
+ required(release?.parent?.version==='0.15.75'&&release?.parent?.release_id==='v0.15.75-9039fff265696091'&&
43
+ release?.parent?.manifest_sha256==='9039fff265696091f9217ba4f19526c6c74a967f9ff48b75ac6e340b5f431248'&&
44
+ release?.parent?.generation_manifest_sha256==='83a212e01173c73f78cff2a6d26e6f3f50f476a8c0ea3cbbf592a466071909e9','lineage mismatch');
45
+ required(root&&path.isAbsolute(root)&&closureRoot&&path.isAbsolute(closureRoot),'closure root absent');
46
+ const expected=path.resolve(root,'..','..','testing','r207-final-closure');
47
+ required(path.resolve(closureRoot).toLowerCase()===expected.toLowerCase(),'closure root outside scoped testing folder');
48
+ let closure;try{closure=JSON.parse(await readFile(path.join(closureRoot,'closure.json'),'utf8'));}catch{fail('closure unreadable');}
49
+ required(closure?.schema==='deadbyte.r207-release-closure.v1'&&closure.status==='RELEASE_READY'&&
50
+ closure.version==='0.15.76'&&closure.train==='R207','closure status mismatch');
51
+ required(closure.production_release_id===release.parent.release_id&&closure.production_manifest_sha256===release.parent.manifest_sha256&&
52
+ closure.production_generation_manifest_sha256===release.parent.generation_manifest_sha256,'production predecessor mismatch');
53
+ required(hex64(closure.manifest_sha256)&&closure.manifest_sha256===sha256(await readFile(path.join(root,'MANIFEST.SHA256'))),'final manifest drift');
54
+ required(closure.compact_tools===43&&closure.full_tools===117,'catalog count mismatch');
55
+ required(closure.owner_enrollment==='NOT_RUN'&&closure.production_activation==='NOT_RUN','prepublication closure crossed authority boundary');
56
+ const receipt=JSON.parse((await checkedFile(path.join(closureRoot,'protected-install-receipt.json'),closure.protected_receipt_sha256)).toString('utf8'));
57
+ required(receipt?.status==='PASS'&&receipt.package_version==='0.15.76'&&receipt.source_manifest_sha256===closure.manifest_sha256&&
58
+ receipt.protected_manifest_sha256===closure.manifest_sha256,'protected exact-byte receipt mismatch');
59
+ const focused=(await checkedFile(path.join(closureRoot,'windows-focused.stdout.log'),closure.focused_stdout_sha256)).toString('utf8');
60
+ const full=(await checkedFile(path.join(closureRoot,'windows-full.stdout.log'),closure.full_stdout_sha256)).toString('utf8');
61
+ const verify=(await checkedFile(path.join(closureRoot,'npm-verify.stdout.log'),closure.verify_stdout_sha256)).toString('utf8');
62
+ required(focused.includes('R207_STAGE_PASS=focused'),'focused protected gate incomplete');
63
+ const focusedSummary=tapSummary(focused,'focused');
64
+ required(focusedSummary.tests>=55&&focusedSummary.fails===0&&focusedSummary.cancelled===0&&focusedSummary.passes>=54&&focusedSummary.skips<=1&&focusedSummary.passes+focusedSummary.skips===focusedSummary.tests,'focused regression count mismatch');
65
+ required(full.includes('R207_STAGE_PASS=full'),'full protected gate incomplete');
66
+ const summary=tapSummary(full,'full');
67
+ required(summary.tests>=2200&&summary.passes>=2100&&summary.fails===0&&summary.cancelled===0&&summary.skips<=70&&summary.passes+summary.skips===summary.tests,'full regression count mismatch');
68
+ required(verify.includes('VERIFY-ONLY PIPELINE PASS')&&verify.includes('Artifact isolation gate PASS')&&verify.includes('VERIFIED_TARBALL '),'npm exact-artifact verification incomplete');
69
+ const line=verify.split(/\r?\n/).find(value=>value.startsWith('VERIFIED_TARBALL '));
70
+ let artifact;try{artifact=JSON.parse(line.slice('VERIFIED_TARBALL '.length));}catch{fail('tarball evidence malformed');}
71
+ required(artifact.filename==='deadbyte-mcp-0.15.76.tgz'&&artifact.bytes>0&&artifact.sha256===closure.tarball_sha256&&
72
+ artifact.integrity===closure.tarball_integrity&&artifact.shasum===closure.tarball_shasum,'tarball identity mismatch');
73
+ return Object.freeze({status:'R207_RELEASE_READY_EVIDENCE_BOUND',manifest_sha256:closure.manifest_sha256,
74
+ tarball_sha256:artifact.sha256,tarball_integrity:artifact.integrity,tarball_shasum:artifact.shasum,
75
+ tests:summary.tests,passes:summary.passes,skips:summary.skips});
76
+ }
@@ -1,7 +1,5 @@
1
1
  // Protected, fixed-scope request generation after Limited migration.
2
2
  import {createHash} from 'node:crypto';
3
- import {execFile} from 'node:child_process';
4
- import {promisify} from 'node:util';
5
3
  import {lstat,open,readFile} from 'node:fs/promises';
6
4
  import path from 'node:path';
7
5
  import {fileURLToPath} from 'node:url';
@@ -10,6 +8,10 @@ import {inspectTrustedInstallation} from '../src/privileged-install-trust-r89.mj
10
8
  import {parseManifest,verifyManifestTreeExact} from '../src/release-slot.mjs';
11
9
  import {createR188OwnerUacRequest} from '../src/r188-owner-uac.mjs';
12
10
  import {createR193LiveMachineEpoch} from '../src/r193-live-owner-subject.mjs';
11
+ import {observeCanonicalController} from '../src/r195-canonical-owner-observation.mjs';
12
+ import {ensureOwnerConsentDirectory} from '../src/r207-owner-enrollment-io.mjs';
13
+ import {execFile} from 'node:child_process';
14
+ import {promisify} from 'node:util';
13
15
  const execFileAsync=promisify(execFile);
14
16
  const THIS=fileURLToPath(import.meta.url),ROOT=path.resolve(path.dirname(THIS),'..');
15
17
  const STATE='C:\\Users\\DEADBYTE\\.deadbyte-mcp',RUNTIME=path.win32.join(STATE,'runtime');
@@ -21,6 +23,7 @@ async function main(){
21
23
  const manifestPath=path.join(ROOT,'MANIFEST.SHA256');
22
24
  await inspectTrustedInstallation(['C:\\Program Files\\nodejs\\node.exe',THIS,manifestPath,
23
25
  path.join(ROOT,'src','r188-owner-uac.mjs'),path.join(ROOT,'src','r193-live-owner-subject.mjs'),
26
+ path.join(ROOT,'src','r195-canonical-owner-observation.mjs'),
24
27
  path.join(ROOT,'src','privileged-install-trust-r89.mjs')]);
25
28
  const manifestBytes=await bounded(manifestPath,1024*1024);
26
29
  await verifyManifestTreeExact(ROOT,parseManifest(manifestBytes.toString('utf8')));
@@ -32,16 +35,14 @@ async function main(){
32
35
  if(!coding.roots?.core?.path||!Array.isArray(autonomous.root_ids)||autonomous.root_ids.length!==1||autonomous.root_ids[0]!=='core')
33
36
  throw Error('R193 request policy root is not the bounded core project');
34
37
  const env={...process.env,DEADBYTE_CONTROLLER_CONFIG:pointer.config_path,DEADBYTE_CONTROLLER_RUNTIME_ROOT:RUNTIME};
35
- const controller=path.join(ROOT,'controller','deadbyte-controller.ps1');
36
- const {stdout}=await execFileAsync(PS,['-NoLogo','-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-File',controller,'status','-Json'],
37
- {env,windowsHide:true,timeout:20000,maxBuffer:1024*1024,encoding:'utf8'});
38
- const observation=JSON.parse(stdout);
38
+ const observation=await observeCanonicalController({stateRoot:STATE,powershell:PS,env,execFileAsync});
39
39
  if(observation.state!=='ONLINE'||!observation.supervisor?.running)throw Error('R193 request requires ONLINE Limited runtime');
40
40
  const machineEpoch=createR193LiveMachineEpoch({activePointer:pointer,controllerObservation:observation});
41
41
  const request=createR188OwnerUacRequest({machineEpoch,releaseManifestSha256:sha(manifestBytes),
42
42
  codingPolicySha256:sha(codingBytes),autonomousPolicySha256:sha(autonomousBytes),
43
43
  rootId:'core',projectRoot:coding.roots.core.path,maxSeconds:600,now:new Date(),expiresInMs:300000});
44
- const out=path.win32.join(RUNTIME,'owner-consent','r193-request-'+request.request_sha256+'.json');
44
+ const consentDir=await ensureOwnerConsentDirectory(RUNTIME);
45
+ const out=path.win32.join(consentDir,'r193-request-'+request.request_sha256+'.json');
45
46
  const handle=await open(out,'wx',0o600);try{await handle.writeFile(canonicalJson(request)+'\n');await handle.sync();}finally{await handle.close();}
46
47
  process.stdout.write(JSON.stringify({status:'R193_OWNER_REQUEST_CREATED',request_path:out,
47
48
  request_sha256:request.request_sha256,review_phrase:request.review_phrase,
@@ -15,6 +15,8 @@ import {buildR193LiveOwnerBoundary} from '../src/r193-live-owner-subject.mjs';
15
15
  import {approveR193OwnerRequest} from '../src/r193-owner-uac-broker.mjs';
16
16
  import {createR186OwnerAnchor} from '../src/r186-owner-anchor.mjs';
17
17
  import {inspectR193OwnerSecretAcl} from '../src/r193-owner-secret-acl.mjs';
18
+ import {observeCanonicalController} from '../src/r195-canonical-owner-observation.mjs';
19
+ import {parseOwnerEnrollmentJson} from '../src/r207-owner-enrollment-io.mjs';
18
20
 
19
21
  const execFileAsync=promisify(execFile);
20
22
  const THIS=fileURLToPath(import.meta.url),ROOT=path.resolve(path.dirname(THIS),'..');
@@ -61,6 +63,7 @@ async function live(request){
61
63
  const protectedFiles=[NODE,THIS,manifestPath,NATIVE,
62
64
  path.join(ROOT,'src','r193-live-owner-subject.mjs'),
63
65
  path.join(ROOT,'src','r193-owner-uac-broker.mjs'),
66
+ path.join(ROOT,'src','r195-canonical-owner-observation.mjs'),
64
67
  path.join(ROOT,'src','r193-uac-r70-bridge.mjs'),
65
68
  path.join(ROOT,'src','r193-owner-secret-acl.mjs'),
66
69
  path.join(ROOT,'src','r188-owner-uac.mjs'),
@@ -77,12 +80,10 @@ async function live(request){
77
80
  const codingPath=path.win32.join(generation,'coding-policy.json');
78
81
  const autonomousPath=path.win32.join(generation,'autonomous-policy.json');
79
82
  const configPath=pointer.config_path;
80
- const controller=path.join(ROOT,'controller','deadbyte-controller.ps1');
81
83
  const env={...process.env,DEADBYTE_CONTROLLER_CONFIG:configPath,
82
84
  DEADBYTE_CONTROLLER_RUNTIME_ROOT:RUNTIME};
83
- const observation=await jsonExec(PS,['-NoLogo','-NoProfile','-NonInteractive',
84
- '-ExecutionPolicy','Bypass','-File',controller,'status','-Json'],{env});
85
- const supervisor=JSON.parse((await bounded(path.win32.join(RUNTIME,'supervisor.instance.json'),64*1024)).toString('utf8'));
85
+ const observation=await observeCanonicalController({stateRoot:STATE,powershell:PS,env,execFileAsync});
86
+ const supervisor=parseOwnerEnrollmentJson(await bounded(path.win32.join(RUNTIME,'supervisor.instance.json'),64*1024),'supervisor ledger');
86
87
  const sources={bridge:observation.bridge,core:observation.core,cloud:observation.cloud,supervisor};
87
88
  const processReports={};
88
89
  for(const component of ['bridge','core','cloud','supervisor'])
@@ -101,7 +102,7 @@ async function main(){
101
102
  if(!['preflight','approve'].includes(mode)||process.argv.slice(2).length!==2||
102
103
  !path.win32.isAbsolute(requestPath))block('usage: <preflight|approve> <absolute-request-json>');
103
104
  await inspectTrustedInstallation([NODE,THIS,path.join(ROOT,'MANIFEST.SHA256'),NATIVE]);
104
- const request=JSON.parse((await bounded(requestPath,64*1024)).toString('utf8'));
105
+ const request=parseOwnerEnrollmentJson(await bounded(requestPath,64*1024),'owner request');
105
106
  const state=await live(request);
106
107
  if(mode==='preflight'){
107
108
  process.stdout.write(JSON.stringify({status:'READY_FOR_OWNER_GESTURE',
@@ -0,0 +1,22 @@
1
+ // Read-only bounded stdin candidate comparison. Does not promote, publish, or grant authority.
2
+ import { evaluateCandidateSelectionR205 } from '../src/candidate-selection-r205.mjs';
3
+
4
+ const MAX_BYTES=64*1024;
5
+ let length=0;
6
+ const parts=[];
7
+ try{
8
+ for await (const chunk of process.stdin){
9
+ length+=chunk.length;
10
+ if(length>MAX_BYTES)throw Error('R205_INPUT_LIMIT_EXCEEDED');
11
+ parts.push(chunk);
12
+ }
13
+ if(length<1)throw Error('R205_INPUT_EMPTY');
14
+ const raw=Buffer.concat(parts).toString('utf8');
15
+ const parsed=JSON.parse(raw);
16
+ const result=evaluateCandidateSelectionR205(parsed);
17
+ process.stdout.write(JSON.stringify(result)+'\n');
18
+ if(result.state!=='PROVISIONAL_CANDIDATE')process.exitCode=2;
19
+ }catch(error){
20
+ process.stderr.write('R205_SELECTION_REFUSED '+String(error?.message??error)+'\n');
21
+ process.exitCode=3;
22
+ }
@@ -0,0 +1,71 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonicalJson } from './canonical-json.mjs';
3
+ import { evaluateCandidateSelectionR205 } from './candidate-selection-r205.mjs';
4
+
5
+ const SHA=/^[0-9a-f]{64}$/;
6
+ const sha=value=>createHash('sha256').update(Buffer.from(canonicalJson(value),'utf8')).digest('hex');
7
+ const insist=(condition,message)=>{if(!condition)throw new Error(message);};
8
+
9
+ function receiptExpectations(input){
10
+ const expectations=[];
11
+ const addCorrectness=(label,artifact,value)=>expectations.push({
12
+ label,kind:'correctness',receipt_sha256:value.receipt_sha256,artifact_sha256:artifact,
13
+ fixture_sha256:input.fixture_sha256,status:value.status
14
+ });
15
+ const addBenchmark=(label,artifact,value)=>expectations.push({
16
+ label,kind:'benchmark',receipt_sha256:value.receipt_sha256,artifact_sha256:artifact,
17
+ fixture_sha256:input.fixture_sha256,environment_sha256:input.environment_sha256,
18
+ samples_sha256:sha(value.samples_ms)
19
+ });
20
+ addCorrectness('baseline correctness',input.baseline.artifact_sha256,input.baseline.correctness);
21
+ addBenchmark('baseline benchmark',input.baseline.artifact_sha256,input.baseline.benchmark);
22
+ for(const candidate of input.candidates){
23
+ addCorrectness(`candidate ${candidate.candidate_id} correctness`,candidate.artifact_sha256,candidate.correctness);
24
+ if(candidate.benchmark)addBenchmark(`candidate ${candidate.candidate_id} benchmark`,candidate.artifact_sha256,candidate.benchmark);
25
+ }
26
+ return expectations;
27
+ }
28
+
29
+ function verifyFact(expected,actual){
30
+ insist(actual&&typeof actual==='object'&&!Array.isArray(actual),`${expected.label} receipt verification failed`);
31
+ insist(actual.verified===true&&actual.receipt_sha256===expected.receipt_sha256,
32
+ `${expected.label} receipt verification failed`);
33
+ insist(SHA.test(actual.receipt_sha256),`${expected.label} receipt verification failed`);
34
+ if(expected.kind==='correctness'){
35
+ insist(actual.evidence_kind==='correctness'&&actual.signer_role==='independent_correctness_oracle'&&
36
+ actual.artifact_sha256===expected.artifact_sha256&&actual.fixture_sha256===expected.fixture_sha256&&
37
+ actual.status===expected.status,`${expected.label} receipt binding mismatch`);
38
+ }else{
39
+ insist(actual.evidence_kind==='benchmark'&&actual.signer_role==='independent_benchmark_runner'&&
40
+ actual.artifact_sha256===expected.artifact_sha256&&actual.fixture_sha256===expected.fixture_sha256&&
41
+ actual.environment_sha256===expected.environment_sha256&&actual.samples_sha256===expected.samples_sha256,
42
+ `${expected.label} receipt binding mismatch`);
43
+ }
44
+ return Object.freeze({receipt_sha256:actual.receipt_sha256,evidence_kind:actual.evidence_kind,
45
+ signer_role:actual.signer_role,artifact_sha256:actual.artifact_sha256,fixture_sha256:actual.fixture_sha256,
46
+ ...(expected.kind==='correctness'?{status:actual.status}:{environment_sha256:actual.environment_sha256,
47
+ samples_sha256:actual.samples_sha256})});
48
+ }
49
+
50
+ export async function verifyCandidateSelectionEvidenceR206(input,{verifyReceipt}={}){
51
+ insist(typeof verifyReceipt==='function','independent receipt verifier required');
52
+ const provisional=evaluateCandidateSelectionR205(input);
53
+ const expectations=receiptExpectations(input);
54
+ const seen=new Set();
55
+ const verified=[];
56
+ for(const expected of expectations){
57
+ insist(SHA.test(expected.receipt_sha256),`${expected.label} receipt hash invalid`);
58
+ insist(!seen.has(expected.receipt_sha256),`${expected.label} receipt reused`);
59
+ seen.add(expected.receipt_sha256);
60
+ let actual;
61
+ try{actual=await verifyReceipt(expected.receipt_sha256,Object.freeze({...expected}));}
62
+ catch(error){throw new Error(`${expected.label} receipt verification failed: ${error?.message??error}`);}
63
+ verified.push(verifyFact(expected,actual));
64
+ }
65
+ const binding={schema:'deadbyte.candidate-selection-evidence-binding.r206.v1',
66
+ selection_report_sha256:provisional.report_sha256,verified_receipts:verified};
67
+ return Object.freeze({...provisional,schema:'deadbyte.candidate-selection-evidence-verdict.r206.v1',
68
+ evidence_verified:true,verified_receipt_count:verified.length,release_authorized:false,
69
+ reason:'Independent decision-bearing receipts verified. Candidate selection remains advisory and cannot authorize release.',
70
+ evidence_binding_sha256:sha(binding)});
71
+ }
@@ -0,0 +1,96 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonicalJson } from './canonical-json.mjs';
3
+
4
+ export const CANDIDATE_SELECTION_SCHEMA='deadbyte.candidate-selection.r205.v1';
5
+ const H=/^[0-9a-f]{64}$/;
6
+ const ID=/^[A-Za-z0-9._-]{1,96}$/;
7
+ const STATES=new Set(['PASS','FAIL','UNKNOWN','INVALID','UNTESTED']);
8
+ const digest=value=>createHash('sha256').update(Buffer.from(canonicalJson(value),'utf8')).digest('hex');
9
+
10
+ function object(value,label,allowed){
11
+ if(!value||typeof value!=='object'||Array.isArray(value))throw Error(label+' invalid');
12
+ const extra=Object.keys(value).filter(k=>!allowed.includes(k));
13
+ if(extra.length)throw Error(label+' unexpected field '+extra[0]);
14
+ return value;
15
+ }
16
+ function hash(value,label){if(typeof value!=='string'||!H.test(value))throw Error(label+' invalid');return value;}
17
+ function sample(value,label){
18
+ if(!Array.isArray(value)||value.length<7||value.length>31||value.some(x=>typeof x!=='number'||!Number.isFinite(x)||x<=0||x>3_600_000))throw Error(label+' invalid');
19
+ return [...value];
20
+ }
21
+ const median=xs=>{const a=[...xs].sort((x,y)=>x-y);return a[Math.floor(a.length/2)];};
22
+ const mad=xs=>median(xs.map(x=>Math.abs(x-median(xs))));
23
+ function correctness(raw,label,artifact,fixture){
24
+ const v=object(raw,label,['status','artifact_sha256','fixture_sha256','receipt_sha256']);
25
+ if(!STATES.has(v.status))throw Error(label+' status invalid');
26
+ if(hash(v.artifact_sha256,label+' artifact')!==artifact||hash(v.fixture_sha256,label+' fixture')!==fixture)throw Error(label+' evidence identity mismatch');
27
+ hash(v.receipt_sha256,label+' receipt');
28
+ return Object.freeze({...v});
29
+ }
30
+ function benchmark(raw,label,artifact,fixture,environment){
31
+ const v=object(raw,label,['artifact_sha256','fixture_sha256','environment_sha256','receipt_sha256','samples_ms']);
32
+ if(hash(v.artifact_sha256,label+' artifact')!==artifact||hash(v.fixture_sha256,label+' fixture')!==fixture||hash(v.environment_sha256,label+' environment')!==environment)throw Error(label+' benchmark identity mismatch');
33
+ hash(v.receipt_sha256,label+' receipt');
34
+ return Object.freeze({...v,samples_ms:sample(v.samples_ms,label+' samples')});
35
+ }
36
+ function normalize(raw){
37
+ const v=object(raw,'candidate selection',['schema','fixture_sha256','environment_sha256','baseline','candidates']);
38
+ if(v.schema!==CANDIDATE_SELECTION_SCHEMA)throw Error('candidate selection schema invalid');
39
+ const fixture=hash(v.fixture_sha256,'fixture'),env=hash(v.environment_sha256,'environment');
40
+ const b=object(v.baseline,'baseline',['artifact_sha256','correctness','benchmark']);
41
+ const baseHash=hash(b.artifact_sha256,'baseline artifact');
42
+ const baseCorrect=correctness(b.correctness,'baseline correctness',baseHash,fixture);
43
+ if(baseCorrect.status!=='PASS')throw Error('baseline correctness not PASS');
44
+ const baseBench=benchmark(b.benchmark,'baseline benchmark',baseHash,fixture,env);
45
+ if(!Array.isArray(v.candidates)||v.candidates.length<1||v.candidates.length>8)throw Error('candidate count invalid');
46
+ const seen=new Set([baseHash]),ids=new Set();
47
+ const candidates=v.candidates.map((item,i)=>{
48
+ const c=object(item,'candidate '+i,['candidate_id','artifact_sha256','correctness','benchmark']);
49
+ if(typeof c.candidate_id!=='string'||!ID.test(c.candidate_id)||ids.has(c.candidate_id))throw Error('candidate id invalid or repeated');
50
+ ids.add(c.candidate_id);
51
+ const h=hash(c.artifact_sha256,'candidate artifact');
52
+ if(seen.has(h))throw Error('candidate artifact duplicated');seen.add(h);
53
+ const result=correctness(c.correctness,'candidate correctness',h,fixture);
54
+ const bench=c.benchmark===undefined?null:benchmark(c.benchmark,'candidate benchmark',h,fixture,env);
55
+ if(result.status==='PASS'&&!bench)throw Error('PASS candidate benchmark missing');
56
+ if(bench&&bench.samples_ms.length!==baseBench.samples_ms.length)throw Error('paired sample length mismatch');
57
+ return {candidate_id:c.candidate_id,artifact_sha256:h,correctness:result,benchmark:bench};
58
+ });
59
+ return {fixture,environment:env,baseline:{artifact_sha256:baseHash,correctness:baseCorrect,benchmark:baseBench},candidates};
60
+ }
61
+ export function buildBlindCandidateViewR205(input){
62
+ const v=normalize(input);
63
+ return Object.freeze({schema:CANDIDATE_SELECTION_SCHEMA,fixture_sha256:v.fixture,environment_sha256:v.environment,
64
+ baseline:{artifact_sha256:v.baseline.artifact_sha256,correctness:v.baseline.correctness,benchmark:v.baseline.benchmark},
65
+ candidates:v.candidates.map(c=>({artifact_sha256:c.artifact_sha256,correctness:c.correctness,benchmark:c.benchmark}))
66
+ .sort((a,b)=>a.artifact_sha256.localeCompare(b.artifact_sha256))});
67
+ }
68
+ export function evaluateCandidateSelectionR205(input,{minimum_gain_pct=5,minimum_win_rate=0.8,max_mad_pct=8,ambiguity_margin_pct=3}={}){
69
+ for(const [name,v,max] of [['minimum_gain_pct',minimum_gain_pct,100],['minimum_win_rate',minimum_win_rate,1],['max_mad_pct',max_mad_pct,100],['ambiguity_margin_pct',ambiguity_margin_pct,100]]){
70
+ if(typeof v!=='number'||!Number.isFinite(v)||v<0||v>max)throw Error(name+' invalid');
71
+ }
72
+ const parsed=normalize(input);
73
+ const baseline=parsed.baseline.benchmark.samples_ms;
74
+ const classified=parsed.candidates.map(c=>{
75
+ if(c.correctness.status!=='PASS')return {candidate_id:c.candidate_id,artifact_sha256:c.artifact_sha256,verdict:c.correctness.status==='FAIL'?'REJECT_CORRECTNESS':'UNGRADED',correctness_status:c.correctness.status};
76
+ const changes=c.benchmark.samples_ms.map((time,i)=>100*(baseline[i]-time)/baseline[i]);
77
+ const gain=median(changes),deviation=mad(changes),wins=changes.filter(x=>x>0).length;
78
+ const winRate=wins/changes.length;
79
+ const eligible=gain>=minimum_gain_pct&&winRate>=minimum_win_rate&&deviation<=max_mad_pct;
80
+ return {candidate_id:c.candidate_id,artifact_sha256:c.artifact_sha256,verdict:eligible?'PERFORMANCE_QUALIFIED':'REJECT_PERFORMANCE',
81
+ correctness_status:'PASS',median_gain_pct:Number(gain.toFixed(4)),mad_pct:Number(deviation.toFixed(4)),
82
+ win_rate:Number(winRate.toFixed(4)),sample_count:changes.length};
83
+ });
84
+ const ranked=classified.filter(x=>x.verdict==='PERFORMANCE_QUALIFIED').sort((a,b)=>b.median_gain_pct-a.median_gain_pct||a.artifact_sha256.localeCompare(b.artifact_sha256));
85
+ const ambiguous=ranked.length>1&&ranked[0].median_gain_pct-ranked[1].median_gain_pct<ambiguity_margin_pct;
86
+ const winner=ranked.length&&!ambiguous?ranked[0]:null;
87
+ const report={schema:'deadbyte.candidate-selection-verdict.r205.v1',
88
+ state:ambiguous?'AMBIGUOUS':winner?'PROVISIONAL_CANDIDATE':'NO_QUALIFIED_CANDIDATE',
89
+ winner_candidate_id:winner?.candidate_id??null,winner_artifact_sha256:winner?.artifact_sha256??null,
90
+ evidence_verified:false,release_authorized:false,
91
+ reason:'Claims and receipt hashes are syntax/identity checked only. Signed receipts, independent oracle and benchmark runner must be verified externally before promotion.',
92
+ fixture_sha256:parsed.fixture,environment_sha256:parsed.environment,baseline_artifact_sha256:parsed.baseline.artifact_sha256,
93
+ candidates:classified.sort((a,b)=>a.artifact_sha256.localeCompare(b.artifact_sha256)),
94
+ blind_evaluation_sha256:digest(buildBlindCandidateViewR205(input))};
95
+ return Object.freeze({...report,report_sha256:digest(report)});
96
+ }
@@ -0,0 +1,30 @@
1
+ const SHA256=/^[0-9a-f]{64}$/;
2
+
3
+ export function validateDesktopPostconditionExpectation(expectation){
4
+ if(expectation===null)return null;
5
+ if(!expectation||typeof expectation!=='object'||Array.isArray(expectation)||
6
+ Object.keys(expectation).sort().join(',')!=='expected_sha256,kind'||
7
+ expectation.kind!=='frame_image_sha256'||!SHA256.test(expectation.expected_sha256??''))
8
+ throw new Error('R196 unsupported desktop postcondition expectation');
9
+ return Object.freeze({kind:expectation.kind,expected_sha256:expectation.expected_sha256});
10
+ }
11
+
12
+ // An input injection receipt is not evidence that the application accepted it.
13
+ // This verifier only establishes the explicit observable postcondition.
14
+ export function evaluateDesktopPostcondition({expectation=null,beforeFrame,afterFrame}={}){
15
+ if(!beforeFrame||!afterFrame||!SHA256.test(beforeFrame.image_sha256??'')||
16
+ !SHA256.test(afterFrame.image_sha256??'')||!SHA256.test(beforeFrame.frame_id??'')||
17
+ !SHA256.test(afterFrame.frame_id??''))throw new Error('R196 desktop frames invalid');
18
+ const base={schema:'deadbyte.desktop-postcondition.v1',
19
+ before_frame_id:beforeFrame.frame_id,after_frame_id:afterFrame.frame_id,
20
+ before_image_sha256:beforeFrame.image_sha256,after_image_sha256:afterFrame.image_sha256};
21
+ if(expectation===null)return Object.freeze({...base,status:'unknown',reason:'no_explicit_expectation',kind:null,expected_sha256:null});
22
+ const verified=validateDesktopPostconditionExpectation(expectation);
23
+ const expected=verified.expected_sha256;
24
+ if(beforeFrame.image_sha256===expected)
25
+ return Object.freeze({...base,status:'unknown',reason:'expected_state_already_present_before_action',
26
+ kind:expectation.kind,expected_sha256:expected});
27
+ return Object.freeze({...base,status:afterFrame.image_sha256===expected?'satisfied':'unsatisfied',
28
+ reason:afterFrame.image_sha256===expected?'exact_post_image_observed':'post_image_differs_from_expected',
29
+ kind:expectation.kind,expected_sha256:expected});
30
+ }
@@ -4,6 +4,8 @@ import path from 'node:path';
4
4
  import { canonicalJson } from './canonical-json.mjs';
5
5
  import { createDesktopAction,createDesktopTarget,guardDesktopAction,bindDesktopPostObservation,bindDesktopActionChain } from './desktop-contract.mjs';
6
6
  import { effectIdentity,bindEffectCompletion } from './effect-ledger.mjs';
7
+ import { evaluateDesktopPostcondition,validateDesktopPostconditionExpectation } from './desktop-postcondition-r196.mjs';
8
+ import { bindUiaElementR199,assertFreshUiaElementR199 } from './desktop-uia-identity-r199.mjs';
7
9
  import { DESKTOP_ACTION_KINDS,DESKTOP_POLICY_SCHEMA,assertDesktopArmed,desktopSessionStatus } from './desktop-policy-r40.mjs';
8
10
 
9
11
  export const DESKTOP_RECEIPT_SCHEMA='deadbyte.desktop-receipt.v1';
@@ -82,6 +84,8 @@ export function createDesktopRuntime({
82
84
  insist(Number.isInteger(maxTargets)&&maxTargets>=1&&maxTargets<=1024,'maxTargets invalid');
83
85
  const receiptPublicKey=publicKey??createPublicKey(privateKey);
84
86
  const targets=new Map();
87
+ const groundedTargets=new Set();
88
+ const uiaBindings=new Map();
85
89
 
86
90
  async function receipt(kind,payload,stamp=now()){
87
91
  return writeReceipt({policy,privateKey,keyId,evidenceDir,kind,payload,nowUtc:stamp});
@@ -193,15 +197,29 @@ export function createDesktopRuntime({
193
197
  const confidence=grounded.query_kind==='name_contains'?0.98:1;
194
198
  const created=createDesktopTarget({
195
199
  frame:fresh.frame,rect:candidate.rect,source:'uia',confidence,
196
- semanticLabelSha256:grounded.query_sha256,windowBinding:fresh.frame.active_window
200
+ // Hash the ephemeral native identity into the target handle to prevent
201
+ // old and new elements at identical coordinates sharing one target_id.
202
+ semanticLabelSha256:Array.isArray(candidate.runtime_id)&&candidate.runtime_id.length>0
203
+ ?sha256Object({query_sha256:grounded.query_sha256,runtime_id:candidate.runtime_id})
204
+ :grounded.query_sha256,
205
+ windowBinding:fresh.frame.active_window
197
206
  });
198
207
  targets.set(created.target_id,created);
199
- while(targets.size>maxTargets)targets.delete(targets.keys().next().value);
208
+ groundedTargets.add(created.target_id);
209
+ if(candidate.runtime_id!==null&&candidate.runtime_id!==undefined){
210
+ const binding=bindUiaElementR199({frame:fresh.frame,grounded,candidate,query});
211
+ uiaBindings.set(created.target_id,binding);
212
+ }
213
+ while(targets.size>maxTargets){
214
+ const removed=targets.keys().next().value;
215
+ targets.delete(removed);groundedTargets.delete(removed);uiaBindings.delete(removed);
216
+ }
200
217
  const evidence=await receipt('desktop_grounding',{
201
218
  requested_frame_id:expected.frame_id,grounded_frame_id:fresh.frame.frame_id,
202
219
  target_id:created.target_id,query_sha256:grounded.query_sha256,query_kind:grounded.query_kind,
203
220
  ordinal:grounded.ordinal,match_count:grounded.matches.length,visited:grounded.visited,truncated:grounded.truncated,
204
- provider_receipt_sha256:grounded.provider_receipt_sha256,control_type:candidate.control_type
221
+ provider_receipt_sha256:grounded.provider_receipt_sha256,control_type:candidate.control_type,
222
+ element_token_sha256:uiaBindings.get(created.target_id)?.token_sha256??null
205
223
  },fresh.frame.observed_at_utc);
206
224
  return Object.freeze({
207
225
  status:'ok',frame:fresh.frame,target:created,
@@ -209,15 +227,18 @@ export function createDesktopRuntime({
209
227
  query_sha256:grounded.query_sha256,query_kind:grounded.query_kind,ordinal:grounded.ordinal,
210
228
  match_count:grounded.matches.length,visited:grounded.visited,truncated:grounded.truncated,
211
229
  control_type:candidate.control_type,provider_receipt_sha256:grounded.provider_receipt_sha256,
230
+ element_token_sha256:uiaBindings.get(created.target_id)?.token_sha256??null,
212
231
  receipt_id:evidence.receipt_id,receipt_sha256:evidence.receipt_sha256
213
232
  })
214
233
  });
215
234
  }
216
235
  async function action({
217
236
  intentId,kind,expectedFrameId,targetId=null,expectedWindow=undefined,point=null,
218
- scrollDelta=null,keyChord=null,text=null,effectContext=null
237
+ scrollDelta=null,keyChord=null,text=null,effectContext=null,expectedPostcondition=null
219
238
  }={}){
220
239
  insist(DESKTOP_ACTION_KINDS.includes(kind),'desktop action kind invalid');
240
+ const postconditionExpectation=validateDesktopPostconditionExpectation(expectedPostcondition);
241
+ const expectedPostconditionSha256=postconditionExpectation?sha256Object(postconditionExpectation):null;
221
242
  const authority=await assertDesktopArmed(policy,kind);
222
243
  if(kind==='text_input'){
223
244
  insist(typeof text==='string','desktop text input invalid');
@@ -243,6 +264,15 @@ export function createDesktopRuntime({
243
264
  // bind it to the same fresh desktop/topology/foreground identity and let the native helper
244
265
  // revalidate HWND/PID immediately before SendInput instead of requiring unrelated pixels to freeze.
245
266
  if(kind!=='key_chord') insist(pre.frame.image_sha256===expected.image_sha256,'desktop pixels changed before action');
267
+ // Before any injection, reacquire the same UIA element through a fresh
268
+ // query bound to the current foreground window and runtime identity.
269
+ if(chosenTarget&&groundedTargets.has(chosenTarget.target_id)){
270
+ const identity=uiaBindings.get(chosenTarget.target_id);
271
+ insist(identity,'R199 UIA target has no RuntimeId binding and is not actionable');
272
+ insist(typeof grounderProvider==='function','R199 UIA re-ground provider unavailable');
273
+ const refreshed=await grounderProvider({frame:pre.frame,query:identity.query});
274
+ assertFreshUiaElementR199(identity,refreshed);
275
+ }
246
276
 
247
277
  let actionPoint=point;
248
278
  if(chosenTarget&&['pointer_move','click_primary','click_secondary','double_click_primary','scroll'].includes(kind)){
@@ -260,7 +290,8 @@ export function createDesktopRuntime({
260
290
  const effectAction=Object.freeze({
261
291
  kind:`desktop_${kind}`,action_sha256:actionObject.action_sha256,frame_id:pre.frame.frame_id,
262
292
  target_id:actionObject.target_id,point:actionObject.point,scroll_delta:actionObject.scroll_delta,
263
- key_chord:actionObject.key_chord,text_sha256:actionObject.text_sha256
293
+ key_chord:actionObject.key_chord,text_sha256:actionObject.text_sha256,
294
+ ...(expectedPostconditionSha256?{expected_postcondition_sha256:expectedPostconditionSha256}:{})
264
295
  });
265
296
  const ctx=effectContext??{};
266
297
  const effectLedger=effectIdentity({
@@ -272,7 +303,8 @@ export function createDesktopRuntime({
272
303
  intent_id:intentId,kind,expected_frame_id:expected.frame_id,pre_frame_id:pre.frame.frame_id,
273
304
  target_id:chosenTarget?.target_id??null,action_sha256:actionObject.action_sha256,
274
305
  guard_sha256:guarded.guard_sha256,authority_instance_nonce:authority.instance_nonce,
275
- effect_id:effectLedger.effect_id,effect_fingerprint:effectLedger.fingerprint
306
+ effect_id:effectLedger.effect_id,effect_fingerprint:effectLedger.fingerprint,
307
+ expected_postcondition_sha256:expectedPostconditionSha256
276
308
  });
277
309
 
278
310
  let effect;
@@ -303,6 +335,9 @@ export function createDesktopRuntime({
303
335
  try{
304
336
  const post=await observerRuntime.capture();
305
337
  const bound=bindDesktopPostObservation({guard:guarded,beforeFrame:pre.frame,action:actionObject,afterFrame:post.frame});
338
+ const postcondition=evaluateDesktopPostcondition({expectation:postconditionExpectation,
339
+ beforeFrame:pre.frame,afterFrame:post.frame});
340
+ const postconditionSha256=sha256Object(postcondition);
306
341
  const postEvidence=await receipt('desktop_post_observation',{
307
342
  started_receipt_id:started.receipt_id,started_receipt_sha256:started.receipt_sha256,
308
343
  effect_id:effectLedger.effect_id,effect_fingerprint:effectLedger.fingerprint,
@@ -329,11 +364,13 @@ export function createDesktopRuntime({
329
364
  action_sha256:actionObject.action_sha256,guard_sha256:guarded.guard_sha256,
330
365
  effect_id:effectLedger.effect_id,effect_fingerprint:effectLedger.fingerprint,effect_completion:effectCompletion,
331
366
  effect:{injected:true,side_effect_state:'confirmed_injected',provider_receipt_sha256:HEX64.test(effect.provider_receipt_sha256??'')?effect.provider_receipt_sha256:null},
332
- action_chain:chain,post_observation:bound
367
+ action_chain:chain,post_observation:bound,
368
+ postcondition,postcondition_sha256:postconditionSha256
333
369
  },post.frame.observed_at_utc);
334
370
  return Object.freeze({
335
371
  status:'ok',injected:true,task_success:null,side_effect_state:'confirmed_injected',
336
372
  action:actionObject,guard:guarded,post_observation:bound,action_chain:chain,
373
+ postcondition,postcondition_sha256:postconditionSha256,
337
374
  effect_id:effectLedger.effect_id,effect_fingerprint:effectLedger.fingerprint,effect_completion:effectCompletion,
338
375
  started_receipt_id:started.receipt_id,started_receipt_sha256:started.receipt_sha256,
339
376
  post_receipt_id:postEvidence.receipt_id,post_receipt_sha256:postEvidence.receipt_sha256,