@eyeauras/insight-cli 0.3.0 → 0.3.2
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 -1
- package/SKILL.md +6 -1
- package/dist/src/analysis-default.d.ts +26 -0
- package/dist/src/analysis-default.js +149 -0
- package/dist/src/analysis-storage.d.ts +39 -20
- package/dist/src/analysis-storage.js +112 -16
- package/dist/src/cli.js +9 -2
- package/dist/src/initial-model.js +26 -5
- package/dist/src/initial-progress.d.ts +4 -0
- package/dist/src/initial-progress.js +64 -0
- package/dist/src/native-inputs.js +2 -1
- package/dist/src/server.js +14 -0
- package/package.json +1 -1
- package/references/native/InsightImportProgress.java +42 -0
- package/references/native/InsightInitialImport.java +8 -2
- package/references/native/InsightSdkFiles.java +13 -10
- package/references/setup.md +27 -0
- package/references/storage.md +35 -7
- package/skill-bundle/insight-artifacts/SKILL.md +1 -1
- package/skill-bundle/insight-artifacts/references/storage.md +35 -7
- package/skill-bundle/insight-binary/SKILL.md +1 -1
- package/skill-bundle/insight-capture/SKILL.md +1 -1
- package/skill-bundle/insight-cli/SKILL.md +6 -1
- package/skill-bundle/insight-cli/references/archive.md +32 -0
- package/skill-bundle/insight-cli/references/managed-runtime.md +162 -0
- package/skill-bundle/insight-cli/references/setup.md +98 -0
- package/skill-bundle/insight-cli/references/storage.md +35 -7
- package/skill-bundle/insight-die/SKILL.md +1 -1
- package/skill-bundle/insight-dnspy/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-dump/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-stack/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-trace/SKILL.md +1 -1
- package/skill-bundle/insight-frida/SKILL.md +1 -1
- package/skill-bundle/insight-ghidra/SKILL.md +27 -1
- package/skill-bundle/insight-ghidra/references/setup.md +27 -0
- package/skill-bundle/insight-ghidra/references/storage.md +35 -7
- package/skill-bundle/insight-ilspy/SKILL.md +1 -1
- package/skill-bundle/insight-interop/SKILL.md +1 -1
- package/skill-bundle/insight-lldb/SKILL.md +1 -1
- package/skill-bundle/insight-llvm/SKILL.md +1 -1
- package/skill-bundle/insight-managed-heap/SKILL.md +1 -1
- package/skill-bundle/insight-managed-metadata/SKILL.md +1 -1
- package/skill-bundle/insight-mitmproxy/SKILL.md +1 -1
- package/skill-bundle/insight-overview/SKILL.md +1 -1
- package/skill-bundle/insight-procmon/SKILL.md +1 -1
- package/skill-bundle/insight-re/SKILL.md +6 -1
- package/skill-bundle/insight-re/references/storage.md +35 -7
- package/skill-bundle/insight-sevenzip/SKILL.md +1 -1
- package/skill-bundle/insight-strace/SKILL.md +1 -1
- package/skill-bundle/insight-tshark/SKILL.md +1 -1
- package/skill-bundle/insight-windbg/SKILL.md +1 -1
- package/skill-bundle/manifest.json +1 -1
- package/skills/insight-artifacts/SKILL.md +1 -1
- package/skills/insight-artifacts/references/storage.md +35 -7
- package/skills/insight-binary/SKILL.md +1 -1
- package/skills/insight-capture/SKILL.md +1 -1
- package/skills/insight-die/SKILL.md +1 -1
- package/skills/insight-dnspy/SKILL.md +1 -1
- package/skills/insight-dotnet-dump/SKILL.md +1 -1
- package/skills/insight-dotnet-stack/SKILL.md +1 -1
- package/skills/insight-dotnet-trace/SKILL.md +1 -1
- package/skills/insight-frida/SKILL.md +1 -1
- package/skills/insight-ghidra/SKILL.md +27 -1
- package/skills/insight-ghidra/references/setup.md +27 -0
- package/skills/insight-ghidra/references/storage.md +35 -7
- package/skills/insight-ilspy/SKILL.md +1 -1
- package/skills/insight-interop/SKILL.md +1 -1
- package/skills/insight-lldb/SKILL.md +1 -1
- package/skills/insight-llvm/SKILL.md +1 -1
- package/skills/insight-managed-heap/SKILL.md +1 -1
- package/skills/insight-managed-metadata/SKILL.md +1 -1
- package/skills/insight-mitmproxy/SKILL.md +1 -1
- package/skills/insight-overview/SKILL.md +1 -1
- package/skills/insight-procmon/SKILL.md +1 -1
- package/skills/insight-re/SKILL.md +6 -1
- package/skills/insight-re/references/storage.md +35 -7
- package/skills/insight-sevenzip/SKILL.md +1 -1
- package/skills/insight-strace/SKILL.md +1 -1
- package/skills/insight-tshark/SKILL.md +1 -1
- package/skills/insight-windbg/SKILL.md +1 -1
|
@@ -2,6 +2,7 @@ import { mkdir, realpath, readFile, open, link, unlink } from 'node:fs/promises'
|
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { stable, object, fail } from './model.js';
|
|
5
|
+
import { initialProgress, progressPhase, readInitialProgress } from './initial-progress.js';
|
|
5
6
|
export const initialRoot = (workspace, target) => join(workspace.directory, 'ghidra-native', 'initial', target);
|
|
6
7
|
export async function initialIntent(workspace, target) {
|
|
7
8
|
try {
|
|
@@ -15,6 +16,24 @@ export async function initialIntent(workspace, target) {
|
|
|
15
16
|
}
|
|
16
17
|
/** Immutable input/authority binding; private attempts and conditional first creation share it. */
|
|
17
18
|
export async function initializeModel(workspace, target, binding, server, fingerprint) {
|
|
19
|
+
try {
|
|
20
|
+
return await initialize(workspace, target, binding, server, fingerprint);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
try {
|
|
24
|
+
const progress = await readInitialProgress(workspace.directory, target.id);
|
|
25
|
+
// A busy or disconnected caller must not overwrite another live native worker's observation.
|
|
26
|
+
if (progress && progress.state !== 'complete' && (progress.pid === process.pid || progress.workerAlive === false)) {
|
|
27
|
+
const code = String(error.code ?? '');
|
|
28
|
+
const state = code === 'tool.timeout' ? 'timed_out' : code === 'tool.cancelled' ? 'cancelled' : progress.state === 'failed' || progress.state === 'cancelled' ? progress.state : 'unknown';
|
|
29
|
+
await initialProgress(initialRoot(workspace, target.id), target.id, progress.phase, state);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch { /* Preserve the authoritative operation failure when observation storage also fails. */ }
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function initialize(workspace, target, binding, server, fingerprint) {
|
|
18
37
|
const root = initialRoot(workspace, target.id);
|
|
19
38
|
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
20
39
|
if (await realpath(root) !== resolve(root))
|
|
@@ -67,20 +86,22 @@ export async function initializeModel(workspace, target, binding, server, finger
|
|
|
67
86
|
const receipt = object(JSON.parse(await readFile(join(root, 'import.json'), 'utf8'))), name = receipt.snapshot;
|
|
68
87
|
if (typeof name !== 'string' || !/^attempt-[a-f0-9-]{36}\.gzf$/.test(name))
|
|
69
88
|
fail('binding', 'Initial model snapshot receipt is malformed.');
|
|
70
|
-
const snapshot = await workspace.captureSnapshot(join(root, name));
|
|
89
|
+
const snapshot = await progressPhase(root, target.id, 'snapshot-verification', () => workspace.captureSnapshot(join(root, name)));
|
|
71
90
|
if (snapshot.sha256 !== receipt.snapshotSha256)
|
|
72
91
|
fail('binding', 'Initial saved snapshot changed before publication.');
|
|
73
|
-
|
|
92
|
+
const publication = (operation) => progressPhase(root, target.id, 'publication-' + operation, () => server.initialPublication(binding, root, operation));
|
|
93
|
+
let result = await publication('stage');
|
|
74
94
|
if (result.state === 'staged') {
|
|
75
|
-
const proof = await
|
|
95
|
+
const proof = await publication('verify-stage');
|
|
76
96
|
if (proof.state !== 'stage_verified' || proof.version !== 1 || proof.snapshotBuffersVerified !== true)
|
|
77
97
|
fail('unknown', 'Independent initial staging readback did not establish the complete snapshot.');
|
|
78
|
-
result = await
|
|
98
|
+
result = await publication('promote');
|
|
79
99
|
}
|
|
80
100
|
if (result.state !== 'published' || result.version !== 1 || result.snapshotBuffersVerified !== true)
|
|
81
101
|
fail('unknown', 'Initial publication requires reconciliation against its saved snapshot.');
|
|
82
|
-
const verified = await
|
|
102
|
+
const verified = await publication('verify');
|
|
83
103
|
if (verified.state !== 'verified' || verified.version !== 1 || verified.snapshotBuffersVerified !== true)
|
|
84
104
|
fail('unknown', 'Independent initial-model readback did not establish the complete saved buffers.');
|
|
85
105
|
workspace.establishModel(target.id, 1, snapshot);
|
|
106
|
+
await initialProgress(root, target.id, 'complete', 'complete');
|
|
86
107
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Observation only. Completion receipts and the workspace remain the outcome authority. */
|
|
2
|
+
export declare function readInitialProgress(directory: string, target: string): Promise<any>;
|
|
3
|
+
export declare function initialProgress(root: string, target: string, phase: string, state?: string): Promise<void>;
|
|
4
|
+
export declare function progressPhase<T>(root: string, target: string, phase: string, action: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { atomicWrite } from './tool-runtime/store.js';
|
|
4
|
+
/** Observation only. Completion receipts and the workspace remain the outcome authority. */
|
|
5
|
+
export async function readInitialProgress(directory, target) {
|
|
6
|
+
try {
|
|
7
|
+
const value = JSON.parse(await readFile(join(directory, 'ghidra-native', 'initial', target, 'progress.json'), 'utf8'));
|
|
8
|
+
if (value.schema !== 1 || value.target !== target)
|
|
9
|
+
throw Error('Invalid initial progress binding');
|
|
10
|
+
const ageMs = Math.max(0, Date.now() - Date.parse(value.updatedAt));
|
|
11
|
+
let workerAlive;
|
|
12
|
+
if (Number.isSafeInteger(value.pid) && value.pid > 0)
|
|
13
|
+
try {
|
|
14
|
+
process.kill(value.pid, 0);
|
|
15
|
+
workerAlive = true;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === 'ESRCH')
|
|
19
|
+
workerAlive = false;
|
|
20
|
+
}
|
|
21
|
+
return { ...value, operation: 'initial-model', ageMs, elapsedMs: Math.max(0, (value.state === 'running' ? Date.now() : Date.parse(value.updatedAt)) - Date.parse(value.startedAt)),
|
|
22
|
+
...(workerAlive === undefined ? {} : { workerAlive }),
|
|
23
|
+
observation: value.state === 'running' && (workerAlive === false || ageMs > 15000) ? 'stale' : 'current' };
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code === 'ENOENT')
|
|
27
|
+
return null;
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function initialProgress(root, target, phase, state = 'running') {
|
|
32
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
33
|
+
let startedAt = new Date().toISOString();
|
|
34
|
+
try {
|
|
35
|
+
const previous = JSON.parse(await readFile(join(root, 'progress.json'), 'utf8'));
|
|
36
|
+
if (previous.target === target)
|
|
37
|
+
startedAt = previous.startedAt;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code !== 'ENOENT')
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
await atomicWrite(join(root, 'progress.json'), { schema: 1, target, phase, state, pid: process.pid, startedAt, updatedAt: new Date().toISOString(), indeterminate: true });
|
|
44
|
+
}
|
|
45
|
+
export async function progressPhase(root, target, phase, action) {
|
|
46
|
+
await initialProgress(root, target, phase);
|
|
47
|
+
let pending = Promise.resolve(), failure;
|
|
48
|
+
const timer = setInterval(() => { pending = pending.then(() => initialProgress(root, target, phase)).catch(error => { failure = error; }); }, 2000);
|
|
49
|
+
timer.unref();
|
|
50
|
+
let actionFailed = false;
|
|
51
|
+
try {
|
|
52
|
+
return await action();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
actionFailed = true;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
clearInterval(timer);
|
|
60
|
+
await pending;
|
|
61
|
+
if (failure && !actionFailed)
|
|
62
|
+
throw failure;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runTool } from './tool-runtime/run.js';
|
|
2
|
+
import { javaClasspathArguments } from './tool-runtime/java-arguments.js';
|
|
2
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
4
|
import { readFile, writeFile, readdir, mkdir, open, rename, rm, realpath, stat } from 'node:fs/promises';
|
|
4
5
|
import { basename, delimiter, dirname, isAbsolute, join, relative, sep } from 'node:path';
|
|
@@ -158,7 +159,7 @@ export async function nativeHelpers(directory, inputs, c) {
|
|
|
158
159
|
if (c)
|
|
159
160
|
await runTool(c, { tool: 'ghidra', role: 'compile-helpers', installation: inputs.fingerprint, executable, arguments: args, timeoutMs: 60000, maxOutputBytes: 262144 });
|
|
160
161
|
else
|
|
161
|
-
await run(executable, args, { timeout: 60000, maxBuffer: 262144 });
|
|
162
|
+
await run(executable, await javaClasspathArguments(temporary, 'ghidra', executable, args), { timeout: 60000, maxBuffer: 262144 });
|
|
162
163
|
const complete = await open(join(temporary, 'complete'), 'wx', 0o600);
|
|
163
164
|
try {
|
|
164
165
|
await complete.writeFile(key);
|
package/dist/src/server.js
CHANGED
|
@@ -203,6 +203,16 @@ export class GhidraServer {
|
|
|
203
203
|
}
|
|
204
204
|
async initialImport(root) {
|
|
205
205
|
const timeoutMilliseconds = ghidraBudget(this.c.profile.values, 'import');
|
|
206
|
+
const report = async () => {
|
|
207
|
+
try {
|
|
208
|
+
const p = object(JSON.parse(await readFile(join(root, 'progress.json'), 'utf8')));
|
|
209
|
+
const elapsed = Math.round((Date.now() - Date.parse(String(p.startedAt))) / 1000);
|
|
210
|
+
this.c.progress(`Ghidra ${String(p.phase)}: ${elapsed}s${typeof p.total === 'number' ? `; task ${String(p.completed)}/${p.total}` : ''}${p.message ? `; ${String(p.message).replace(/[\r\n\x00-\x1f]/g, ' ')}` : ''}`);
|
|
211
|
+
}
|
|
212
|
+
catch { /* Progress observation never changes the native operation's outcome. */ }
|
|
213
|
+
};
|
|
214
|
+
const timer = setInterval(() => { void report(); }, 5000);
|
|
215
|
+
timer.unref();
|
|
206
216
|
try {
|
|
207
217
|
return await this.run('initial-import', 'InsightInitialImport', [root], {}, undefined, [0], timeoutMilliseconds);
|
|
208
218
|
}
|
|
@@ -211,8 +221,12 @@ export class GhidraServer {
|
|
|
211
221
|
throw new CliError(error.message + ' Initial import remains unproved. Increase profile initialImportSeconds and continue the original scope; incomplete private analysis restarts from the retained input.', { code: error.code, result: { phase: 'initial-import', timeoutMilliseconds, budgetSetting: 'initialImportSeconds', outcome: 'unknown', diagnostics: error.result } });
|
|
212
222
|
throw error;
|
|
213
223
|
}
|
|
224
|
+
finally {
|
|
225
|
+
clearInterval(timer);
|
|
226
|
+
}
|
|
214
227
|
}
|
|
215
228
|
async initialPublication(binding, root, operation = 'stage') {
|
|
229
|
+
this.c.progress('Ghidra initial publication: ' + operation);
|
|
216
230
|
return this.run('initial-publication', 'db.InsightInitialPublication', [root, operation], await this.credentials(binding, operation.startsWith('verify') ? 'reader' : 'publisher'));
|
|
217
231
|
}
|
|
218
232
|
async publish(binding, path, snapshot, expected, request, journal, sourceSha) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import com.google.gson.*;
|
|
2
|
+
import ghidra.util.task.TaskMonitorAdapter;
|
|
3
|
+
import insight.InsightInitialJournal;
|
|
4
|
+
import java.nio.file.*;
|
|
5
|
+
import java.time.Instant;
|
|
6
|
+
import java.util.*;
|
|
7
|
+
|
|
8
|
+
/** A bounded observation channel, never a completion receipt or an overall percentage. */
|
|
9
|
+
class InsightImportProgress extends TaskMonitorAdapter implements AutoCloseable {
|
|
10
|
+
private final Path root;
|
|
11
|
+
private final String target,started=Instant.now().toString();
|
|
12
|
+
private final Timer heartbeat=new Timer("insight-import-progress",true);
|
|
13
|
+
private String phase="starting",state="running";
|
|
14
|
+
private String message="";
|
|
15
|
+
private long completed,total;
|
|
16
|
+
private boolean indeterminate=true;
|
|
17
|
+
private volatile Throwable writeFailure;
|
|
18
|
+
InsightImportProgress(Path root,String target)throws Exception{
|
|
19
|
+
super(true);this.root=root;this.target=target;emit();
|
|
20
|
+
heartbeat.scheduleAtFixedRate(new TimerTask(){public void run(){try{emit();}catch(Throwable failure){writeFailure=failure;}}},2000,2000);
|
|
21
|
+
}
|
|
22
|
+
synchronized void phase(String value)throws Exception{phase=value;setMessage("");initialize(0);setIndeterminate(true);emit();}
|
|
23
|
+
public synchronized void setMessage(String value){message=Objects.toString(value,"");}
|
|
24
|
+
public synchronized String getMessage(){return message;}
|
|
25
|
+
public synchronized void initialize(long maximum){completed=0;total=maximum;indeterminate=maximum<=0;}
|
|
26
|
+
public synchronized void setProgress(long value){completed=value;}
|
|
27
|
+
public synchronized void incrementProgress(long value){completed+=value;}
|
|
28
|
+
public synchronized long getProgress(){return completed;}
|
|
29
|
+
public synchronized void setMaximum(long value){total=value;}
|
|
30
|
+
public synchronized long getMaximum(){return total;}
|
|
31
|
+
public synchronized void setIndeterminate(boolean value){indeterminate=value;}
|
|
32
|
+
public synchronized boolean isIndeterminate(){return indeterminate;}
|
|
33
|
+
synchronized void state(String value)throws Exception{state=value;emit();}
|
|
34
|
+
private synchronized void emit()throws Exception{
|
|
35
|
+
JsonObject p=new JsonObject();p.addProperty("schema",1);p.addProperty("target",target);p.addProperty("phase",phase);p.addProperty("state",state);
|
|
36
|
+
p.addProperty("pid",ProcessHandle.current().pid());p.addProperty("startedAt",started);p.addProperty("updatedAt",Instant.now().toString());
|
|
37
|
+
p.addProperty("indeterminate",isIndeterminate());String message=Objects.toString(getMessage(),"");p.addProperty("message",message.substring(0,Math.min(message.length(),1024)));
|
|
38
|
+
if(!isIndeterminate()&&getMaximum()>0){p.addProperty("completed",getProgress());p.addProperty("total",getMaximum());}
|
|
39
|
+
InsightInitialJournal.write(root.resolve("progress.json"),p);
|
|
40
|
+
}
|
|
41
|
+
public synchronized void close()throws Exception{heartbeat.cancel();if(state.equals("running"))state=isCancelled()?"cancelled":"failed";emit();if(writeFailure!=null)throw new java.io.IOException("Progress persistence failed",writeFailure);}
|
|
42
|
+
}
|
|
@@ -23,6 +23,8 @@ class InsightInitialImport {
|
|
|
23
23
|
JsonObject s=new JsonObject();s.addProperty("intentHash",InsightInitialJournal.hash(root.resolve("intent.json")));
|
|
24
24
|
s.addProperty("phase","importing");s.addProperty("pid",ProcessHandle.current().pid());s.addProperty("snapshot","attempt-"+UUID.randomUUID()+".gzf");
|
|
25
25
|
Path attempt=root.resolve(InsightInitialJournal.text(s,"snapshot")+".json");InsightInitialJournal.write(attempt,s);InsightInitialJournal.write(journal,s);
|
|
26
|
+
try(var progress=new InsightImportProgress(root,InsightInitialJournal.text(p,"target"))){
|
|
27
|
+
progress.phase("runtime-initialization");
|
|
26
28
|
Application.initializeApplication(new GhidraApplicationLayout(),new HeadlessGhidraApplicationConfiguration());
|
|
27
29
|
JsonObject settings=p.getAsJsonObject("settings"),request=new JsonObject();
|
|
28
30
|
request.addProperty("input",input.toString());request.addProperty("output",root.resolve(InsightInitialJournal.text(s,"snapshot")).toString());
|
|
@@ -31,10 +33,14 @@ class InsightInitialImport {
|
|
|
31
33
|
if(settings.has("compilerSpec"))request.add("compiler_spec",settings.get("compilerSpec"));
|
|
32
34
|
if(settings.has("imageBase"))request.add("image_base",settings.get("imageBase"));
|
|
33
35
|
if(settings.has("loaderOptions"))request.add("options",settings.get("loaderOptions"));
|
|
34
|
-
JsonObject metadata
|
|
36
|
+
JsonObject metadata;
|
|
37
|
+
try{metadata=new Gson().toJsonTree(InsightSdkFiles.importProgram(request,progress)).getAsJsonObject();}
|
|
38
|
+
catch(Throwable failure){progress.state(progress.isCancelled()?"cancelled":"failed");throw failure;}
|
|
39
|
+
progress.phase("snapshot-verification");
|
|
35
40
|
Path file=root.resolve(InsightInitialJournal.text(s,"snapshot"));InsightInitialJournal.sync(file);
|
|
36
41
|
s.add("metadata",metadata);s.addProperty("snapshotSha256",InsightInitialJournal.hash(file));s.addProperty("phase","prepared");InsightInitialJournal.write(attempt,s);InsightInitialJournal.write(journal,s);
|
|
37
|
-
InsightInitialJournal.prepared(root);out.println("{\"state\":\"prepared\"}");
|
|
42
|
+
InsightInitialJournal.prepared(root);progress.state("prepared");out.println("{\"state\":\"prepared\"}");
|
|
43
|
+
}
|
|
38
44
|
}
|
|
39
45
|
System.exit(0);
|
|
40
46
|
}catch(Throwable failure){failure.printStackTrace(System.err);out.println("{\"state\":\"failed\"}");System.exit(1);}
|
|
@@ -54,14 +54,17 @@ class InsightSdkFiles {
|
|
|
54
54
|
return Map.of("supported",rows,"all_loader_names",LoaderService.getAllLoaderNames());
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
static
|
|
57
|
+
static void phase(TaskMonitor monitor,String phase)throws Exception{if(monitor instanceof InsightImportProgress progress)progress.phase(phase);}
|
|
58
|
+
static Object importProgram(JsonObject p) throws Exception {return importProgram(p,TaskMonitor.DUMMY);}
|
|
59
|
+
static Object importProgram(JsonObject p,TaskMonitor monitor) throws Exception {
|
|
60
|
+
phase(monitor,"import");
|
|
58
61
|
String selected=text(p,"loader");Object consumer=new Object();Loader loader=null;
|
|
59
62
|
if(selected.isEmpty()){
|
|
60
63
|
Language language=text(p,"language").isEmpty()?null:DefaultLanguageService.getLanguageService().getLanguage(new LanguageID(text(p,"language")));
|
|
61
64
|
CompilerSpec compiler=language==null?null:text(p,"compiler_spec").isEmpty()?language.getDefaultCompilerSpec():language.getCompilerSpecByID(new CompilerSpecID(text(p,"compiler_spec")));
|
|
62
|
-
var loaded=language==null?AutoImporter.importByUsingBestGuess(Path.of(text(p,"input")).toFile(),null,"/",consumer,new MessageLog(),
|
|
63
|
-
AutoImporter.importByLookingForLcs(Path.of(text(p,"input")).toFile(),null,"/",language,compiler,consumer,new MessageLog(),
|
|
64
|
-
try{if(loaded==null||loaded.size()!=1)throw new IllegalArgumentException("Expected one imported program");return saveImport(loaded.getPrimaryDomainObject(),p,"automatic");}
|
|
65
|
+
var loaded=language==null?AutoImporter.importByUsingBestGuess(Path.of(text(p,"input")).toFile(),null,"/",consumer,new MessageLog(),monitor):
|
|
66
|
+
AutoImporter.importByLookingForLcs(Path.of(text(p,"input")).toFile(),null,"/",language,compiler,consumer,new MessageLog(),monitor);
|
|
67
|
+
try{if(loaded==null||loaded.size()!=1)throw new IllegalArgumentException("Expected one imported program");return saveImport(loaded.getPrimaryDomainObject(),p,"automatic",monitor);}
|
|
65
68
|
finally{if(loaded!=null)loaded.release(consumer);}
|
|
66
69
|
}
|
|
67
70
|
for(Loader candidate:ClassSearcher.getInstances(Loader.class))if(candidate.getClass().getName().equals(selected)||candidate.getClass().getSimpleName().equals(selected)||candidate.getName().equals(selected)){
|
|
@@ -77,24 +80,24 @@ class InsightSdkFiles {
|
|
|
77
80
|
Language effectiveLanguage=language;if(effectiveLanguage==null&&selectedSpec.getLanguageCompilerSpec()!=null)effectiveLanguage=DefaultLanguageService.getLanguageService().getLanguage(selectedSpec.getLanguageCompilerSpec().languageID);
|
|
78
81
|
var options=configured(loader.getDefaultOptions(provider,selectedSpec,null,false,false),optionValues(p),effectiveLanguage==null?null:effectiveLanguage.getAddressFactory());
|
|
79
82
|
String error=loader.validateOptions(provider,selectedSpec,options,null);if(error!=null)throw new IllegalArgumentException("Native loader rejected options: "+error);
|
|
80
|
-
var settings=new Loader.ImporterSettings(provider,"OwnedInput",null,"/",false,selectedSpec,options,consumer,new MessageLog(),
|
|
83
|
+
var settings=new Loader.ImporterSettings(provider,"OwnedInput",null,"/",false,selectedSpec,options,consumer,new MessageLog(),monitor);
|
|
81
84
|
var result=loader.load(settings);
|
|
82
85
|
if(result==null)throw new IllegalStateException("Native loader did not return a program");
|
|
83
86
|
try {
|
|
84
87
|
if(result.size()!=1||!(result.getPrimaryDomainObject() instanceof Program))throw new IllegalArgumentException("Loader must produce one program per explicit target");
|
|
85
|
-
return saveImport((Program)result.getPrimaryDomainObject(),p,loader.getClass().getName());
|
|
88
|
+
return saveImport((Program)result.getPrimaryDomainObject(),p,loader.getClass().getName(),monitor);
|
|
86
89
|
} finally {result.release(consumer);}
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
|
-
static Object saveImport(Program program,JsonObject p,String loader)throws Exception{
|
|
92
|
+
static Object saveImport(Program program,JsonObject p,String loader,TaskMonitor monitor)throws Exception{
|
|
90
93
|
boolean analyze=p.has("auto_analyze")&&p.get("auto_analyze").getAsBoolean();
|
|
91
|
-
if(analyze){ghidra.app.script.GhidraScriptUtil.acquireBundleHostReference();int tx=program.startTransaction("Initial analysis");boolean success=false;
|
|
92
|
-
try{AutoAnalysisManager analysis=AutoAnalysisManager.getAnalysisManager(program);analysis.initializeOptions();analysis.reAnalyzeAll(null);analysis.startAnalysis(
|
|
94
|
+
if(analyze){phase(monitor,"analysis");ghidra.app.script.GhidraScriptUtil.acquireBundleHostReference();int tx=program.startTransaction("Initial analysis");boolean success=false;
|
|
95
|
+
try{AutoAnalysisManager analysis=AutoAnalysisManager.getAnalysisManager(program);analysis.initializeOptions();analysis.reAnalyzeAll(null);analysis.startAnalysis(monitor);monitor.checkCancelled();success=true;}
|
|
93
96
|
finally{program.endTransaction(tx,success);ghidra.app.script.GhidraScriptUtil.releaseBundleHostReference();}}
|
|
94
97
|
if(p.has("image_base")){int tx=program.startTransaction("Initial image base");boolean success=false;
|
|
95
98
|
try{program.setImageBase(program.getAddressFactory().getDefaultAddressSpace().getAddress(new BigInteger(text(p,"image_base"),16).longValue()),true);success=true;}
|
|
96
99
|
finally{program.endTransaction(tx,success);}}
|
|
97
|
-
program.saveToPackedFile(Path.of(text(p,"output")).toFile(),
|
|
100
|
+
phase(monitor,"save");program.saveToPackedFile(Path.of(text(p,"output")).toFile(),monitor);monitor.checkCancelled();
|
|
98
101
|
return Map.of("state","imported","loader",loader,"language",program.getLanguageID().toString(),"base_address",program.getImageBase().toString(),
|
|
99
102
|
"compiler",program.getCompilerSpec().getCompilerSpecID().toString(),"sha256",program.getExecutableSHA256(),"auto_analyzed",analyze);
|
|
100
103
|
}
|
package/references/setup.md
CHANGED
|
@@ -69,3 +69,30 @@ After an import timeout, wait for the original worker to end, raise its budget a
|
|
|
69
69
|
the original scope. Incomplete private analysis restarts; a completed receipt is reused.
|
|
70
70
|
A save timeout reports `phase: save`, the effective budget and `saveSeconds`. Inspect
|
|
71
71
|
`sessions status`; a native log or broken response stream cannot prove a saved snapshot.
|
|
72
|
+
|
|
73
|
+
## Initial import and progress
|
|
74
|
+
|
|
75
|
+
New targets import **without automatic analysis**. Reuse existing selectors/projects before
|
|
76
|
+
opening another copy; `ghidra open --session <session> --target <target>` preserves its bound
|
|
77
|
+
load settings, including older analysis choices. `--no-analysis` makes the fast choice explicit;
|
|
78
|
+
`--analysis` opts into a full analysis only during the first import of a new target.
|
|
79
|
+
After a fast import, use scoped `ghidra analysis run` to process pending analysis, or
|
|
80
|
+
`ghidra analysis reanalyze` to deliberately queue full reanalysis. These are saved private
|
|
81
|
+
mutations; merge deliberately. For a narrow question prefer bounded function disassembly,
|
|
82
|
+
function creation and decompilation first; inspect help for the exact operation.
|
|
83
|
+
|
|
84
|
+
For long first opens, `ghidra progress --session <session> --target <target> --json`
|
|
85
|
+
reads retained progress without starting Java or repeating the operation. It reports the
|
|
86
|
+
current stage, elapsed time, heartbeat age and native task message/counters when available;
|
|
87
|
+
those counters belong to the current task, not the whole import. Human output also reports
|
|
88
|
+
progress through Factory; `--json` keeps one final JSON result on stdout and stays quiet.
|
|
89
|
+
If the execution host yields, keep its same running execution and inspect progress every
|
|
90
|
+
5-10 seconds when useful; relay stage changes or meaningful stalls, and do other independent
|
|
91
|
+
work. Do not silently extend waits to minutes or start a second import. Until selectors are
|
|
92
|
+
returned, use the existing `sessions list/status` and `tools processes` inventory to locate the
|
|
93
|
+
reserved scope. A stale heartbeat, dead worker or unknown result is not completion: inspect
|
|
94
|
+
`sessions status` and recovery before continuing. Pre-upgrade workers have no progress record;
|
|
95
|
+
null progress means unavailable, never success. Progress is observational, not a completion receipt. This command tracks first open only;
|
|
96
|
+
its old complete record does not describe later `analysis run`/`reanalyze` operations.
|
|
97
|
+
`operation: initial-model` completes after verified shared-model publication; the private
|
|
98
|
+
session context can still be opening. Inspect `sessions status` for context readiness.
|
package/references/storage.md
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
# Analysis storage and migration
|
|
2
2
|
|
|
3
3
|
`storage show --json` reports effective profile storage, availability, active file/byte usage,
|
|
4
|
-
exact included trees
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
to
|
|
4
|
+
exact included trees, retained migration and installation default base. Before selecting a
|
|
5
|
+
base, storage starts in Factory profile AppData. `configure` and `migrate` select that base
|
|
6
|
+
for subsequent new/empty profiles as well as moving the current profile. Add `--profile-only`
|
|
7
|
+
to keep the installation default unchanged. The effective root appends `insight-cli/<profile>`.
|
|
8
|
+
Initialized profiles keep their existing locations when the default changes; profiles with
|
|
9
|
+
ordinary AppData analysis trees stay there until explicitly migrated.
|
|
10
|
+
|
|
11
|
+
The per-profile non-secret `analysisDirectory` setting remains in Factory's profile. The
|
|
12
|
+
installation default is bound storage-control metadata beside Factory's profile store, paired
|
|
13
|
+
with ownership metadata on the chosen base. It is not an environment or checkout setting.
|
|
14
|
+
Missing/replaced storage fails before creating analysis data; there is no system-disk fallback.
|
|
15
|
+
New profiles initialize on their first ordinary command, durably bind their own empty directory,
|
|
16
|
+
and keep permanent compatibility paths. `storage show` remains passive.
|
|
8
17
|
|
|
9
18
|
```sh
|
|
10
19
|
insight-cli permissions grant Update
|
|
@@ -29,7 +38,20 @@ user-selected outputs outside these trees keep their locations.
|
|
|
29
38
|
|
|
30
39
|
Factory keeps profiles, permissions, IPC election/socket, migration-control metadata and
|
|
31
40
|
application logs in current-user AppData. Secrets stay in the OS keyring. Credential/profile
|
|
32
|
-
identities do not change. Updating/installing npm
|
|
41
|
+
identities do not change. Updating/installing npm preserves existing profile locations and does not select a disk on its own.
|
|
42
|
+
|
|
43
|
+
## Select a default after an earlier migration
|
|
44
|
+
|
|
45
|
+
For installations migrated with an older CLI, select their existing mounted base once:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
insight-cli storage default --directory "/mnt/analysis volume" --json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This Update operation changes the installation default without moving existing data. Future
|
|
52
|
+
profiles inherit it without repeating configuration. The base cannot be inside a profile data
|
|
53
|
+
root or overlap Factory control storage. A base owned by another installation is rejected.
|
|
54
|
+
A missing volume or replaced base/ownership marker is never adopted automatically.
|
|
33
55
|
|
|
34
56
|
## Move an existing profile
|
|
35
57
|
|
|
@@ -44,7 +66,7 @@ insight-cli storage migrate --directory "/mnt/analysis volume" --json
|
|
|
44
66
|
insight-cli storage show --json
|
|
45
67
|
```
|
|
46
68
|
|
|
47
|
-
Dry-run checks separation, destination contents, access, conservative capacity and quiescence
|
|
69
|
+
Dry-run reports whether it would select the installation default and checks separation, destination contents, access, conservative capacity and quiescence
|
|
48
70
|
without reserving/copying a migration. Source/destination/configuration roots cannot be equal
|
|
49
71
|
or nested. Destination symlink roots, escaping source links, special files, unresolved links
|
|
50
72
|
and inventories above one million entries or 128 directory levels are rejected. Internal
|
|
@@ -72,7 +94,9 @@ file, rechecks capacity and finishes the retained transaction. Source and destin
|
|
|
72
94
|
anchor identities are bound durably. Missing/replaced volumes fail without creating a system-disk
|
|
73
95
|
fallback; mount the original volume before resuming.
|
|
74
96
|
|
|
75
|
-
Rollback restores retained source and the prior Factory setting.
|
|
97
|
+
Rollback restores retained source and the prior Factory setting. The installation default stays
|
|
98
|
+
selected because subsequent profiles may already use it; a resumed older migration cannot
|
|
99
|
+
overwrite a newer default selection. After activation it requires
|
|
76
100
|
an unchanged destination and a verified retained source. Native reopening or later findings can
|
|
77
101
|
make rollback unsafe, in which case it refuses and preserves both copies. Use forward migration
|
|
78
102
|
to another empty base after using the new location. A rolled-back destination remains for manual
|
|
@@ -97,3 +121,7 @@ before starting another. This release provides no nonempty-destination adoption,
|
|
|
97
121
|
synchronization, live-DB snapshotting or network-filesystem guarantees. POSIX directory fsync
|
|
98
122
|
is used. Windows has file fsync and rename/junction semantics; Node cannot fsync directories
|
|
99
123
|
there, and internal symlinks can require OS privileges.
|
|
124
|
+
|
|
125
|
+
Empty-profile initialization resumes automatically on its next command after interruption. Its
|
|
126
|
+
retained source contains no analysis entries, so the CLI cleans only that owned empty backup
|
|
127
|
+
automatically. Migration of existing data always retains its source for explicit cleanup.
|
|
@@ -3,7 +3,7 @@ name: insight-artifacts
|
|
|
3
3
|
description: Retain a completed reverse-engineering evidence file or directory as an ordinary checkpoint using insight-cli artifacts save. Use after Ghidra or any external tool; preserve existing project formats and source of truth.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Insight Artifacts 0.3.
|
|
6
|
+
# Insight Artifacts 0.3.2
|
|
7
7
|
|
|
8
8
|
If local help/status reports AppData or IPC access denied, retry that **read-only probe**
|
|
9
9
|
with the agent host's normal permission escalation, then use that route for later authorized
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
# Analysis storage and migration
|
|
2
2
|
|
|
3
3
|
`storage show --json` reports effective profile storage, availability, active file/byte usage,
|
|
4
|
-
exact included trees
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
to
|
|
4
|
+
exact included trees, retained migration and installation default base. Before selecting a
|
|
5
|
+
base, storage starts in Factory profile AppData. `configure` and `migrate` select that base
|
|
6
|
+
for subsequent new/empty profiles as well as moving the current profile. Add `--profile-only`
|
|
7
|
+
to keep the installation default unchanged. The effective root appends `insight-cli/<profile>`.
|
|
8
|
+
Initialized profiles keep their existing locations when the default changes; profiles with
|
|
9
|
+
ordinary AppData analysis trees stay there until explicitly migrated.
|
|
10
|
+
|
|
11
|
+
The per-profile non-secret `analysisDirectory` setting remains in Factory's profile. The
|
|
12
|
+
installation default is bound storage-control metadata beside Factory's profile store, paired
|
|
13
|
+
with ownership metadata on the chosen base. It is not an environment or checkout setting.
|
|
14
|
+
Missing/replaced storage fails before creating analysis data; there is no system-disk fallback.
|
|
15
|
+
New profiles initialize on their first ordinary command, durably bind their own empty directory,
|
|
16
|
+
and keep permanent compatibility paths. `storage show` remains passive.
|
|
8
17
|
|
|
9
18
|
```sh
|
|
10
19
|
insight-cli permissions grant Update
|
|
@@ -29,7 +38,20 @@ user-selected outputs outside these trees keep their locations.
|
|
|
29
38
|
|
|
30
39
|
Factory keeps profiles, permissions, IPC election/socket, migration-control metadata and
|
|
31
40
|
application logs in current-user AppData. Secrets stay in the OS keyring. Credential/profile
|
|
32
|
-
identities do not change. Updating/installing npm
|
|
41
|
+
identities do not change. Updating/installing npm preserves existing profile locations and does not select a disk on its own.
|
|
42
|
+
|
|
43
|
+
## Select a default after an earlier migration
|
|
44
|
+
|
|
45
|
+
For installations migrated with an older CLI, select their existing mounted base once:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
insight-cli storage default --directory "/mnt/analysis volume" --json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This Update operation changes the installation default without moving existing data. Future
|
|
52
|
+
profiles inherit it without repeating configuration. The base cannot be inside a profile data
|
|
53
|
+
root or overlap Factory control storage. A base owned by another installation is rejected.
|
|
54
|
+
A missing volume or replaced base/ownership marker is never adopted automatically.
|
|
33
55
|
|
|
34
56
|
## Move an existing profile
|
|
35
57
|
|
|
@@ -44,7 +66,7 @@ insight-cli storage migrate --directory "/mnt/analysis volume" --json
|
|
|
44
66
|
insight-cli storage show --json
|
|
45
67
|
```
|
|
46
68
|
|
|
47
|
-
Dry-run checks separation, destination contents, access, conservative capacity and quiescence
|
|
69
|
+
Dry-run reports whether it would select the installation default and checks separation, destination contents, access, conservative capacity and quiescence
|
|
48
70
|
without reserving/copying a migration. Source/destination/configuration roots cannot be equal
|
|
49
71
|
or nested. Destination symlink roots, escaping source links, special files, unresolved links
|
|
50
72
|
and inventories above one million entries or 128 directory levels are rejected. Internal
|
|
@@ -72,7 +94,9 @@ file, rechecks capacity and finishes the retained transaction. Source and destin
|
|
|
72
94
|
anchor identities are bound durably. Missing/replaced volumes fail without creating a system-disk
|
|
73
95
|
fallback; mount the original volume before resuming.
|
|
74
96
|
|
|
75
|
-
Rollback restores retained source and the prior Factory setting.
|
|
97
|
+
Rollback restores retained source and the prior Factory setting. The installation default stays
|
|
98
|
+
selected because subsequent profiles may already use it; a resumed older migration cannot
|
|
99
|
+
overwrite a newer default selection. After activation it requires
|
|
76
100
|
an unchanged destination and a verified retained source. Native reopening or later findings can
|
|
77
101
|
make rollback unsafe, in which case it refuses and preserves both copies. Use forward migration
|
|
78
102
|
to another empty base after using the new location. A rolled-back destination remains for manual
|
|
@@ -97,3 +121,7 @@ before starting another. This release provides no nonempty-destination adoption,
|
|
|
97
121
|
synchronization, live-DB snapshotting or network-filesystem guarantees. POSIX directory fsync
|
|
98
122
|
is used. Windows has file fsync and rename/junction semantics; Node cannot fsync directories
|
|
99
123
|
there, and internal symlinks can require OS privileges.
|
|
124
|
+
|
|
125
|
+
Empty-profile initialization resumes automatically on its next command after interruption. Its
|
|
126
|
+
retained source contains no analysis entries, so the CLI cleans only that owned empty backup
|
|
127
|
+
automatically. Migration of existing data always retains its source for explicit cleanup.
|
|
@@ -3,7 +3,7 @@ name: insight-binary
|
|
|
3
3
|
description: Query structured PE, ELF and thin Mach-O headers, sections, imports and exports, or make an explicitly guarded new PE subsystem copy through insight-cli binary. Use for scoped binary structure, linkage and copy-only header edits without target execution.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Insight Binary 0.3.
|
|
6
|
+
# Insight Binary 0.3.2
|
|
7
7
|
|
|
8
8
|
If local help/status reports AppData or IPC access denied, retry that **read-only probe**
|
|
9
9
|
with the agent host's normal permission escalation, then use that route for later authorized
|
|
@@ -3,7 +3,7 @@ name: insight-capture
|
|
|
3
3
|
description: Capture one explicitly authorized native program run, exact stdout/stderr and declared file snapshots through insight-cli capture, then inspect or compare retained reports without replay. Use for controlled input/output research, preserving original files and interrupted or unknown outcomes.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Insight Capture 0.3.
|
|
6
|
+
# Insight Capture 0.3.2
|
|
7
7
|
|
|
8
8
|
If local help/status reports AppData or IPC access denied, retry that **read-only probe**
|
|
9
9
|
with the agent host's normal permission escalation, then use that route for later authorized
|
|
@@ -3,7 +3,7 @@ name: insight-cli
|
|
|
3
3
|
description: Choose and prepare reverse-engineering capabilities from the insight-cli toolbox for binary, managed-code and runtime investigations. Use to discover the appropriate tool or its packaged skill before installing native runtimes.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# insight-cli 0.3.
|
|
6
|
+
# insight-cli 0.3.2
|
|
7
7
|
|
|
8
8
|
If local help/status reports AppData or IPC access denied, retry that **read-only probe**
|
|
9
9
|
with the agent host's normal permission escalation, then use that route for later authorized
|
|
@@ -39,6 +39,11 @@ installing npm binaries does not register them in an agent host. See
|
|
|
39
39
|
Inspect `storage show --json` before relocating a profile. Use the supported
|
|
40
40
|
[storage workflow](references/storage.md), preserving unknown operations and native folders.
|
|
41
41
|
|
|
42
|
+
|
|
43
|
+
New targets import without full auto-analysis; `--analysis` opts in at first import.
|
|
44
|
+
Reuse existing session/target selectors before opening another copy. Read [setup](references/setup.md)
|
|
45
|
+
for explicit later analysis and passive first-open progress (`ghidra progress`).
|
|
46
|
+
|
|
42
47
|
Use generated help as the syntax authority. Catalog metadata describes shipped capabilities;
|
|
43
48
|
profile runtime readiness comes from the selected tool's `status`. Unavailable profile/IPC
|
|
44
49
|
access means execution readiness is unknown. Preserve work and diagnose access before trying
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Exchange private documentation with an archive
|
|
2
|
+
|
|
3
|
+
Archive exchange is disabled until the selected Factory profile has an explicit `archiveUrl`.
|
|
4
|
+
Use HTTPS for a remote service; local HTTP is supported for local services. URLs cannot contain
|
|
5
|
+
credentials, query parameters or fragments. An optional bearer token is stored with
|
|
6
|
+
`auth login --role archive --password-stdin`; it is a Factory secret and is never passed to
|
|
7
|
+
Ghidra or the debuggee. `auth logout` clears this token as well as external server credentials.
|
|
8
|
+
|
|
9
|
+
Use `ghidra archive ingest-function --address <entry>` or `ingest-program`, with the usual
|
|
10
|
+
`--session` and `--target`. Both export the selected private view. `--version-label` supplies
|
|
11
|
+
the archive version; its default is `unknown`. Internal project directories are not used as
|
|
12
|
+
version labels. Bulk selection skips default function names unless `--include-default-named`
|
|
13
|
+
is set; `--limit` bounds visited functions. Zero selects all, subject to the documented bound.
|
|
14
|
+
`--dry-run` validates payloads and returns would-be counts without a network request.
|
|
15
|
+
|
|
16
|
+
The service contract is `POST <archiveUrl>/v1/doc_archive/upsert` with JSON and an optional
|
|
17
|
+
`Authorization: Bearer ...` header. The payload matches the pinned GhidraMCP builder: binary
|
|
18
|
+
name, explicit version, function address, run/tool identity, name/prototype, calling convention,
|
|
19
|
+
flags, matching hash/shape metadata, tags, named locals and instruction comments when present.
|
|
20
|
+
The binary name is the logical target name. Each successful JSON response must contain a
|
|
21
|
+
boolean `created` and a nonnegative integer `conflicts_enqueued`. Redirects are refused.
|
|
22
|
+
|
|
23
|
+
Results retain created, updated, conflict, skipped and error counts. Archive conflict counts
|
|
24
|
+
describe the external service's decisions; they do not resolve program merge conflicts.
|
|
25
|
+
Use an optional stable `--request-id` when a response might be lost. A repeated ID returns
|
|
26
|
+
the saved successful result or the retained uncertain outcome, without sending again.
|
|
27
|
+
Each outbound payload identity and the source snapshot are retained before dispatch. Unknown
|
|
28
|
+
external outcomes stay observable and do not block unrelated private research. A new request ID
|
|
29
|
+
expresses another explicit submission; the facade does not assume the service can deduplicate it.
|
|
30
|
+
|
|
31
|
+
Archive requests do not publish the program or alter another session. Developing or deploying
|
|
32
|
+
the external archive service is outside this package; acceptance uses an owned contract fixture.
|