@xaccefy/pi-casefile 0.9.4 → 0.10.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/README.md +31 -67
- package/package.json +13 -16
- package/src/confirmation.ts +729 -0
- package/src/evidence.ts +4 -4
- package/src/index.ts +189 -293
- package/src/ledger-internal.ts +321 -0
- package/src/ledger.ts +75 -1142
- package/src/oob-oracle.ts +279 -0
- package/src/poc-runner.ts +10 -0
- package/src/scratchpad.ts +5 -6
- package/src/workflow.ts +46 -327
- package/skills/casefile/SKILL.md +0 -44
- package/src/ledger-worker-entry.ts +0 -35
- package/src/ledger-worker.ts +0 -77
- package/src/pipeline-submit.ts +0 -797
package/src/workflow.ts
CHANGED
|
@@ -1,374 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Recon workflow injected once per session into the agent's system prompt.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* wording is compressed, nothing is dropped.
|
|
4
|
+
* Scope: RECON only — attack-surface mapping and intel collection, fanned out
|
|
5
|
+
* to recon subagents. Everything after recon (hunting the leads, PoC work, the
|
|
6
|
+
* confirmation gate, reporting) is the main agent's own inline job through the
|
|
7
|
+
* casefile tools; this file does NOT orchestrate a hunt/trace/validate pipeline.
|
|
9
8
|
*/
|
|
10
|
-
import { KILL_REASON_VALUES } from "./ledger.ts";
|
|
11
|
-
|
|
12
|
-
const KILL_REASONS_TEXT = KILL_REASON_VALUES.join(" / ");
|
|
13
|
-
|
|
14
|
-
/** Case-lifecycle diagram, shared by the FULL and LITE workflows. */
|
|
15
|
-
const LIFECYCLE_DIAGRAM = `
|
|
16
|
-
\`\`\`
|
|
17
|
-
+--- KILLED (dead end, documented why)
|
|
18
|
-
|
|
|
19
|
-
RECON -> HYPOTHESIS --+
|
|
20
|
-
|
|
|
21
|
-
+--> INVESTIGATING --> CONFIRMED --> REPORTED
|
|
22
|
-
| ^ |
|
|
23
|
-
| | chain/primitive |
|
|
24
|
-
| +-----------------+
|
|
25
|
-
|
|
|
26
|
-
+--> KILLED (insufficient impact, duplicate, etc.)
|
|
27
|
-
\`\`\``;
|
|
28
9
|
|
|
29
10
|
/**
|
|
30
11
|
* Subagent-dispatch conventions per host. Pi (pi-subagents extension) dispatches
|
|
31
|
-
* through
|
|
32
|
-
* dispatches through its native
|
|
33
|
-
*
|
|
12
|
+
* through `subagent({ workflowScript: runs.run(...) })`; OMP (fork, @oh-my-pi)
|
|
13
|
+
* dispatches through its native `task` tool with a tasks array. The recon body
|
|
14
|
+
* is identical — only the launch mechanics differ.
|
|
34
15
|
*/
|
|
35
16
|
type DispatchSpec = {
|
|
36
|
-
/**
|
|
17
|
+
/** How to launch a recon subagent on this host. */
|
|
37
18
|
reference: string;
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
/**
|
|
19
|
+
/** The parallel recon fan-out line. */
|
|
20
|
+
fanout: string;
|
|
21
|
+
/** Retry discipline when a recon subagent fails. */
|
|
41
22
|
crash: string;
|
|
42
|
-
/** Skeptic dispatch snippet (follows "dispatch it before main-agent validation with "). */
|
|
43
|
-
skeptic: string;
|
|
44
23
|
};
|
|
45
24
|
|
|
46
25
|
const PI_DISPATCH: DispatchSpec = {
|
|
47
26
|
reference:
|
|
48
|
-
"**Subagent dispatch:**
|
|
49
|
-
|
|
50
|
-
"
|
|
27
|
+
"**Subagent dispatch:** each recon launch uses `subagent({ workflowScript: \"return runs.run('recon-<area>-1', { agent: 'recon', task: '...' })\", context: 'fresh', async: true })`. Fan out a round with ONE workflowScript using `return runs.all([...])`, at most 3 recon tasks. Stable keys include run, recon area, and attempt.",
|
|
28
|
+
fanout:
|
|
29
|
+
"Launch ONE async workflowScript whose `runs.all([...])` dispatches at most 3 recon subagents, one per intel area (surface map, client-side / JS mining, passive intel + fingerprint/CVE).",
|
|
51
30
|
crash:
|
|
52
|
-
"**Subagent failure handling:** a crash, timeout, hung run,
|
|
53
|
-
skeptic:
|
|
54
|
-
"`subagent({ workflowScript: \"return runs.run('skeptic-<case>-1', { agent: 'skeptic', task: '...' })\", context: 'fresh', async: true })`",
|
|
31
|
+
"**Subagent failure handling:** a crash, timeout, hung run, or unparseable output is a RETRY, not a result. Re-launch the same recon area with a new attempt key and a stronger model; on repeat failure, record `blocked: recon <area> failed` on the recon case and continue with the areas that returned.",
|
|
55
32
|
};
|
|
56
33
|
|
|
57
34
|
const OMP_DISPATCH: DispatchSpec = {
|
|
58
35
|
reference:
|
|
59
|
-
"**Subagent dispatch (OMP):**
|
|
60
|
-
|
|
61
|
-
"
|
|
36
|
+
"**Subagent dispatch (OMP):** each recon launch uses `task({ context: 'fresh', tasks: [{ name: 'recon-<area>-1', agent: 'recon', task: '...' }] })`. Fan out a round with ONE `task` call whose `tasks` array carries at most 3 recon tasks. Stable names include run, recon area, and attempt. Results deliver automatically; steer with `hub`.",
|
|
37
|
+
fanout:
|
|
38
|
+
"Launch ONE async `task` call whose `tasks` array dispatches at most 3 recon subagents, one per intel area (surface map, client-side / JS mining, passive intel + fingerprint/CVE).",
|
|
62
39
|
crash:
|
|
63
|
-
"**Subagent failure handling:** a failed or hung task, timeout,
|
|
64
|
-
skeptic:
|
|
65
|
-
"`task({ context: 'fresh', tasks: [{ name: 'skeptic-<case>-1', agent: 'skeptic', task: '...' }] })`",
|
|
40
|
+
"**Subagent failure handling:** a failed or hung task, timeout, or unparseable output is a RETRY, not a result. Re-dispatch the same recon area with a new attempt name and a stronger model; on repeat failure, record `blocked: recon <area> failed` on the recon case and continue with the areas that returned.",
|
|
66
41
|
};
|
|
67
42
|
|
|
68
|
-
/** Build the
|
|
69
|
-
function
|
|
43
|
+
/** Build the recon workflow for a host's dispatch convention. */
|
|
44
|
+
function buildReconWorkflow(d: DispatchSpec): string {
|
|
70
45
|
return `
|
|
71
|
-
#
|
|
46
|
+
# Recon Workflow (Attack-Surface Mapping & Intel Collection)
|
|
72
47
|
|
|
73
|
-
Think like a real external attacker, not a code reviewer.
|
|
48
|
+
Think like a real external attacker, not a code reviewer. This workflow covers ONE job: turn a target into a rich attack-surface map and intel picture that later hunting can act on. Recon fans out to subagents; hunting the leads, validation, the PoC confirmation gate, and reporting are yours to run afterward, inline, through the casefile tools.
|
|
74
49
|
|
|
75
50
|
## Tool Reference
|
|
76
51
|
|
|
77
|
-
**Casefile (state tracking):** CaseAdd, CaseUpdate, CaseGet, CaseList, CaseSearch, CaseLink, CaseUnlink, CaseContext,
|
|
52
|
+
**Casefile (state tracking):** CaseAdd, CaseUpdate, CaseGet, CaseList, CaseSearch, CaseLink, CaseUnlink, CaseContext, EvidenceAdd
|
|
78
53
|
|
|
79
|
-
**Scratchpad (
|
|
54
|
+
**Scratchpad (recon artifacts):** ScratchpadInit, ScratchpadWrite, ScratchpadRead, ScratchpadResume, ScratchpadCheckpoint, ScratchpadPhaseDone, ScratchpadClear
|
|
80
55
|
|
|
81
|
-
**Web lookup
|
|
56
|
+
**Web lookup / intel:** web_search, web_fetch, exploit_search, context7, deepwiki, http_request
|
|
82
57
|
|
|
83
58
|
${d.reference}
|
|
84
59
|
|
|
85
|
-
**
|
|
86
|
-
|
|
87
|
-
## Dispatch Discipline (fewer calls, batched, verifier-in-the-loop)
|
|
88
|
-
|
|
89
|
-
This pipeline is SEQUENTIAL-dependent: each stage consumes the previous stage's output, so only HUNT genuinely fans out. Do NOT scatter one async subagent call per finding — that pays full coordination cost for no parallel payoff and turns you into a message router. Two rules keep it cheap:
|
|
90
|
-
|
|
91
|
-
1. **Two dispatch points, each ONE batched call.** (a) **HUNT** — one call whose \`runs.all\`/\`tasks\` carries ≤3 batched auditors (related classes grouped by surface/family). (b) **TRACE+SKEPTIC** — one call carrying a trace task per prioritized finding, plus a skeptic task for each \`confidence: high\` finding; batch the whole round in a single dispatch, never one dispatch per finding. RECON, VALIDATE/PoC, ConfirmFinding, CHAIN, and REPORT stay INLINE with you.
|
|
92
|
-
2. **Barrier, then submit the whole batch in one pass.** Let the batched call return ALL of its results, then \`PipelineSubmit\` each output back-to-back before choosing the next stage. Do not interleave fresh dispatches with the delivery of a prior batch. A crash / timeout / unparseable / schema-invalid result for one item is a RETRY for THAT item in the next batch — never a verdict, never a reason to drop the stage.
|
|
93
|
-
|
|
94
|
-
Every stage boundary is a \`PipelineSubmit\` gate (schema + pre-filter, in code): nothing advances on prose. This is the verifier-in-the-loop — the same principle the machine PoC gate applies at CONFIRMED, applied at every stage transition.
|
|
95
|
-
|
|
96
|
-
## Stage Machine (run in order — you are the coordinator)
|
|
60
|
+
**Delegation boundary:** recon/intel gathering runs as subagents (the \`recon\` agent). You, the main coordinator, own scoping, consolidating recon results into the attack-surface map, filing hypotheses, and every state decision. Everything past recon is your inline job — there is no hunt/trace/validate subagent pipeline.
|
|
97
61
|
|
|
98
|
-
|
|
62
|
+
## Recon — what to gather
|
|
99
63
|
|
|
100
|
-
|
|
64
|
+
Live web target, CTF, or bounty box: gather high-signal intel and turn it into the map hunting will use — entry-point inventory (URL, method, params, auth state), attacker model, auth/role boundaries, trust boundaries, likely vuln-class batches, and known gaps. Aim for the richest useful map, not the largest raw pile.
|
|
101
65
|
|
|
102
|
-
|
|
103
|
-
- **Client-side
|
|
104
|
-
- **
|
|
105
|
-
- **Fingerprint for decisions** — stack + version confidence
|
|
106
|
-
- **Bank useful leads** — write the entry-point map, selected HUNT class batches, and open gaps to the scratchpad; file high-value leaks (source map, origin IP, exposed schema, leaked creds) as \`EvidenceAdd role=observation\`. Tactical commands: web-pentest skill §2.
|
|
66
|
+
- **Surface mapping** — routes, endpoints, parameters, auth models; \`robots.txt\`, \`sitemap.xml\`, \`/.well-known/\`, OpenAPI/Swagger, GraphQL introspection.
|
|
67
|
+
- **Client-side mining** — pull JS bundles / source maps for SPA or API-heavy apps; bank discovered endpoints, params, and secrets as leads.
|
|
68
|
+
- **Passive intel** — public metadata, schemas, passive archives, exposed backup/VCS checks, when scope allows and the result can change target, auth, or class selection.
|
|
69
|
+
- **Fingerprint for decisions** — stack + version confidence drives \`exploit_search\` and class selection; record uncertainty instead of guessing.
|
|
107
70
|
|
|
108
|
-
|
|
71
|
+
## Dispatch discipline (recon fans out; consolidation is yours)
|
|
109
72
|
|
|
110
|
-
|
|
73
|
+
Scope the target first, then fan recon out ONCE per round: ${d.fanout} Let the batched call return ALL results, then consolidate them yourself. Do NOT scatter one subagent per URL — group by intel area. Re-enter recon only when a gap actually blocks a decision.
|
|
111
74
|
|
|
112
75
|
${d.crash}
|
|
113
76
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
## Case Lifecycle (State Machine)
|
|
117
|
-
${LIFECYCLE_DIAGRAM}
|
|
118
|
-
|
|
119
|
-
### Phase → State map
|
|
120
|
-
|
|
121
|
-
| Phase | Case State | What happens |
|
|
122
|
-
|-------|-----------|-------------|
|
|
123
|
-
| RECON | (none) | Map attack surface, fingerprint, search CVEs. Something interesting → HYPOTHESIS. |
|
|
124
|
-
| HUNT | HYPOTHESIS | Document the lead (impact not required yet). Clear intended-behavior/artifact → KILLED; else INVESTIGATING. |
|
|
125
|
-
| TRACE / SKEPTIC / VALIDATE | INVESTIGATING | Trace reachability, attempt disconfirmation, and produce the pending PoC evidence bundle. Failure stays INVESTIGATING or becomes KILLED. |
|
|
126
|
-
| MAIN REVIEW | CONFIRMED | The main agent judges whether the machine differential actually establishes the vulnerability and impact, then commits through ConfirmFinding. |
|
|
127
|
-
| CHAIN | CONFIRMED | Link confirmed findings and evaluate multi-step exploit paths; this stage does not confirm new cases. |
|
|
128
|
-
| REPORT | REPORTED | CaseContext → main-agent report writing → report-readiness gate. |
|
|
129
|
-
|
|
130
|
-
### Preconditions Per State Transition (MANDATORY)
|
|
131
|
-
|
|
132
|
-
| Advance To | Required Case Fields | On Disk |
|
|
133
|
-
|-----------|---------------------|---------|
|
|
134
|
-
| HYPOTHESIS → INVESTIGATING | evidence (observations), confidence | Notes on what was observed |
|
|
135
|
-
| INVESTIGATING → **CONFIRMED** | evidence, poc, **impact** (content below), severity, **target**, **disconfirmation** (the main agent's documented disprove attempt) | PromoteFinding phase 1: PoC runs 2× against target + 1× against an operator-approved \`control_target\` (same script, sha256-enforced); every run completes at exit zero with output fully captured and writes nonce-bound \`evidence.json\` with a response-body predicate; the harness obtains conclusive target/control responses and requires \`target_only\`. Then the **main/coordinator agent itself** reviews and calls **ConfirmFinding**, which captures a fresh second harness replay before commit. Worker agents cannot submit phase 2. Zero exit is necessary run integrity, never vulnerability proof; output markers are diagnostic only. |
|
|
136
|
-
| Any → KILLED | assumptions (why it died) | — |
|
|
137
|
-
| CONFIRMED → REPORTED | CaseContext(id) succeeded (records report path) AND you wrote the report file | Context bundle + report file |
|
|
138
|
-
|
|
139
|
-
**Empty required field = you cannot advance.** The fields ARE the gates.
|
|
140
|
-
|
|
141
|
-
### Advance vs kill vs stay
|
|
142
|
-
|
|
143
|
-
Staying in HYPOTHESIS/INVESTIGATING is fine — you're still working. Do not force a transition.
|
|
144
|
-
|
|
145
|
-
- **HYPOTHESIS → KILLED only when:** documented intended behavior, duplicate, artifact/noise, or you proved no attack path exists after testing.
|
|
146
|
-
- **HYPOTHESIS → INVESTIGATING:** something real, actively testing (source-sink not required yet).
|
|
147
|
-
- **INVESTIGATING → KILLED:** proved insufficient impact, environmental issue, unreliable exploit, or duplicate after investigation.
|
|
148
|
-
- **INVESTIGATING → CONFIRMED:** the gates below must pass.
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## At HYPOTHESIS
|
|
153
|
-
|
|
154
|
-
Document without impact proof: **what happened** (behavior/error/timing/leak), **where** (endpoint/parameter/component/line), **who can reach it** (unauth/user/admin), **unknowns → next experiments**.
|
|
155
|
-
|
|
156
|
-
Do NOT kill a hypothesis just because impact is unclear — impact may come from chaining. Kill only when: clearly documented/intended behavior (after checking docs), duplicate, test artifact/cache noise/browser quirk, or you tested and proved no attack path (not "I can't see one").
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## At INVESTIGATING (chaining primitives)
|
|
161
|
-
|
|
162
|
-
Primitives: open redirect, limited SSRF, info leak of non-sensitive data, reflected XSS on non-sensitive page, CSRF on public-only action. For each:
|
|
163
|
-
|
|
164
|
-
1. **What can this combine with?** (SSRF + internal service, open redirect + OAuth callback, leak + other endpoint)
|
|
165
|
-
2. **Does it cross a trust boundary?** Unauth trigger? Low-priv user reaching an admin endpoint?
|
|
166
|
-
3. **Worst-case chain in C/I/A?**
|
|
167
|
-
|
|
168
|
-
Record chains via CaseLink. Keep the primitive INVESTIGATING while exploring; KILL only if you prove no chain exists after testing.
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## At VALIDATE (before CONFIRMED)
|
|
173
|
-
|
|
174
|
-
All of the following must be answered and documented in evidence + impact. Incomplete = stay INVESTIGATING.
|
|
175
|
-
|
|
176
|
-
### 0. Attacker Model
|
|
177
|
-
|
|
178
|
-
1. **Who is the attacker?** (unauth internet, low-priv user, tenant peer, SSRF pivot)
|
|
179
|
-
2. **What can they already do without the bug?** (baseline)
|
|
180
|
-
3. **What extra power does the bug grant beyond that baseline?**
|
|
181
|
-
4. **Is the path realistic in production?** (auth, CSRF, WAF, network, feature flags, admin-only)
|
|
182
|
-
|
|
183
|
-
If you cannot name a concrete attacker who gains something they should not have → do NOT confirm; stay INVESTIGATING or KILL with reason.
|
|
184
|
-
|
|
185
|
-
### 1. Disconfirmation (mandatory)
|
|
186
|
-
|
|
187
|
-
The finding must survive an attempt to disprove it. Two tiers, gated on \`confidence\` (severity comes later, from the PoC):
|
|
188
|
-
|
|
189
|
-
**\`confidence: high\` → skeptic subagent (MANDATORY):** dispatch it before main-agent validation with ${d.skeptic}. It independently re-reads the source (or re-probes live), verifies scope, tries to disprove, and audits any PoC file you already have for cheats. Its schema-validated CONFIRMED verdict must carry its own \`disconfirmation_attempt\` (CONFIRMED verdicts without one are rejected by PipelineSubmit). DISPROVEN → add EvidenceAdd role=refutation, then killed directly, no tie-breaker. UNDETERMINED → block/re-dispatch; do not validate yet. Do NOT skip; do NOT self-disconfirm high-confidence findings.
|
|
190
|
-
|
|
191
|
-
**Below high → self-disconfirmation:** actively try to disprove your own finding; document it (see the strong/weak example below). Not a formality.
|
|
192
|
-
|
|
193
|
-
An attempt: reproduce under different conditions (auth/config/network position); test the behavior against docs/baseline endpoints; trigger protections (WAF/CSP/CSRF/rate limits); try to trigger the same behavior without your attacker-controlled input. Document in \`disconfirmation\`: what you tried, how (conditions/inputs/target), result (failing to disprove is the expected outcome), why the attempt was valid.
|
|
194
|
-
|
|
195
|
-
Strong example: "Read /api/users/123 as user B after confirming user A owns 123 → 403. Repeated with X-Override-User header (seen in admin traffic) → user A's data returned. Protection bypassed via the admin header."
|
|
196
|
-
Weak: "Tried to disprove. Could not." — insufficient.
|
|
197
|
-
|
|
198
|
-
**The CONFIRMED disconfirmation comes from the main agent, not a script or worker.** There is no \`disconfirmation_path\` gate: after PromoteFinding, the main/coordinator must write its own failed disproof attempt, which becomes the case's \`disconfirmation\`, and call ConfirmFinding to capture the fresh phase-2 replay. A worker/subagent cannot call PromoteFinding or ConfirmFinding, and a verdict without the main agent's \`disconfirmation_attempt\` is rejected.
|
|
199
|
-
|
|
200
|
-
**Evidence chain closure (before PromoteFinding):** promotion is rejected unless the case carries an **artifact-backed** \`observation\` evidence item (EvidenceAdd role=observation with \`artifact_path\` — the initial signal, stored with its SHA-256) in addition to the auto-recorded reproduction item. Record observations as you go, not at promote time.
|
|
201
|
-
|
|
202
|
-
**Main-agent validation only:** do not dispatch validation. You write the smallest reliable PoC that demonstrates the **maximum reachable impact** of the vulnerability, set the case's poc/evidence/impact/severity/target fields, and run PromoteFinding yourself. "Smallest" means no fragile ceremony, mocks, or unrelated exploit steps — not a weaker impact demonstration. Do not stop at a benign marker if a stronger in-scope, non-destructive primitive is reachable (read/write, privilege change, account takeover path, data exposure, etc.). If the PoC fails, refine it yourself up to the local budget; if proof cannot meet the gate, kill or keep the case investigating with the exact blocker.
|
|
203
|
-
|
|
204
|
-
**PromoteFinding (phase 1) — evidence bundle, not markers.** Pick the differential \`mode\` that fits the class:
|
|
205
|
-
|
|
206
|
-
- **\`mode: "inter_host"\` (default)** — body-carried proof that is the same on any host (file read, injection exfil, info leak, reflection). Call with \`poc_path\`, an operator-approved \`control_target\` from \`PI_POC_CONTROL_TARGETS\`, optional same-byte \`control_path\` (defaults to \`poc_path\`), and \`local: true\` when the bug needs network. The harness sends the SAME request to target and control and requires \`target_only\`.
|
|
207
|
-
- **\`mode: "intra_target"\`** — access-control and business-logic classes (IDOR/BOLA, auth bypass, privilege escalation, mass assignment, logic/price tampering) where the discriminating variable is the attacker's IDENTITY or a PARAMETER, not the host. A different host lacks the victim's object/state, so inter-host proves nothing. Instead the evidence declares \`verify.mode: "intra_target"\` and a \`baseline\` (a legitimate SAME-host request — the attacker's own object, a properly-authorized request, the field omitted); the harness sends attack + baseline to the case target and requires the proof on the attack response only. No \`control_target\`/\`control_path\`.
|
|
208
|
-
|
|
209
|
-
Every run must complete with fully captured output and write nonce-bound \`evidence.json\` whose \`expect\` includes \`body_contains\` or \`body_regex\`; status-only evidence is rejected. The harness pins DNS at connect time and keeps redirects on the bound host. Private replay requires operator authorization. Blind/OOB classes fail closed until a source-separated oracle exists.
|
|
210
|
-
|
|
211
|
-
**ConfirmFinding (phase 2) — main-agent-only commit.** After PromoteFinding succeeds, do not dispatch confirmation. The main/coordinator agent must inspect the exact PoC/evidence, hunt trivial predicates/fabrication, attempt disconfirmation, and call \`ConfirmFinding(case_id, verdict)\` itself. A CONFIRMED call performs and stores a fresh harness-owned target/control replay; a caller-supplied re-execution checkbox is not accepted. CONFIRMED requires \`re_execution_note\`, \`differential: "target_only"\`, and the main agent's \`disconfirmation_attempt\`. Worker processes are rejected. **Never \`CaseUpdate(status: "confirmed")\` directly.**
|
|
212
|
-
|
|
213
|
-
**PoC audit (anti-cheat, before PromoteFinding):** have an independent eye on the PoC script itself. For \`confidence: high\` findings the skeptic agent re-reads the PoC file hunting unconditional success, trivial checks, constants, and local mocks. Record the audit as EvidenceAdd \`observation\` (or \`refutation\` if cheated). The main agent must re-read the exact script before ConfirmFinding; workers may challenge evidence but never run validation or decide promotion. Deterministic backstops are code: output completeness, nonce binding, response-body predicates, deterministic runs, operator-approved control, DNS-pinned conclusive replay, same-file sha256, and PoC byte-identity re-check at commit.
|
|
214
|
-
|
|
215
|
-
### 2. Design & Runtime Check — non-intentionality gate (mandatory)
|
|
216
|
-
|
|
217
|
-
A finding is report-worthy only if the behavior is a genuine flaw — not documented intent and not already neutralized by the runtime the target ships on. Prove the difference by searching before you confirm; record the search (what you looked at, what you found) in \`disconfirmation\`/\`evidence\` for the report's non-intentionality proof.
|
|
218
|
-
|
|
219
|
-
**Search:** (1) project docs — README/docs/comments near the sink; (2) changelog/release notes — deliberate feature or known issue?; (3) git history/blame — commit messages/PRs ("fix:", "feat:", "intentional", "trade-off"); (4) issue tracker/accepted PRs; (5) runtime/framework docs — does the shipped version already mitigate (patched version, middleware, WAF, CSRF, CSP, runtime defaults)?
|
|
220
|
-
|
|
221
|
-
**Outcomes:**
|
|
222
|
-
|
|
223
|
-
- **BY DESIGN** — docs/history show intent → KILL \`intended_behavior\`, UNLESS the documented intent IS the flaw ("we knowingly accept this risk" on a security-sensitive path with real impact is still a finding — say why in evidence).
|
|
224
|
-
- **FIXED IN THE RUNTIME** — the runtime already blocks the path → KILL \`framework_protection\`, or downgrade to \`info\` if only a hardening note.
|
|
225
|
-
- **NEITHER** — no documented intent and no runtime mitigation → this is the non-intentionality evidence; cite what you searched (docs read, commits checked, versions compared).
|
|
77
|
+
## Bank the results
|
|
226
78
|
|
|
227
|
-
|
|
79
|
+
- Write the entry-point map, selected class batches, and open gaps to the scratchpad: \`ScratchpadWrite(run_id, "recon", "entry-points.md", ...)\`.
|
|
80
|
+
- File high-value leaks (source map, origin IP, exposed schema, leaked creds) as \`EvidenceAdd role=observation\`.
|
|
81
|
+
- Turn each credible lead into a hypothesis: \`CaseAdd\` it with its \`disproveIf\` (what would rule it out). Every observed anomaly — an unexpected 200, an error leak, a timing gap, an exposed endpoint — is a HYPOTHESIS, not just a note.
|
|
228
82
|
|
|
229
|
-
|
|
83
|
+
## Hand-off
|
|
230
84
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
Fails the gate: "attacker can read files" without target + protections; "works on localhost" without prod differences; "the code path exists" without a reachable victim asset; "could be dangerous / may lead to RCE" without a concrete production path.
|
|
234
|
-
|
|
235
|
-
Name the **specific target host/repo** in the target field. Dev-only with non-default config → document honestly; consider KILL.
|
|
236
|
-
|
|
237
|
-
### 4. KILL at Validate stage
|
|
238
|
-
|
|
239
|
-
Documented intended behavior · self-XSS/self-DoS only · requires admin/root role that already has the power · local-only/offline/impossible deployment · needs physical access or social engineering with no trust-boundary break · no C/I/A/financial effect for anyone but the attacker · PoC proves a code path but no victim asset · protections block the path and are not bypassed. **Kill gate:** killing a case that reached \`investigating\`/\`confirmed\` requires a refutation evidence item (EvidenceAdd role=refutation — the disprove attempt that ended the lead); a keyword in free text is not enough once the case advanced past hypothesis.
|
|
240
|
-
|
|
241
|
-
### 5. Evidence-First Doctrine
|
|
242
|
-
|
|
243
|
-
Every claim must be traceable to observed/reproduced behavior, source code, or documented platform behavior. Insufficient evidence → state uncertainty and propose the next experiment. Never assume success where verification is incomplete.
|
|
244
|
-
|
|
245
|
-
### 6. Impact Gate
|
|
246
|
-
|
|
247
|
-
Prove at least **one** real attacker-facing violation against a production-viable target:
|
|
248
|
-
|
|
249
|
-
| Category | Required proof |
|
|
250
|
-
|----------|----------------|
|
|
251
|
-
| **Confidentiality** | Attacker reads data they must not see |
|
|
252
|
-
| **Integrity** | Attacker changes data/state they must not control |
|
|
253
|
-
| **Availability** | Attacker degrades service for **others** |
|
|
254
|
-
| **Financial / authz** | Direct money, privilege, or account takeover path |
|
|
255
|
-
|
|
256
|
-
Impact text answers: *who is hurt, what is lost, how the attacker reaches it from production.* Theoretical impact, a second unproven bug, or unreachable-from-attacker → stay INVESTIGATING (chain it) or KILL.
|
|
257
|
-
|
|
258
|
-
**Severity is derived from PROVEN impact, not guessed** — set only after the machine differential and the main-agent review demonstrate the impact; a zero exit or PoC output alone is insufficient:
|
|
259
|
-
- **critical** = RCE, account takeover, or direct fund theft demonstrated in confirmed evidence
|
|
260
|
-
- **high** = sensitive data read/write, privilege escalation, SSRF to internal services
|
|
261
|
-
- **medium** = limited data exposure, XSS on sensitive page, IDOR on non-critical resources
|
|
262
|
-
- **low** = info leak, open redirect, self-only impact with a victim path
|
|
263
|
-
- **info** = best-practice gap, no demonstrated impact
|
|
264
|
-
|
|
265
|
-
"Could lead to"/"may allow"/"theoretically" = NOT proven — drop to what the confirmed harness evidence shows. Claim the highest impact the harness and main-agent review actually prove; unsupported escalation gets rejected at triage.
|
|
266
|
-
|
|
267
|
-
### 7. Adversarial Self-Review
|
|
268
|
-
|
|
269
|
-
1. Why this might NOT be a vulnerability.
|
|
270
|
-
2. Alternative explanations for the observation.
|
|
271
|
-
3. Why each alternative was rejected **with evidence**.
|
|
272
|
-
4. What blocks a real attacker in production today, and whether each is bypassed.
|
|
273
|
-
5. Would triage reject this as informative/N/A?
|
|
274
|
-
|
|
275
|
-
### 8. Root Cause → Boundary → Impact
|
|
276
|
-
|
|
277
|
-
\`\`\`
|
|
278
|
-
Entry (attacker-controlled) → Code path → Trust boundary crossed → Victim impact
|
|
279
|
-
\`\`\`
|
|
280
|
-
|
|
281
|
-
Reproduce at least twice or via two methods.
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
## At REPORT
|
|
286
|
-
|
|
287
|
-
1. **Run CaseContext(case_id)** — writes the context bundle (complete record, PoC + disconfirmation logs, links, pipeline artifacts) and records the report path.
|
|
288
|
-
2. **Write the report yourself** at the returned report path using the context bundle. In XP swarm, reporting stays with the main agent.
|
|
289
|
-
3. **Report-readiness gate** (YOU check this before accepting; on failure, edit the report yourself):
|
|
290
|
-
- Deterministic reproduction by another researcher
|
|
291
|
-
- Steps realistic in production
|
|
292
|
-
- Impact justified without inflation (would the vendor agree?)
|
|
293
|
-
- Root cause + fix guidance concrete
|
|
294
|
-
- Attacker model + victim impact + target explicit
|
|
295
|
-
- No internal identifiers: no case IDs, ledger paths, PoC filenames, or local paths in the report file
|
|
296
|
-
|
|
297
|
-
The ledger enforces a machine floor on the report file before accepting \`reported\`: non-trivial size, required section headings (Summary / Impact / Remediation), and a forbidden-identifier scan (case id, ledger/report paths, PoC/control/disconfirmation basenames). A report that fails the scan keeps the case CONFIRMED — fix the file, then retry the transition.
|
|
298
|
-
|
|
299
|
-
---
|
|
300
|
-
|
|
301
|
-
## KILLED cataloging
|
|
302
|
-
|
|
303
|
-
When a case is definitively dead (not "I don't know yet"), record the reason: ${KILL_REASONS_TEXT} (true bug, no realistic attacker value). Documenting kills prevents re-opening dead ends. Cases with unresolved unknowns stay INVESTIGATING, not killed.
|
|
85
|
+
Recon ends when more collection is unlikely to change the hunt plan. Hand off to yourself: the filed HYPOTHESIS cases are the hunt queue. Investigate them inline through the casefile. Recon does not confirm findings — the PoC confirmation gate (PromoteFinding → ConfirmFinding) is unchanged and stays the main agent's job.
|
|
304
86
|
`.trim();
|
|
305
87
|
}
|
|
306
88
|
|
|
307
|
-
/**
|
|
308
|
-
export const
|
|
309
|
-
|
|
310
|
-
/** Cyber workflow for OMP (fork of Pi; native `task` dispatch). */
|
|
311
|
-
export const STATIC_CYBER_WORKFLOW_OMP = buildCyberWorkflow(OMP_DISPATCH);
|
|
89
|
+
/** Recon workflow for Pi Agent (pi-subagents dispatch). */
|
|
90
|
+
export const STATIC_RECON_WORKFLOW = buildReconWorkflow(PI_DISPATCH);
|
|
312
91
|
|
|
313
|
-
/**
|
|
314
|
-
|
|
315
|
-
*
|
|
316
|
-
* Same attacker discipline as the full workflow, but the main agent does every
|
|
317
|
-
* stage itself (recon, hunt, trace, validate, chain, report). Built for CTF and
|
|
318
|
-
* single-shot engagements where subagent orchestration is overkill.
|
|
319
|
-
*/
|
|
320
|
-
export const STATIC_CYBER_WORKFLOW_LITE = `
|
|
321
|
-
# Cyber Workflow — LITE (Single-Agent)
|
|
322
|
-
|
|
323
|
-
You are the ONLY agent. Do NOT dispatch subagents (no auditor, tracer, skeptic, or chain agents). You do every stage yourself, inline: recon, hunt, trace, validate, chain, report — the full attacker discipline without subagent orchestration overhead. Great for CTF and focused single-target engagements.
|
|
324
|
-
|
|
325
|
-
Think like a real external attacker, not a code reviewer. Technical bugs are cheap; **reachable attacker impact** is what matters.
|
|
326
|
-
|
|
327
|
-
## Tool Reference
|
|
328
|
-
|
|
329
|
-
**Casefile (state tracking):** CaseAdd, CaseUpdate, CaseGet, CaseList, CaseSearch, CaseLink, CaseUnlink, CaseContext, PromoteFinding, ConfirmFinding, PipelineSubmit
|
|
330
|
-
|
|
331
|
-
**Scratchpad (pipeline artifacts):** ScratchpadInit, ScratchpadResume, ScratchpadCheckpoint, ScratchpadWrite, ScratchpadRead, ScratchpadPhaseDone, ScratchpadClear
|
|
332
|
-
|
|
333
|
-
**Web lookup (research):** web_search, web_fetch, exploit_search, context7, deepwiki, http_request
|
|
334
|
-
|
|
335
|
-
**No subagent/task tool.** In lite mode you do not dispatch subagents (pi's \`subagent\` or OMP's \`task\`). All specialist work is yours.
|
|
336
|
-
|
|
337
|
-
## Case Lifecycle (State Machine)
|
|
338
|
-
${LIFECYCLE_DIAGRAM}
|
|
339
|
-
|
|
340
|
-
## Stage discipline (all done by you, inline)
|
|
341
|
-
|
|
342
|
-
1. **RECON — attack-surface mapping.** Blackbox/CTF: aggressively gather high-signal intel and turn it into entry points, auth models, trust boundaries, attacker model, vuln-class batches, and gaps. Fingerprint credible stack/version signals and search CVEs (\`exploit_search\`) when the version confidence is useful. Use JS/source maps, \`robots.txt\`, \`sitemap.xml\`, \`/.well-known/\`, OpenAPI/Swagger, GraphQL introspection, exposed backup/VCS checks, and passive archives when they can change class selection, target selection, or attacker modeling. Record discovered entry points (URL, method, params, auth state), selected class targets, and gaps/assumptions: \`ScratchpadWrite(run_id, "recon", "entry-points.md", ...)\`.
|
|
343
|
-
2. **HUNT** — choose attack classes from recon and examine relevant entry points. \`CaseAdd\` each lead as a hypothesis. Track coverage per class.
|
|
344
|
-
3. **TRACE / observe** — prove reachability and understand the mechanism by observing how the target behaves, then analyzing the reaction. Read the source (grep/find); probe the live endpoint (\`http_request\`) and diff responses (status vs length vs timing vs error) as you vary one input; or for a binary/local target trace syscalls + library calls (\`strace\`/\`ltrace\`) and watch crashes, signals, and return codes under malformed/boundary input. Infer the internal model from the differential, feed anomalies back as hypotheses, and only advance reachable findings.
|
|
345
|
-
4. **VALIDATE** — write a PoC that emits nonce-bound \`evidence.json\`, run it via \`PromoteFinding\` (2 target runs + same-script control), review and disconfirm it yourself, and commit via \`ConfirmFinding\`, which performs the fresh phase-2 replay (see the gates below). Derive severity from the proven impact.
|
|
346
|
-
5. **CHAIN** — link confirmed findings via \`CaseLink\` to find exploit chains.
|
|
347
|
-
6. **REPORT** — run \`CaseContext\` to write the context bundle, then write the final report yourself per the report style checklist below, then \`CaseUpdate(status: "reported")\`.
|
|
348
|
-
|
|
349
|
-
## Report style checklist (lite — you are the writer)
|
|
350
|
-
|
|
351
|
-
Write the final report as a self-contained markdown file at the report path CaseContext recorded, applying the fixed report format rules:
|
|
352
|
-
|
|
353
|
-
- **Title:** \`<vuln class>: <exact trigger/location> — <honest impact>\` (e.g. "IDOR: order delivery address of any user", "SQLi: blind boolean-based via GET").
|
|
354
|
-
- **Structure:** Summary (2-3 sentences) → Vulnerability Details (CWE, CVSS 3.1 vector + score, affected asset/version) → Description (root cause + why NOT intended behavior, citing the docs/git search) → Steps to Reproduce (numbered, verbatim requests/responses/scripts, deterministic) → Impact (attacker model → maximum proven C/I/A outcome, not speculation) → Mitigation / Remediation → References → Disclosure timeline (only if dates are known).
|
|
355
|
-
- **Tone:** factual, calm, evidence-carried. NO case IDs, ledger paths, PoC filenames, local paths, or "I discovered" narratives. Never invent evidence — "version not determined" beats a guess. Severity from proven impact only.
|
|
356
|
-
|
|
357
|
-
## Gates (unchanged — these keep findings honest)
|
|
358
|
-
|
|
359
|
-
- **No finding is confirmed until its target is verified in scope** per the program's scope instruction. Out-of-scope findings are killed, not confirmed.
|
|
360
|
-
- **No finding is validated without a reachability trace** showing REACHABLE. UNREACHABLE requires a concrete blocker; unresolved auth/WAF/source ambiguity stays INVESTIGATING or BLOCKED, not killed.
|
|
361
|
-
- **High-confidence findings: do your own adversarial disconfirmation.** No skeptic subagent in lite mode — actively try to disprove your own finding and document the attempt in \`disconfirmation\`. Failing to disprove is the expected outcome.
|
|
362
|
-
- **Confirmed requires** evidence + poc + impact + severity + target + disconfirmation, via the two-phase gate: **PromoteFinding** in the differential \`mode\` that fits the class — \`inter_host\` (default) with an operator-approved \`control_target\` for body-carried proof (file read, injection exfil, info leak, reflection), or \`intra_target\` for access-control/logic classes (IDOR, auth bypass, privilege escalation, logic), where the evidence declares \`verify.mode:"intra_target"\` + a same-host \`baseline\` and the harness requires the proof on the attack response only (no control target). Then you, the main agent, inspect the bundle, attempt disconfirmation, and call **ConfirmFinding** yourself. That call captures a fresh second replay before commit. Do not delegate validation or confirmation. The machine gate requires zero-exit complete runs, nonce binding, body evidence, determinism, a DNS-pinned conclusive \`target_only\` differential, and script identity; zero exit is never proof and markers are diagnostic only. \`local:true\` and private replay remain operator-gated. No mocks and no direct \`CaseUpdate(status: "confirmed")\`.
|
|
363
|
-
- **Severity is derived from proven PoC impact, not theory.** Demonstrate and claim the highest impact the attacker can actually reach; claiming less than a proven escalation is wrong, and over-claiming an unproven one gets the finding rejected at triage.
|
|
364
|
-
- **Evidence-first:** every claim must be traceable to observed/reproduced behavior, source code, or documented platform behavior.
|
|
365
|
-
- **Design & runtime check (mandatory before CONFIRMED):** actively search the target's docs, git history, changelog, and runtime/framework docs for evidence the behavior is BY DESIGN or already FIXED IN THE RUNTIME. Found it → KILL (\`intended_behavior\` / \`framework_protection\`), unless the documented intent is itself the flaw with real attacker impact. Not found → document the search in \`disconfirmation\` as non-intentionality proof.
|
|
366
|
-
|
|
367
|
-
## KILLED cataloging
|
|
368
|
-
|
|
369
|
-
When a case is definitively dead (not "I don't know yet"), record the reason: ${KILL_REASONS_TEXT}. **A kill without a reason is rejected by the ledger** — add an EvidenceAdd \`refutation\` item or state the reason token in assumptions/nextStep. Documenting kills prevents re-opening dead ends. Cases with unresolved unknowns stay INVESTIGATING, not killed.
|
|
370
|
-
|
|
371
|
-
## Stall rule (deferred)
|
|
372
|
-
|
|
373
|
-
3 rounds without new signal, new surface, or new techniques → CaseUpdate(status: 'blocked', blockers: ["deferred after 3 rounds — revisit when: <exact condition>"]). Blocked-with-revisit-condition is the deferred state; do not kill leads that are merely stalled.
|
|
374
|
-
`.trim();
|
|
92
|
+
/** Recon workflow for OMP (fork of Pi; native `task` dispatch). */
|
|
93
|
+
export const STATIC_RECON_WORKFLOW_OMP = buildReconWorkflow(OMP_DISPATCH);
|
package/skills/casefile/SKILL.md
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: casefile
|
|
3
|
-
description: Use when tracking security investigations, bug bounty findings, CTF leads, audit evidence, exploit chains, dead ends, or reports in the Casefile ledger.
|
|
4
|
-
license: MIT
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Casefile Tracker
|
|
8
|
-
|
|
9
|
-
Use Casefile to maintain durable security investigation state across agent turns.
|
|
10
|
-
|
|
11
|
-
## Workflow
|
|
12
|
-
|
|
13
|
-
1. Check existing cases before opening a new one with CaseList or CaseSearch.
|
|
14
|
-
2. Open new leads with CaseAdd as `hypothesis` or `investigating`.
|
|
15
|
-
3. Promote cases with CaseUpdate only after materially new evidence, proof, impact, blockers, remediation, or status changes.
|
|
16
|
-
4. Mark `confirmed` only via the two-phase gate — main-agent `PromoteFinding` (PoC 2× target + 1× operator-approved control, nonce-bound body evidence, then a DNS-pinned conclusive `target_only` replay; use the post-PoC harness-generated canary for reflection-capable requests) → the main agent personally reviews → `ConfirmFinding`, which captures a fresh second harness replay before commit. Never delegate validation or confirmation; worker/subagent gate calls are rejected. Exit zero is run integrity, never vulnerability proof. A predicate differential is evidence, not an automatic exploit verdict. Blind/OOB findings remain investigating without a source-separated oracle.
|
|
17
|
-
5. Use CaseLink and CaseUnlink for exploit chains. Do not edit linked case IDs directly.
|
|
18
|
-
6. Use CaseContext only for confirmed or already reported cases: it writes the full context bundle (complete record, verification logs, links, pipeline artifacts) and records the report path. Then write the report and CaseUpdate status=`reported`.
|
|
19
|
-
7. Use `killed` for disproven, duplicate, or dead-end leads, and include evidence, blockers, next step, or assumptions explaining why.
|
|
20
|
-
|
|
21
|
-
## State machine
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
hypothesis → investigating → confirmed → reported
|
|
25
|
-
↓ ↓
|
|
26
|
-
blocked killed (terminal)
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
- investigating requires evidence + confidence
|
|
30
|
-
- confirmed requires PromoteFinding + ConfirmFinding (never CaseUpdate)
|
|
31
|
-
- killed/reported are terminal (no field edits or re-links)
|
|
32
|
-
|
|
33
|
-
## Tool Map
|
|
34
|
-
|
|
35
|
-
- `CaseAdd`: create a new case.
|
|
36
|
-
- `CaseUpdate`: update an existing case.
|
|
37
|
-
- `PromoteFinding`: phase 1 — require a body predicate, bind the verify URL, pin DNS, and send the same request to target and an operator-approved `PI_POC_CONTROL_TARGETS` control. Both responses must be conclusive and only `target_only` passes. For reflection, place `{{PI_POC_CANARY}}` exactly once in the request and declare `verify.canary`; the harness creates the secret after the PoC exits and requires target-only reflection. Network/private access remains operator-gated; OOB fails closed without source separation.
|
|
38
|
-
- `ConfirmFinding`: phase 2 — main-agent-only review and fresh harness replay before commit (CONFIRMED promotes; NOT_CONFIRMED keeps investigating; worker/subagent calls are rejected). Record `canary_assessment=verified` when requested, otherwise `not_applicable` with a concrete reason.
|
|
39
|
-
- `CaseGet`: read one case by ID.
|
|
40
|
-
- `CaseList`: list cases with filters and pagination.
|
|
41
|
-
- `CaseSearch`: search all fields or a scoped field.
|
|
42
|
-
- `CaseLink`: bidirectionally link two cases.
|
|
43
|
-
- `CaseUnlink`: remove a bidirectional case link.
|
|
44
|
-
- `CaseContext`: write the case context bundle (complete record, PoC/disconfirmation logs, links, pipeline artifacts) for a confirmed or reported case and record the report path for the main agent.
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worker-thread entry for heavy ledger reads (suggestChains, writeCaseContext).
|
|
3
|
-
*
|
|
4
|
-
* Runs in a dedicated thread so an O(n²) chain scan or a multi-megabyte
|
|
5
|
-
* context-bundle build never blocks the agent's event loop. Each call spawns
|
|
6
|
-
* a fresh worker — rare operations, no lifecycle to manage, no stale state.
|
|
7
|
-
* The worker opens its OWN connection to the same WAL database (read-mostly
|
|
8
|
-
* work + one small reportPath write); WAL + busy_timeout make multi-connection
|
|
9
|
-
* access safe.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { parentPort, workerData } from "node:worker_threads";
|
|
13
|
-
import { suggestChains, writeCaseContext } from "./ledger.ts";
|
|
14
|
-
import { setScratchpadRoot } from "./scratchpad.ts";
|
|
15
|
-
|
|
16
|
-
process.env.PI_CASEFILE_PATH = workerData.casefilePath as string;
|
|
17
|
-
setScratchpadRoot(workerData.workspaceRoot as string | undefined);
|
|
18
|
-
|
|
19
|
-
type WorkerRequest =
|
|
20
|
-
| { op: "suggestChains"; caseId?: string }
|
|
21
|
-
| { op: "writeCaseContext"; id: string };
|
|
22
|
-
|
|
23
|
-
parentPort?.on("message", (req: WorkerRequest) => {
|
|
24
|
-
try {
|
|
25
|
-
if (req.op === "suggestChains") {
|
|
26
|
-
parentPort?.postMessage({ ok: true, result: suggestChains(req.caseId) });
|
|
27
|
-
} else if (req.op === "writeCaseContext") {
|
|
28
|
-
parentPort?.postMessage({ ok: true, result: writeCaseContext(req.id) });
|
|
29
|
-
} else {
|
|
30
|
-
parentPort?.postMessage({ ok: false, error: `unknown ledger worker op` });
|
|
31
|
-
}
|
|
32
|
-
} catch (e) {
|
|
33
|
-
parentPort?.postMessage({ ok: false, error: (e as Error).message });
|
|
34
|
-
}
|
|
35
|
-
});
|
package/src/ledger-worker.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Async offload for the two heavy ledger reads — main-thread side.
|
|
3
|
-
*
|
|
4
|
-
* suggestChains is O(rules × cases²) over the whole ledger; writeCaseContext
|
|
5
|
-
* reads every artifact of every matching scratchpad run and builds a
|
|
6
|
-
* multi-hundred-KB bundle. Both ran synchronously inside async tool handlers,
|
|
7
|
-
* stalling the event loop. Each call here spawns a short-lived worker thread
|
|
8
|
-
* (ledger-worker-entry.ts) and falls back to the inline sync function when
|
|
9
|
-
* the worker cannot run (Node < 22.18 has no default type stripping for the
|
|
10
|
-
* TS entry) or times out. Writes stay sync on the main thread: they are
|
|
11
|
-
* single-row upserts, bounded by the 5s busy_timeout.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { Worker } from "node:worker_threads";
|
|
15
|
-
import {
|
|
16
|
-
type CaseContextResult,
|
|
17
|
-
type ChainSuggestion,
|
|
18
|
-
getCasefilePath,
|
|
19
|
-
suggestChains,
|
|
20
|
-
writeCaseContext,
|
|
21
|
-
} from "./ledger.ts";
|
|
22
|
-
import { detectWorkspaceRoot } from "./scratchpad.ts";
|
|
23
|
-
|
|
24
|
-
type WorkerResponse = { ok: true; result: unknown } | { ok: false; error: string };
|
|
25
|
-
|
|
26
|
-
const WORKER_TIMEOUT_MS = 30_000;
|
|
27
|
-
|
|
28
|
-
function runInLedgerWorker(
|
|
29
|
-
request:
|
|
30
|
-
| {
|
|
31
|
-
op: "suggestChains";
|
|
32
|
-
caseId?: string;
|
|
33
|
-
}
|
|
34
|
-
| {
|
|
35
|
-
op: "writeCaseContext";
|
|
36
|
-
id: string;
|
|
37
|
-
},
|
|
38
|
-
): Promise<unknown> {
|
|
39
|
-
const { promise, resolve, reject } = Promise.withResolvers<unknown>();
|
|
40
|
-
const worker = new Worker(new URL("./ledger-worker-entry.ts", import.meta.url), {
|
|
41
|
-
workerData: { casefilePath: getCasefilePath(), workspaceRoot: detectWorkspaceRoot() },
|
|
42
|
-
});
|
|
43
|
-
const timer = setTimeout(() => {
|
|
44
|
-
void worker.terminate();
|
|
45
|
-
reject(new Error("ledger worker timed out"));
|
|
46
|
-
}, WORKER_TIMEOUT_MS);
|
|
47
|
-
worker.once("message", (msg: WorkerResponse) => {
|
|
48
|
-
clearTimeout(timer);
|
|
49
|
-
void worker.terminate();
|
|
50
|
-
if (msg.ok) resolve(msg.result);
|
|
51
|
-
else reject(new Error(msg.error));
|
|
52
|
-
});
|
|
53
|
-
worker.once("error", (e) => {
|
|
54
|
-
clearTimeout(timer);
|
|
55
|
-
reject(e);
|
|
56
|
-
});
|
|
57
|
-
worker.postMessage(request);
|
|
58
|
-
return promise;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** suggestChains on a worker thread; inline fallback keeps behavior identical. */
|
|
62
|
-
export async function suggestChainsAsync(caseId?: string): Promise<ChainSuggestion[]> {
|
|
63
|
-
try {
|
|
64
|
-
return (await runInLedgerWorker({ op: "suggestChains", caseId })) as ChainSuggestion[];
|
|
65
|
-
} catch {
|
|
66
|
-
return suggestChains(caseId);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** writeCaseContext on a worker thread; inline fallback keeps behavior identical. */
|
|
71
|
-
export async function writeCaseContextAsync(id: string): Promise<CaseContextResult> {
|
|
72
|
-
try {
|
|
73
|
-
return (await runInLedgerWorker({ op: "writeCaseContext", id })) as CaseContextResult;
|
|
74
|
-
} catch {
|
|
75
|
-
return writeCaseContext(id);
|
|
76
|
-
}
|
|
77
|
-
}
|