@eyeauras/insight-cli 0.0.0-stage → 0.1.0
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/.cli-package-build.json +1 -0
- package/LICENSE +21 -0
- package/README.md +54 -2
- package/SKILL.md +55 -0
- package/dist/src/administration-results.d.ts +1 -0
- package/dist/src/administration-results.js +84 -0
- package/dist/src/administration.d.ts +14 -0
- package/dist/src/administration.js +120 -0
- package/dist/src/archive-contract.d.ts +6 -0
- package/dist/src/archive-contract.js +108 -0
- package/dist/src/archive.d.ts +12 -0
- package/dist/src/archive.js +116 -0
- package/dist/src/arguments.d.ts +14 -0
- package/dist/src/arguments.js +62 -0
- package/dist/src/artifacts.d.ts +17 -0
- package/dist/src/artifacts.js +115 -0
- package/dist/src/bin.d.ts +2 -0
- package/dist/src/bin.js +4 -0
- package/dist/src/cli.d.ts +10 -0
- package/dist/src/cli.js +387 -0
- package/dist/src/comment-effects.d.ts +3 -0
- package/dist/src/comment-effects.js +55 -0
- package/dist/src/contexts.d.ts +115 -0
- package/dist/src/contexts.js +1068 -0
- package/dist/src/coordinator.d.ts +41 -0
- package/dist/src/coordinator.js +160 -0
- package/dist/src/cross-results.d.ts +14 -0
- package/dist/src/cross-results.js +71 -0
- package/dist/src/cross.d.ts +3 -0
- package/dist/src/cross.js +43 -0
- package/dist/src/data-effects.d.ts +3 -0
- package/dist/src/data-effects.js +103 -0
- package/dist/src/debugger-results.d.ts +4 -0
- package/dist/src/debugger-results.js +151 -0
- package/dist/src/debugger-supervisor.d.ts +1 -0
- package/dist/src/debugger-supervisor.js +136 -0
- package/dist/src/debugger.d.ts +21 -0
- package/dist/src/debugger.js +267 -0
- package/dist/src/documentation-effects.d.ts +5 -0
- package/dist/src/documentation-effects.js +56 -0
- package/dist/src/extensions.d.ts +64 -0
- package/dist/src/extensions.js +289 -0
- package/dist/src/function-effects.d.ts +4 -0
- package/dist/src/function-effects.js +112 -0
- package/dist/src/ghidra.d.ts +50 -0
- package/dist/src/ghidra.js +468 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/initial-model.d.ts +6 -0
- package/dist/src/initial-model.js +85 -0
- package/dist/src/json.d.ts +2 -0
- package/dist/src/json.js +8 -0
- package/dist/src/lifecycle.d.ts +49 -0
- package/dist/src/lifecycle.js +148 -0
- package/dist/src/memory-inputs.d.ts +8 -0
- package/dist/src/memory-inputs.js +56 -0
- package/dist/src/merge.d.ts +29 -0
- package/dist/src/merge.js +66 -0
- package/dist/src/model.d.ts +74 -0
- package/dist/src/model.js +67 -0
- package/dist/src/mutation-effects.d.ts +4 -0
- package/dist/src/mutation-effects.js +321 -0
- package/dist/src/mutation-inputs.d.ts +2 -0
- package/dist/src/mutation-inputs.js +71 -0
- package/dist/src/mutations.d.ts +4 -0
- package/dist/src/mutations.js +53 -0
- package/dist/src/native-inputs.d.ts +28 -0
- package/dist/src/native-inputs.js +171 -0
- package/dist/src/native-queries.d.ts +2 -0
- package/dist/src/native-queries.js +29 -0
- package/dist/src/projects.d.ts +31 -0
- package/dist/src/projects.js +301 -0
- package/dist/src/prototype-cli.d.ts +8 -0
- package/dist/src/prototype-cli.js +155 -0
- package/dist/src/queries.d.ts +5 -0
- package/dist/src/queries.js +84 -0
- package/dist/src/query-policy.d.ts +6 -0
- package/dist/src/query-policy.js +72 -0
- package/dist/src/query-results.d.ts +2 -0
- package/dist/src/query-results.js +224 -0
- package/dist/src/research.d.ts +184 -0
- package/dist/src/research.js +808 -0
- package/dist/src/saved-materials.d.ts +12 -0
- package/dist/src/saved-materials.js +196 -0
- package/dist/src/scripts.d.ts +19 -0
- package/dist/src/scripts.js +79 -0
- package/dist/src/sdk-contracts.d.ts +13 -0
- package/dist/src/sdk-contracts.js +37 -0
- package/dist/src/sdk-files.d.ts +29 -0
- package/dist/src/sdk-files.js +249 -0
- package/dist/src/sdk.d.ts +4 -0
- package/dist/src/sdk.js +125 -0
- package/dist/src/server.d.ts +44 -0
- package/dist/src/server.js +455 -0
- package/dist/src/store.d.ts +33 -0
- package/dist/src/store.js +178 -0
- package/dist/src/sync-policy.d.ts +22 -0
- package/dist/src/sync-policy.js +123 -0
- package/dist/src/transport.d.ts +23 -0
- package/dist/src/transport.js +135 -0
- package/dist/src/ui-results.d.ts +2 -0
- package/dist/src/ui-results.js +80 -0
- package/dist/src/ui.d.ts +17 -0
- package/dist/src/ui.js +88 -0
- package/dist/src/utility.d.ts +14 -0
- package/dist/src/utility.js +43 -0
- package/dist/src/version-tracking.d.ts +57 -0
- package/dist/src/version-tracking.js +200 -0
- package/dist/src/workspace.d.ts +199 -0
- package/dist/src/workspace.js +564 -0
- package/package.json +45 -4
- package/references/InsightRepositoryProbe.java +55 -0
- package/references/administration.md +33 -0
- package/references/archive.md +32 -0
- package/references/artifacts.md +47 -0
- package/references/debugger.md +40 -0
- package/references/extensions-and-sync.md +124 -0
- package/references/mutations.json +1424 -0
- package/references/native/InsightApplyDocumentation.java +48 -0
- package/references/native/InsightArchivePayloads.java +36 -0
- package/references/native/InsightBlockTransfer.java +57 -0
- package/references/native/InsightEmulation.java +93 -0
- package/references/native/InsightExtensions.java +36 -0
- package/references/native/InsightFunctionDatabases.java +98 -0
- package/references/native/InsightGuiMain.java +32 -0
- package/references/native/InsightHeadlessMain.java +10 -0
- package/references/native/InsightInitialImport.java +42 -0
- package/references/native/InsightInitialJournal.java +48 -0
- package/references/native/InsightInitialPublication.java +142 -0
- package/references/native/InsightKeytool.java +16 -0
- package/references/native/InsightNativeReceipt.java +31 -0
- package/references/native/InsightPlugins.java +34 -0
- package/references/native/InsightProjectFiles.java +92 -0
- package/references/native/InsightPublicationRecovery.java +63 -0
- package/references/native/InsightPythonWheels.java +31 -0
- package/references/native/InsightResearchQueries.java +188 -0
- package/references/native/InsightSdkFiles.java +142 -0
- package/references/native/InsightSdkScript.java +31 -0
- package/references/native/InsightSdkTools.java +338 -0
- package/references/native/InsightSdkTypes.java +132 -0
- package/references/native/InsightServerAdministration.java +82 -0
- package/references/native/InsightServerInit.java +62 -0
- package/references/native/InsightServerMain.java +43 -0
- package/references/native/InsightServerProbe.java +29 -0
- package/references/native/InsightSnapshotFields.java +68 -0
- package/references/native/InsightSnapshotInspect.java +189 -0
- package/references/native/InsightSnapshotPublisher.java +123 -0
- package/references/native/InsightStartDebugger.java +28 -0
- package/references/native/InsightStopDebugger.java +20 -0
- package/references/native/InsightTraceInspect.java +24 -0
- package/references/native/InsightTraceState.java +130 -0
- package/references/native/InsightTraceTools.java +114 -0
- package/references/native/InsightVersionTracking.java +91 -0
- package/references/native-workflows.md +112 -0
- package/references/queries.json +1761 -0
- package/references/query-semantics.md +96 -0
- package/references/recovery.md +53 -0
- package/references/research.md +101 -0
- package/references/setup.md +47 -0
- package/runtime/GhidraMCP-7.0.0.jar +0 -0
- package/runtime/GhidraMCP-LICENSE +202 -0
- package/runtime/GhidraMCP-NOTICE +10 -0
- package/runtime/provenance.json +11 -0
- package/skills/insight-re/SKILL.md +108 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Batch, type Binding, type FindingRecord, type Snapshot, type Target } from './model.js';
|
|
2
|
+
import { Store } from './store.js';
|
|
3
|
+
/** Ghidra operations are separately acknowledged calls. The journal, not a batch endpoint,
|
|
4
|
+
* is the recovery boundary. Implementations must preserve staged project directories. */
|
|
5
|
+
export interface Publisher {
|
|
6
|
+
open(target: Target, batchId: string): Promise<Binding>;
|
|
7
|
+
checkout(target: Target, binding: Binding): Promise<Snapshot>;
|
|
8
|
+
read(target: Target, binding: Binding, findings: FindingRecord[]): Promise<Snapshot>;
|
|
9
|
+
apply(target: Target, binding: Binding, finding: FindingRecord): Promise<void>;
|
|
10
|
+
save(target: Target, binding: Binding): Promise<void>;
|
|
11
|
+
checkIn(target: Target, binding: Binding, batchId: string): Promise<number>;
|
|
12
|
+
published(target: Target, findings: FindingRecord[]): Promise<Snapshot>;
|
|
13
|
+
history(target: Target, batchId: string): Promise<number[]>;
|
|
14
|
+
released(target: Target): Promise<boolean>;
|
|
15
|
+
release(target: Target, binding: Binding): Promise<void>;
|
|
16
|
+
complete(target: Target, binding: Binding): Promise<void>;
|
|
17
|
+
/** Return only after every owned publisher process is stopped, staged copy backed up,
|
|
18
|
+
* and any owned checkout safely released. This must refuse a live/unidentified writer. */
|
|
19
|
+
abandon(target: Target, batch: Batch): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export type Checkpoint = (phase: string, batch: Batch) => Promise<void>;
|
|
22
|
+
export declare class Coordinator {
|
|
23
|
+
readonly store: Store;
|
|
24
|
+
readonly publisher: Publisher;
|
|
25
|
+
private checkpoint;
|
|
26
|
+
private running;
|
|
27
|
+
constructor(store: Store, publisher: Publisher, checkpoint?: Checkpoint, running?: Map<string, Promise<Batch | {
|
|
28
|
+
state: "empty";
|
|
29
|
+
targetId: string;
|
|
30
|
+
}>>);
|
|
31
|
+
settled(): Promise<void>;
|
|
32
|
+
integrate(targetId: string): Promise<Batch | {
|
|
33
|
+
state: "empty";
|
|
34
|
+
targetId: string;
|
|
35
|
+
}>;
|
|
36
|
+
private mark;
|
|
37
|
+
private validate;
|
|
38
|
+
private integrateOnce;
|
|
39
|
+
private verify;
|
|
40
|
+
recover(batchId: string, abandon?: boolean): Promise<Batch>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { assertIdentity, fail, key } from './model.js';
|
|
2
|
+
export class Coordinator {
|
|
3
|
+
store;
|
|
4
|
+
publisher;
|
|
5
|
+
checkpoint;
|
|
6
|
+
running;
|
|
7
|
+
constructor(store, publisher, checkpoint = async () => { }, running = new Map()) {
|
|
8
|
+
this.store = store;
|
|
9
|
+
this.publisher = publisher;
|
|
10
|
+
this.checkpoint = checkpoint;
|
|
11
|
+
this.running = running;
|
|
12
|
+
}
|
|
13
|
+
async settled() { await Promise.allSettled([...this.running.values()]); }
|
|
14
|
+
integrate(targetId) {
|
|
15
|
+
const active = this.running.get(targetId);
|
|
16
|
+
if (active)
|
|
17
|
+
return active;
|
|
18
|
+
const work = this.integrateOnce(targetId).finally(() => this.running.delete(targetId));
|
|
19
|
+
this.running.set(targetId, work);
|
|
20
|
+
return work;
|
|
21
|
+
}
|
|
22
|
+
async mark(id, phase, patch = {}) {
|
|
23
|
+
const batch = this.store.update(id, phase, patch);
|
|
24
|
+
await this.checkpoint(phase, batch);
|
|
25
|
+
return batch;
|
|
26
|
+
}
|
|
27
|
+
validate(snapshot, target, findings, proposed) {
|
|
28
|
+
assertIdentity(snapshot.identity, target);
|
|
29
|
+
if (!Number.isSafeInteger(snapshot.version) || snapshot.version < 1)
|
|
30
|
+
fail('malformed', 'Readback did not contain a valid version.');
|
|
31
|
+
for (const finding of findings) {
|
|
32
|
+
if (!Object.hasOwn(snapshot.values, key(finding)))
|
|
33
|
+
fail('partial', 'Readback omitted a requested comment.');
|
|
34
|
+
const actual = snapshot.values[key(finding)];
|
|
35
|
+
if (actual !== null && typeof actual !== 'string')
|
|
36
|
+
fail('malformed', 'Readback contained a malformed comment.');
|
|
37
|
+
if (proposed && actual !== finding.after)
|
|
38
|
+
fail('verification', 'Published readback differs from the proposal.');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async integrateOnce(targetId) {
|
|
42
|
+
const target = this.store.target(targetId);
|
|
43
|
+
let batch = this.store.begin(targetId);
|
|
44
|
+
if (!batch)
|
|
45
|
+
return { state: 'empty', targetId };
|
|
46
|
+
try {
|
|
47
|
+
await this.checkpoint('accepted', batch);
|
|
48
|
+
const binding = await this.publisher.open(target, batch.id);
|
|
49
|
+
assertIdentity(binding.identity, target);
|
|
50
|
+
if (binding.role !== 'publisher' || !Number.isSafeInteger(binding.pid) || binding.pid <= 0)
|
|
51
|
+
fail('binding', 'Publisher binding is invalid.');
|
|
52
|
+
batch = await this.mark(batch.id, 'applying', { binding });
|
|
53
|
+
const baseline = await this.publisher.checkout(target, binding);
|
|
54
|
+
assertIdentity(baseline.identity, target);
|
|
55
|
+
if (!Number.isSafeInteger(baseline.version) || baseline.version < 1)
|
|
56
|
+
fail('malformed', 'Checkout did not report its baseline version.');
|
|
57
|
+
batch = await this.mark(batch.id, 'applying', { baselineVersion: baseline.version, binding });
|
|
58
|
+
const current = await this.publisher.read(target, binding, batch.findings);
|
|
59
|
+
this.validate(current, target, batch.findings, false);
|
|
60
|
+
if (current.version !== baseline.version)
|
|
61
|
+
fail('binding', 'Local copy is not bound to the fresh checkout baseline.');
|
|
62
|
+
const scopes = new Map();
|
|
63
|
+
const conflicts = [];
|
|
64
|
+
for (const f of batch.findings) {
|
|
65
|
+
const peer = scopes.get(key(f));
|
|
66
|
+
if (f.baselineVersion > baseline.version || current.values[key(f)] !== f.before ||
|
|
67
|
+
peer && (peer.before !== f.before || peer.after !== f.after)) {
|
|
68
|
+
conflicts.push({ findingId: f.findingId, peerFindingId: peer?.findingId, observed: current.values[key(f)], expected: f.before, proposed: f.after });
|
|
69
|
+
}
|
|
70
|
+
scopes.set(key(f), f);
|
|
71
|
+
}
|
|
72
|
+
if (conflicts.length) {
|
|
73
|
+
this.store.conflicts(batch.id, conflicts);
|
|
74
|
+
// No proposal has been applied; release must finish before unblocking the lane.
|
|
75
|
+
await this.publisher.release(target, binding);
|
|
76
|
+
batch = await this.mark(batch.id, 'conflict_pending', { detail: { conflicts } });
|
|
77
|
+
fail('conflict', 'Comment proposals conflict with the current baseline or each other.', batch);
|
|
78
|
+
}
|
|
79
|
+
for (const f of scopes.values()) {
|
|
80
|
+
await this.publisher.apply(target, binding, f);
|
|
81
|
+
const observed = await this.publisher.read(target, binding, [f]);
|
|
82
|
+
this.validate(observed, target, [f], true);
|
|
83
|
+
await this.checkpoint('local_application', this.store.batch(batch.id));
|
|
84
|
+
}
|
|
85
|
+
const all = await this.publisher.read(target, binding, batch.findings);
|
|
86
|
+
this.validate(all, target, batch.findings, true);
|
|
87
|
+
await this.publisher.save(target, binding);
|
|
88
|
+
batch = await this.mark(batch.id, 'staged');
|
|
89
|
+
// Durable check-in intent precedes the remote request. A kill at this boundary is
|
|
90
|
+
// deliberately uncertain even if the remote call had not started yet.
|
|
91
|
+
batch = await this.mark(batch.id, 'checking_in');
|
|
92
|
+
const version = await this.publisher.checkIn(target, binding, batch.id);
|
|
93
|
+
if (!Number.isSafeInteger(version) || version <= baseline.version)
|
|
94
|
+
fail('malformed', 'Check-in did not establish a newer server version.');
|
|
95
|
+
batch = await this.mark(batch.id, 'publication_unknown', { publishedVersion: version });
|
|
96
|
+
return await this.verify(target, batch);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
const latest = this.store.batch(batch.id);
|
|
100
|
+
if (!['conflict_pending', 'published_verified'].includes(latest.state)) {
|
|
101
|
+
const uncertain = ['checking_in', 'publication_unknown'].includes(latest.state);
|
|
102
|
+
const result = this.store.update(batch.id, uncertain ? 'publication_unknown' : 'failed', { detail: { phase: latest.state } });
|
|
103
|
+
fail(uncertain ? 'unknown' : 'staged', uncertain ? 'Publication outcome is unresolved; reconcile this batch before any retry.' : 'Integration stopped; the journal and staged copy are retained.', result);
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async verify(target, batch) {
|
|
109
|
+
const versions = await this.publisher.history(target, batch.id);
|
|
110
|
+
if (versions.length !== 1 || versions[0] !== batch.publishedVersion)
|
|
111
|
+
fail('unknown', 'Server history does not uniquely establish this publication.');
|
|
112
|
+
const actual = await this.publisher.published(target, batch.findings);
|
|
113
|
+
this.validate(actual, target, batch.findings, true);
|
|
114
|
+
if (actual.version !== batch.publishedVersion || !await this.publisher.released(target))
|
|
115
|
+
fail('unknown', 'Independent readback or checkout release is unresolved.');
|
|
116
|
+
await this.checkpoint('verification', batch);
|
|
117
|
+
if (batch.binding)
|
|
118
|
+
await this.publisher.complete(target, batch.binding);
|
|
119
|
+
return this.mark(batch.id, 'published_verified');
|
|
120
|
+
}
|
|
121
|
+
async recover(batchId, abandon = false) {
|
|
122
|
+
let batch = this.store.batch(batchId);
|
|
123
|
+
const target = this.store.target(batch.targetId);
|
|
124
|
+
if (this.running.has(batch.targetId))
|
|
125
|
+
fail('busy', 'The publication owner is still executing this lane.');
|
|
126
|
+
if (['published_verified', 'conflict_pending'].includes(batch.state))
|
|
127
|
+
return batch;
|
|
128
|
+
if (batch.ownerPid !== process.pid) {
|
|
129
|
+
try {
|
|
130
|
+
process.kill(batch.ownerPid, 0);
|
|
131
|
+
fail('busy', 'The prior owner is still alive; recovery cannot take over.');
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (error.code !== 'ESRCH')
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const intent = this.store.events(batchId).some(e => e.state === 'checking_in');
|
|
139
|
+
if (intent) {
|
|
140
|
+
if (abandon)
|
|
141
|
+
fail('unknown', 'An uncertain check-in cannot be abandoned or retried.');
|
|
142
|
+
const versions = await this.publisher.history(target, batch.id);
|
|
143
|
+
if (versions.length !== 1)
|
|
144
|
+
fail('unknown', 'Server history is insufficient; this lane remains blocked.', batch);
|
|
145
|
+
batch = this.store.update(batchId, 'publication_unknown', { publishedVersion: versions[0] });
|
|
146
|
+
try {
|
|
147
|
+
return await this.verify(target, batch);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
fail('unknown', 'Reconciliation could not verify every publication result; this lane remains blocked.', batch);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!abandon)
|
|
154
|
+
fail('staged', 'A pre-publication stage is retained; use explicit abandon after stopping its publisher.', batch);
|
|
155
|
+
await this.publisher.abandon(target, batch);
|
|
156
|
+
// Findings remain observable. A fresh request can explicitly resubmit from a verified
|
|
157
|
+
// baseline; the original failed batch and stage are never overwritten or replayed.
|
|
158
|
+
return this.store.update(batchId, 'conflict_pending', { detail: { abandoned: true, requiresNewProposals: true } });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Scope } from './workspace.js';
|
|
2
|
+
/** Domain contracts for the pinned comparison leaves, independent of backend acknowledgment. */
|
|
3
|
+
export declare function crossResult(path: string, parameters: Record<string, unknown>, input: unknown): Record<string, unknown>;
|
|
4
|
+
export declare const transferCategories: readonly ["function_names", "function_signatures", "function_plate_comments", "function_calling_conventions", "function_no_return_flags", "function_tags", "function_locals", "data_types", "data_definitions", "data_plate_comments", "instruction_comments", "data_item_comments", "labels", "globals", "external_locations", "bookmarks", "equates"];
|
|
5
|
+
export declare function transferResult(input: unknown, dryRun: boolean): {
|
|
6
|
+
dryRun: boolean;
|
|
7
|
+
categories: Record<string, {
|
|
8
|
+
planned: number;
|
|
9
|
+
applied: number;
|
|
10
|
+
}>;
|
|
11
|
+
total: number;
|
|
12
|
+
};
|
|
13
|
+
/** Routing identities become logical scopes; arbitrary function/symbol names remain untouched. */
|
|
14
|
+
export declare function logicalPrograms(value: unknown, names: Map<string, Scope>): unknown;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { object, fail } from './model.js';
|
|
2
|
+
const count = (value) => { if (!Number.isSafeInteger(value) || Number(value) < 0)
|
|
3
|
+
fail('partial', 'Cross-target result has an invalid count.'); return Number(value); };
|
|
4
|
+
const scores = (value) => { if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1)
|
|
5
|
+
fail('partial', 'Cross-target result has an invalid similarity score.'); };
|
|
6
|
+
/** Domain contracts for the pinned comparison leaves, independent of backend acknowledgment. */
|
|
7
|
+
export function crossResult(path, parameters, input) {
|
|
8
|
+
const value = object(input);
|
|
9
|
+
if (JSON.stringify(value).length > 1048576)
|
|
10
|
+
fail('limit', 'Cross-target result exceeds one MiB; narrow its scope.');
|
|
11
|
+
if (path === '/compare_programs_documentation') {
|
|
12
|
+
if (!Array.isArray(value.programs) || value.programs.length !== 2 || value.count !== 2)
|
|
13
|
+
fail('partial', 'Comparison did not return both selected contexts.');
|
|
14
|
+
for (const item of value.programs) {
|
|
15
|
+
const program = object(item);
|
|
16
|
+
const total = count(program.total_functions);
|
|
17
|
+
if (count(program.documented) + count(program.undocumented) !== total)
|
|
18
|
+
fail('partial', 'Program documentation counts are inconsistent.');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else if (path === '/diff_functions') {
|
|
22
|
+
const a = object(value.function_a), b = object(value.function_b);
|
|
23
|
+
if (typeof a.name !== 'string' || typeof b.name !== 'string')
|
|
24
|
+
fail('partial', 'Diff omitted its function identities.');
|
|
25
|
+
scores(object(value.summary).similarity_score);
|
|
26
|
+
for (const key of ['body_equal', 'body_added', 'body_removed'])
|
|
27
|
+
count(object(value.summary)[key]);
|
|
28
|
+
for (const key of ['prologue_diff', 'body_diff', 'epilogue_diff'])
|
|
29
|
+
if (!Array.isArray(value[key]))
|
|
30
|
+
fail('partial', 'Diff omitted an instruction section.');
|
|
31
|
+
}
|
|
32
|
+
else if (path === '/bulk_fuzzy_match' || path === '/find_similar_functions_fuzzy') {
|
|
33
|
+
if (!Array.isArray(value.matches) || value.matches.length > Number(parameters.limit ?? (path === '/bulk_fuzzy_match' ? 50 : 20)))
|
|
34
|
+
fail('partial', 'Matching result exceeds its page.');
|
|
35
|
+
if (path === '/bulk_fuzzy_match') {
|
|
36
|
+
count(value.total_source_functions);
|
|
37
|
+
if (value.offset !== parameters.offset || value.limit !== parameters.limit)
|
|
38
|
+
fail('partial', 'Matching page changed its selectors.');
|
|
39
|
+
}
|
|
40
|
+
else
|
|
41
|
+
count(value.total_matches);
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
fail('schema', 'Cross-target query lacks a result contract.');
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
export const transferCategories = ['function_names', 'function_signatures', 'function_plate_comments', 'function_calling_conventions', 'function_no_return_flags',
|
|
48
|
+
'function_tags', 'function_locals', 'data_types', 'data_definitions', 'data_plate_comments', 'instruction_comments', 'data_item_comments', 'labels', 'globals', 'external_locations', 'bookmarks', 'equates'];
|
|
49
|
+
export function transferResult(input, dryRun) {
|
|
50
|
+
const value = object(input);
|
|
51
|
+
if (value.dry_run !== dryRun || count(value.errors) !== 0)
|
|
52
|
+
fail('partial', 'Documentation transfer reported an incomplete outcome.');
|
|
53
|
+
const categories = {};
|
|
54
|
+
let total = 0;
|
|
55
|
+
for (const key of transferCategories) {
|
|
56
|
+
const item = object(value[key]), planned = count(item.planned), applied = count(item.applied);
|
|
57
|
+
if (applied !== planned)
|
|
58
|
+
fail('partial', 'Documentation transfer skipped part of ' + key);
|
|
59
|
+
categories[key] = { planned, applied };
|
|
60
|
+
total += applied;
|
|
61
|
+
}
|
|
62
|
+
return { dryRun, categories, total };
|
|
63
|
+
}
|
|
64
|
+
/** Routing identities become logical scopes; arbitrary function/symbol names remain untouched. */
|
|
65
|
+
export function logicalPrograms(value, names) {
|
|
66
|
+
if (Array.isArray(value))
|
|
67
|
+
return value.map(item => logicalPrograms(item, names));
|
|
68
|
+
if (!value || typeof value !== 'object')
|
|
69
|
+
return value;
|
|
70
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => !['source_path', 'target_path'].includes(key)).map(([key, item]) => [key, ['program', 'source_program', 'target_program'].includes(key) && typeof item === 'string' && names.has(item) ? names.get(item) : logicalPrograms(item, names)]));
|
|
71
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { command, Permission } from '@eyeauras/cli-factory';
|
|
2
|
+
import { scope } from './research.js';
|
|
3
|
+
import { id, fail } from './model.js';
|
|
4
|
+
import { option, key, parsed, selectedOptions } from './arguments.js';
|
|
5
|
+
import { crossResult } from './cross-results.js';
|
|
6
|
+
import { qualifyQuery } from './query-policy.js';
|
|
7
|
+
const leaves = [
|
|
8
|
+
{ path: '/compare_programs_documentation', group: 'documentation', name: 'compare', params: [] },
|
|
9
|
+
{ path: '/diff_functions', group: 'functions', name: 'diff', params: [{ name: 'address_a', type: 'string', required: true, semantic: 'address' }, { name: 'address_b', type: 'string', required: true, semantic: 'address' }] },
|
|
10
|
+
{ path: '/find_similar_functions_fuzzy', group: 'documentation', name: 'find-similar-functions-fuzzy', params: [{ name: 'function', type: 'string', required: true },
|
|
11
|
+
{ name: 'threshold', type: 'number', required: false, default: '0.7' }, { name: 'limit', type: 'integer', required: false, default: '20' }] },
|
|
12
|
+
{ path: '/bulk_fuzzy_match', group: 'documentation', name: 'bulk-fuzzy-match', params: [{ name: 'threshold', type: 'number', required: false, default: '0.7' },
|
|
13
|
+
{ name: 'offset', type: 'integer', required: false, default: '0' }, { name: 'limit', type: 'integer', required: false, default: '50' }, { name: 'filter', type: 'string', required: false, default: '', allowEmpty: true }] },
|
|
14
|
+
];
|
|
15
|
+
export function crossCommands(research) {
|
|
16
|
+
const groups = new Map();
|
|
17
|
+
for (const leaf of leaves) {
|
|
18
|
+
const children = groups.get(leaf.group) ?? [];
|
|
19
|
+
groups.set(leaf.group, children);
|
|
20
|
+
children.push(command(leaf.name, 'Compare two explicitly selected working contexts', async ({ options: o }, c) => {
|
|
21
|
+
const selected = scope(o), other = { session: o.otherSession === undefined ? selected.session : id(o.otherSession), target: id(o.otherTarget) }, parameters = {};
|
|
22
|
+
for (const p of leaf.params) {
|
|
23
|
+
const input = o[key(p.name)];
|
|
24
|
+
if (input !== undefined)
|
|
25
|
+
parameters[p.name] = parsed(p, input);
|
|
26
|
+
}
|
|
27
|
+
if (parameters.threshold !== undefined && (Number(parameters.threshold) < 0 || Number(parameters.threshold) > 1))
|
|
28
|
+
fail('invalid', 'Threshold must be between 0 and 1.');
|
|
29
|
+
const value = crossResult(leaf.path, parameters, await research(c).crossRead(selected, other, leaf.path, parameters));
|
|
30
|
+
return { ...selected, other, value: qualifyQuery(leaf.path, value) };
|
|
31
|
+
}, { permission: Permission.ReadOnly, options: [...selectedOptions, { flags: '--other-target <id>', description: 'Second immutable target', required: true },
|
|
32
|
+
{ flags: '--other-session <id>', description: 'Second context session (defaults to --session)' }, ...leaf.params.map(option)] }));
|
|
33
|
+
}
|
|
34
|
+
const documentation = groups.get('documentation');
|
|
35
|
+
documentation.push(command('transfer', 'Copy documentation into a saved private destination; publish with explicit merge', ({ options: o }, c) => {
|
|
36
|
+
const selected = scope(o), source = { session: o.sourceSession === undefined ? selected.session : id(o.sourceSession), target: id(o.sourceTarget) };
|
|
37
|
+
return research(c).transfer(selected, source, Boolean(o.dryRun), o.requestId === undefined ? undefined : id(o.requestId));
|
|
38
|
+
}, { permission: 'Destructive', options: [...selectedOptions, { flags: '--source-target <id>', description: 'Documentation source target', required: true },
|
|
39
|
+
{ flags: '--source-session <id>', description: 'Source context session (defaults to --session)' },
|
|
40
|
+
{ flags: '--dry-run', description: 'Preview transfer counts without applying documentation' },
|
|
41
|
+
{ flags: '--request-id <id>', description: 'Retrieve the prior transfer outcome after a lost response' }] }));
|
|
42
|
+
return [...groups].map(([name, children]) => command(name, `${name} across two working contexts`, children));
|
|
43
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ResearchBackend } from './research.js';
|
|
2
|
+
import type { Artifact } from './artifacts.js';
|
|
3
|
+
export declare function verifyDataMutation(backend: ResearchBackend, snapshot: Artifact, path: string, p: Record<string, unknown>, response: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { address } from './research.js';
|
|
2
|
+
import { object, fail } from './model.js';
|
|
3
|
+
const equal = (actual, expected, label) => { if (actual !== expected)
|
|
4
|
+
fail('partial', `Saved ${label} differs from the requested effect.`); };
|
|
5
|
+
export async function verifyDataMutation(backend, snapshot, path, p, response) {
|
|
6
|
+
if (!backend.inspect)
|
|
7
|
+
fail('configuration', 'Data edits require saved-state inspection.');
|
|
8
|
+
const inspect = (kind, parameters = {}) => backend.inspect(snapshot, { kind, ...parameters });
|
|
9
|
+
const resolve = async (name) => { const value = await inspect('resolve_type', { name }); equal(value.exists, true, 'type presence'); return object(value.type); };
|
|
10
|
+
if (path === '/import_data_types') {
|
|
11
|
+
if (response.parse_succeeded !== true)
|
|
12
|
+
fail('partial', 'Type import did not complete parsing.');
|
|
13
|
+
const value = await inspect('import_check', { source: p.source });
|
|
14
|
+
if (!Array.isArray(value.types) || !value.types.length)
|
|
15
|
+
fail('partial', 'Imported type inventory is absent.');
|
|
16
|
+
for (const type of value.types)
|
|
17
|
+
equal(object(type).matches, true, 'imported type ' + object(type).name);
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
if (path === '/set_function_this_type') {
|
|
21
|
+
const value = await inspect('function', { address: p.function_address });
|
|
22
|
+
equal(value.exists, true, 'member function presence');
|
|
23
|
+
let name = String(p.this_type).trim();
|
|
24
|
+
if (!name.endsWith('*'))
|
|
25
|
+
name += ' *';
|
|
26
|
+
const pointer = await resolve(name);
|
|
27
|
+
const parameters = value.parameters;
|
|
28
|
+
if (!Array.isArray(parameters))
|
|
29
|
+
fail('partial', 'Saved member parameters are absent.');
|
|
30
|
+
const self = parameters.find(param => object(param).auto === true && object(param).name === 'this');
|
|
31
|
+
if (!self)
|
|
32
|
+
fail('partial', 'Saved implicit this parameter is absent.');
|
|
33
|
+
equal(object(self).type, pointer.name, 'implicit this type');
|
|
34
|
+
equal(String(value.namespace).split('::').at(-1), pointer.pointerBase, 'owning class');
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
if (path === '/clear_flow_and_repair') {
|
|
38
|
+
if (response.success !== true || response.repair !== true)
|
|
39
|
+
fail('partial', 'Flow repair lacks a completed outcome.');
|
|
40
|
+
const range = object(response.seed_range);
|
|
41
|
+
equal(address(String(range.start_address)), address(p.start_address), 'repair seed start');
|
|
42
|
+
if (p.end_address)
|
|
43
|
+
equal(address(String(range.end_address_exclusive)), address(p.end_address), 'repair seed end');
|
|
44
|
+
const value = await inspect('instruction_range', { address: p.start_address, end_address: range.end_address_exclusive });
|
|
45
|
+
equal(value.count, object(response.observations).instructions_in_seed_after, 'repaired instruction count');
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
const data = await inspect('data', { address: p.address });
|
|
49
|
+
if (path === '/set_global' && p.type_name) {
|
|
50
|
+
equal(data.exists, true, 'defined global presence');
|
|
51
|
+
const type = await resolve(p.type_name);
|
|
52
|
+
if (Number(p.array_length ?? 0) > 0) {
|
|
53
|
+
const details = object(data.details);
|
|
54
|
+
equal(details.elementType, type.name, 'global array element');
|
|
55
|
+
equal(details.elementCount, p.array_length, 'global array length');
|
|
56
|
+
}
|
|
57
|
+
else
|
|
58
|
+
equal(data.type, type.name, 'global type');
|
|
59
|
+
}
|
|
60
|
+
if (path === '/apply_data_classification') {
|
|
61
|
+
equal(data.exists, true, 'classified data presence');
|
|
62
|
+
const definition = object(p.type_definition);
|
|
63
|
+
if (p.classification === 'PRIMITIVE') {
|
|
64
|
+
const type = await resolve(definition.type);
|
|
65
|
+
equal(data.type, type.name, 'classified primitive type');
|
|
66
|
+
}
|
|
67
|
+
if (p.classification === 'STRUCTURE') {
|
|
68
|
+
equal(data.type, definition.name, 'classified structure type');
|
|
69
|
+
const type = await resolve(definition.name);
|
|
70
|
+
const fields = type.fields;
|
|
71
|
+
if (!Array.isArray(fields) || !Array.isArray(definition.fields))
|
|
72
|
+
fail('partial', 'Classified structure layout is absent.');
|
|
73
|
+
equal(fields.length, definition.fields.length, 'classified field count');
|
|
74
|
+
for (let i = 0; i < fields.length; i++) {
|
|
75
|
+
const requested = object(definition.fields[i]), expected = await resolve(requested.type);
|
|
76
|
+
equal(object(fields[i]).name, requested.name, 'classified field name');
|
|
77
|
+
equal(object(fields[i]).type, expected.name, 'classified field type');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (p.classification === 'ARRAY') {
|
|
81
|
+
const type = await resolve(definition.element_type ?? definition.element_struct), details = object(data.details);
|
|
82
|
+
equal(details.elementType, type.name, 'classified array element');
|
|
83
|
+
equal(details.elementCount, Number(definition.count), 'classified array length');
|
|
84
|
+
}
|
|
85
|
+
if (p.classification === 'STRING')
|
|
86
|
+
equal(data.isString, true, 'classified string value');
|
|
87
|
+
if (p.comment) {
|
|
88
|
+
const comment = await inspect('comments', { address: p.address });
|
|
89
|
+
equal(comment.pre, String(p.comment).replaceAll('\\n', '\n').replaceAll('\\t', '\t').replaceAll('\\r', '\r'), 'classified comment');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
let symbols, comments;
|
|
93
|
+
if (p.name) {
|
|
94
|
+
symbols = await inspect('symbols', { address: p.address });
|
|
95
|
+
if (!Array.isArray(symbols.symbols) || !symbols.symbols.some(symbol => object(symbol).name === p.name))
|
|
96
|
+
fail('partial', 'Saved global label is absent.');
|
|
97
|
+
}
|
|
98
|
+
if (path === '/set_global' && p.plate_comment) {
|
|
99
|
+
comments = await inspect('comments', { address: p.address });
|
|
100
|
+
equal(comments.plate, p.plate_comment, 'global plate comment');
|
|
101
|
+
}
|
|
102
|
+
return { data, ...(symbols ? { symbols } : {}), ...(comments ? { comments } : {}) };
|
|
103
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ResearchBackend } from './research.js';
|
|
2
|
+
import type { Artifact } from './artifacts.js';
|
|
3
|
+
export declare function debuggerProgramMetadata(backend: Pick<ResearchBackend, 'inspect'>, snapshot: Artifact): Promise<string | undefined>;
|
|
4
|
+
export declare function debuggerResult(operation: string, p: Record<string, unknown>, raw: unknown): Record<string, unknown> | Record<string, unknown>[];
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { object, fail, text } from './model.js';
|
|
2
|
+
import { address, publicAddress } from './research.js';
|
|
3
|
+
const array = (raw) => { if (!Array.isArray(raw))
|
|
4
|
+
fail('partial', 'Debugger inventory is absent.'); return raw.map(object); };
|
|
5
|
+
const count = (value) => { if (!Number.isSafeInteger(value) || Number(value) < 0)
|
|
6
|
+
fail('partial', 'Debugger count is invalid.'); };
|
|
7
|
+
export async function debuggerProgramMetadata(backend, snapshot) {
|
|
8
|
+
if (!backend.inspect)
|
|
9
|
+
return undefined;
|
|
10
|
+
const value = object(await backend.inspect(snapshot, { kind: 'debugger_bookmarks' }));
|
|
11
|
+
if (typeof value.fingerprint !== 'string' || !/^[a-f0-9]{64}$/.test(value.fingerprint))
|
|
12
|
+
fail('partial', 'Debugger program metadata inspection is incomplete.');
|
|
13
|
+
count(value.count);
|
|
14
|
+
return value.fingerprint;
|
|
15
|
+
}
|
|
16
|
+
export function debuggerResult(operation, p, raw) {
|
|
17
|
+
if (['launch_offers', 'traces', 'modules', 'list_breakpoints', 'stack_trace'].includes(operation)) {
|
|
18
|
+
const rows = array(raw);
|
|
19
|
+
for (const row of rows) {
|
|
20
|
+
if (operation === 'launch_offers') {
|
|
21
|
+
text(row.title);
|
|
22
|
+
text(row.config_name);
|
|
23
|
+
if (typeof row.supports_image !== 'boolean' || typeof row.requires_image !== 'boolean' || !Array.isArray(row.parameters))
|
|
24
|
+
fail('partial', 'Debugger launch offer is incomplete.');
|
|
25
|
+
}
|
|
26
|
+
if (operation === 'traces') {
|
|
27
|
+
text(row.name);
|
|
28
|
+
if (typeof row.current !== 'boolean')
|
|
29
|
+
fail('partial', 'Trace selection is absent.');
|
|
30
|
+
count(row.module_count);
|
|
31
|
+
count(row.thread_count);
|
|
32
|
+
}
|
|
33
|
+
if (operation === 'modules') {
|
|
34
|
+
text(row.name);
|
|
35
|
+
if (row.base !== undefined)
|
|
36
|
+
row.base = publicAddress(row.base);
|
|
37
|
+
if (row.size !== undefined && !/^0x[0-9a-f]+$/i.test(String(row.size)))
|
|
38
|
+
fail('partial', 'Module size is invalid.');
|
|
39
|
+
}
|
|
40
|
+
if (operation === 'list_breakpoints') {
|
|
41
|
+
row.address = publicAddress(row.address);
|
|
42
|
+
count(row.trace_location_count);
|
|
43
|
+
if (typeof row.name !== 'string' || row.name.length > 4096 || row.name.includes('\0'))
|
|
44
|
+
fail('partial', 'Breakpoint name is invalid.');
|
|
45
|
+
text(row.state);
|
|
46
|
+
if (!Array.isArray(row.kinds) || row.kinds.some(kind => typeof kind !== 'string'))
|
|
47
|
+
fail('partial', 'Breakpoint kinds are absent.');
|
|
48
|
+
}
|
|
49
|
+
if (operation === 'stack_trace') {
|
|
50
|
+
count(row.level);
|
|
51
|
+
if (row.address !== undefined)
|
|
52
|
+
row.address = publicAddress(row.address);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return rows;
|
|
56
|
+
}
|
|
57
|
+
const value = object(raw);
|
|
58
|
+
if (['write_memory', 'write_register'].includes(operation)) {
|
|
59
|
+
if (value.status !== 'written' || value.verified_from !== 'live_target_refresh' || !/^-?\d+$/.test(String(value.snap)))
|
|
60
|
+
fail('partial', 'Live write omitted independent refreshed target verification.');
|
|
61
|
+
if (operation === 'write_memory') {
|
|
62
|
+
if (address(value.address) !== address(p.address) || String(value.bytes_hex).toLowerCase() !== String(p.bytes_hex).toLowerCase() || Number(value.size) !== String(p.bytes_hex).length / 2)
|
|
63
|
+
fail('partial', 'Live memory verification differs from the requested bytes.');
|
|
64
|
+
value.address = publicAddress(value.address);
|
|
65
|
+
}
|
|
66
|
+
else if (String(value.register).toLowerCase() !== String(p.register).toLowerCase() || !/^(?:0x)?[a-f0-9]+$/i.test(String(value.value)) || BigInt('0x' + String(value.value).replace(/^0x/i, '')) !== BigInt('0x' + String(p.value).replace(/^0x/i, '')) || !Number.isSafeInteger(value.bits) || Number(value.bits) < 1 || Number(value.bits) > 512)
|
|
67
|
+
fail('partial', 'Live register verification differs from the requested value.');
|
|
68
|
+
}
|
|
69
|
+
else if (['timeline', 'objects', 'object_values'].includes(operation)) {
|
|
70
|
+
if (!Array.isArray(value.items) || typeof value.complete !== 'boolean' || value.items.length > Number(p.limit ?? 1000))
|
|
71
|
+
fail('partial', 'Trace inventory omitted its bounded completion contract.');
|
|
72
|
+
for (const rawRow of value.items) {
|
|
73
|
+
const row = object(rawRow);
|
|
74
|
+
if (operation === 'timeline') {
|
|
75
|
+
if (!/^-?\d+$/.test(String(row.snap)) || typeof row.description !== 'string')
|
|
76
|
+
fail('partial', 'Trace timeline row is incomplete.');
|
|
77
|
+
}
|
|
78
|
+
if (operation === 'objects') {
|
|
79
|
+
if (!/^\d+$/.test(String(row.object_id)) || typeof row.path !== 'string' || typeof row.schema !== 'string' || typeof row.life !== 'string')
|
|
80
|
+
fail('partial', 'Trace object row is incomplete.');
|
|
81
|
+
}
|
|
82
|
+
if (operation === 'object_values') {
|
|
83
|
+
if (row.key !== p.key || typeof row.lifespan !== 'string' || typeof object(row.value).kind !== 'string')
|
|
84
|
+
fail('partial', 'Trace value row differs from the selected key.');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (operation === 'objects' && (!Number.isSafeInteger(value.visited) || Number(value.visited) < 0 || Number(value.visited) > Number(p.max_scan)))
|
|
88
|
+
fail('partial', 'Trace object work budget is inconsistent.');
|
|
89
|
+
if (operation === 'object_values' && String(value.object_id) !== String(p.object_id))
|
|
90
|
+
fail('binding', 'Trace value query selected another object.');
|
|
91
|
+
}
|
|
92
|
+
else if (operation === 'schedule') {
|
|
93
|
+
if (value.validated !== true || value.executed !== false || typeof value.schedule !== 'string' || !/^-?\d+$/.test(String(value.base_snap)) || !/^\d+$/.test(String(value.ticks)) || !/^\d+$/.test(String(value.patches)) || typeof value.snap_only !== 'boolean')
|
|
94
|
+
fail('partial', 'Trace schedule validation is incomplete.');
|
|
95
|
+
}
|
|
96
|
+
else if (operation === 'status') {
|
|
97
|
+
if (value.debugger_active !== true || typeof value.trace_active !== 'boolean')
|
|
98
|
+
fail('partial', 'Debugger status is incomplete.');
|
|
99
|
+
count(value.open_traces);
|
|
100
|
+
if (value.trace_active) {
|
|
101
|
+
if (typeof value.target_connected !== 'boolean')
|
|
102
|
+
fail('partial', 'Trace connection state is absent.');
|
|
103
|
+
count(value.module_count);
|
|
104
|
+
if (value.execution_state !== undefined && !['RUNNING', 'STOPPED', 'ALIVE', 'TERMINATED', 'UNKNOWN', 'INACTIVE'].includes(String(value.execution_state)))
|
|
105
|
+
fail('partial', 'Debugger execution state is invalid.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else if (operation === 'registers') {
|
|
109
|
+
for (const [name, register] of Object.entries(value)) {
|
|
110
|
+
text(name, 128);
|
|
111
|
+
if (typeof register !== 'string' || !/^0x[a-f0-9]{1,16}$/i.test(register))
|
|
112
|
+
fail('partial', 'Register result is not an exact hexadecimal value.');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (operation === 'read_memory') {
|
|
116
|
+
if (address(value.address) !== address(p.address) || !Number.isSafeInteger(value.size) || Number(value.size) < 0 || Number(value.size) > Number(p.size) || typeof value.hex !== 'string' || !/^(?:[a-f0-9]{2})*$/i.test(value.hex) || value.hex.length !== Number(value.size) * 2 || !Array.isArray(value.dwords) || value.dwords.length !== Math.floor(Number(value.size) / 4))
|
|
117
|
+
fail('partial', 'Debugger memory read is inconsistent.');
|
|
118
|
+
value.address = publicAddress(value.address);
|
|
119
|
+
for (let i = 0; i < value.dwords.length; i++) {
|
|
120
|
+
const bytes = value.hex.slice(i * 8, i * 8 + 8).match(/../g).reverse().join('');
|
|
121
|
+
if (typeof value.dwords[i] !== 'string' || String(value.dwords[i]).toLowerCase() !== '0x' + bytes.toLowerCase())
|
|
122
|
+
fail('partial', 'Debugger DWORDs differ from its memory bytes.');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (operation === 'static_to_dynamic') {
|
|
126
|
+
if (address(value.static_address) !== address(p.address))
|
|
127
|
+
fail('binding', 'Static mapping selected another address.');
|
|
128
|
+
value.static_address = publicAddress(value.static_address);
|
|
129
|
+
value.dynamic_address = publicAddress(value.dynamic_address);
|
|
130
|
+
}
|
|
131
|
+
else if (operation === 'dynamic_to_static') {
|
|
132
|
+
if (address(value.dynamic_address) !== address(p.address) || typeof value.module !== 'string' || !/^0x[0-9a-f]+$/i.test(String(value.offset)))
|
|
133
|
+
fail('partial', 'Dynamic mapping result is incomplete.');
|
|
134
|
+
value.dynamic_address = publicAddress(value.dynamic_address);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const expected = { launch: 'launched', resume: 'resumed', interrupt: 'interrupted', step: p.kind === 'out' ? 'stepped_out' : 'stepped', set_breakpoint: 'set', remove_breakpoint: 'removed' };
|
|
138
|
+
if (value.status !== expected[operation] || !expected[operation])
|
|
139
|
+
fail('partial', 'Debugger action did not establish its expected result.');
|
|
140
|
+
if (operation === 'launch') {
|
|
141
|
+
text(value.offer_title);
|
|
142
|
+
text(value.trace_name);
|
|
143
|
+
}
|
|
144
|
+
if (operation === 'set_breakpoint' || operation === 'remove_breakpoint') {
|
|
145
|
+
if (address(value.address) !== address(p.address))
|
|
146
|
+
fail('binding', 'Breakpoint action selected another address.');
|
|
147
|
+
value.address = publicAddress(value.address);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|