omk-adaptorch-wpl 0.91.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 +49 -0
- package/dist/adaptorch-client.d.ts +184 -0
- package/dist/adaptorch-client.d.ts.map +1 -0
- package/dist/adaptorch-client.js +109 -0
- package/dist/adaptorch-client.js.map +1 -0
- package/dist/adjudicator-registry.d.ts +93 -0
- package/dist/adjudicator-registry.d.ts.map +1 -0
- package/dist/adjudicator-registry.js +89 -0
- package/dist/adjudicator-registry.js.map +1 -0
- package/dist/adjudicator.d.ts +73 -0
- package/dist/adjudicator.d.ts.map +1 -0
- package/dist/adjudicator.js +356 -0
- package/dist/adjudicator.js.map +1 -0
- package/dist/b2c-mapper.d.ts +25 -0
- package/dist/b2c-mapper.d.ts.map +1 -0
- package/dist/b2c-mapper.js +186 -0
- package/dist/b2c-mapper.js.map +1 -0
- package/dist/b2c-verdict.d.ts +66 -0
- package/dist/b2c-verdict.d.ts.map +1 -0
- package/dist/b2c-verdict.js +5 -0
- package/dist/b2c-verdict.js.map +1 -0
- package/dist/deep-wall.d.ts +80 -0
- package/dist/deep-wall.d.ts.map +1 -0
- package/dist/deep-wall.js +97 -0
- package/dist/deep-wall.js.map +1 -0
- package/dist/evaluate-correctness-wall.d.ts +35 -0
- package/dist/evaluate-correctness-wall.d.ts.map +1 -0
- package/dist/evaluate-correctness-wall.js +128 -0
- package/dist/evaluate-correctness-wall.js.map +1 -0
- package/dist/in-memory-adaptorch.d.ts +12 -0
- package/dist/in-memory-adaptorch.d.ts.map +1 -0
- package/dist/in-memory-adaptorch.js +26 -0
- package/dist/in-memory-adaptorch.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/live-adaptorch-transport.d.ts +14 -0
- package/dist/live-adaptorch-transport.d.ts.map +1 -0
- package/dist/live-adaptorch-transport.js +19 -0
- package/dist/live-adaptorch-transport.js.map +1 -0
- package/dist/loop.d.ts +149 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +219 -0
- package/dist/loop.js.map +1 -0
- package/dist/mcp-introspection-transport.d.ts +18 -0
- package/dist/mcp-introspection-transport.d.ts.map +1 -0
- package/dist/mcp-introspection-transport.js +29 -0
- package/dist/mcp-introspection-transport.js.map +1 -0
- package/dist/policy-wall.d.ts +34 -0
- package/dist/policy-wall.d.ts.map +1 -0
- package/dist/policy-wall.js +125 -0
- package/dist/policy-wall.js.map +1 -0
- package/dist/receipt-signature.d.ts +28 -0
- package/dist/receipt-signature.d.ts.map +1 -0
- package/dist/receipt-signature.js +30 -0
- package/dist/receipt-signature.js.map +1 -0
- package/dist/regenerate-packet.d.ts +25 -0
- package/dist/regenerate-packet.d.ts.map +1 -0
- package/dist/regenerate-packet.js +29 -0
- package/dist/regenerate-packet.js.map +1 -0
- package/dist/repair-budget.d.ts +24 -0
- package/dist/repair-budget.d.ts.map +1 -0
- package/dist/repair-budget.js +44 -0
- package/dist/repair-budget.js.map +1 -0
- package/dist/repair-loop.d.ts +18 -0
- package/dist/repair-loop.d.ts.map +1 -0
- package/dist/repair-loop.js +54 -0
- package/dist/repair-loop.js.map +1 -0
- package/dist/retry-backoff.d.ts +56 -0
- package/dist/retry-backoff.d.ts.map +1 -0
- package/dist/retry-backoff.js +75 -0
- package/dist/retry-backoff.js.map +1 -0
- package/dist/signed-receipt.d.ts +29 -0
- package/dist/signed-receipt.d.ts.map +1 -0
- package/dist/signed-receipt.js +25 -0
- package/dist/signed-receipt.js.map +1 -0
- package/dist/state-machine.d.ts +40 -0
- package/dist/state-machine.d.ts.map +1 -0
- package/dist/state-machine.js +88 -0
- package/dist/state-machine.js.map +1 -0
- package/dist/types.d.ts +149 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/wall-meta.d.ts +3 -0
- package/dist/wall-meta.d.ts.map +1 -0
- package/dist/wall-meta.js +3 -0
- package/dist/wall-meta.js.map +1 -0
- package/package.json +46 -0
package/dist/loop.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verdict-to-disposition integration layer (Part 3, `final-part3-integration.md`).
|
|
3
|
+
*
|
|
4
|
+
* This file implements exactly the bridge Part 3 defines between Part 1 (core algorithm,
|
|
5
|
+
* `final-part1-core-algorithm.md`) and Part 2 (verification layer,
|
|
6
|
+
* `final-part2-verification-layer.md`): projecting Part 2's five-state record-level
|
|
7
|
+
* `AdjudicationResult.verdict` onto Part 1's packet disposition vocabulary, and the two
|
|
8
|
+
* distinct failure-handling paths (`VERIFIER-ERROR` vs. `ADJUDICATION_FAILED`) Part 3's
|
|
9
|
+
* "VERIFIER-ERROR vs. ADJUDICATION_FAILED" section keeps separate.
|
|
10
|
+
*
|
|
11
|
+
* What is implemented here (Part 3 §1, §2 note, §3 note):
|
|
12
|
+
* - {@link projectVerdictToDisposition} - Part 3 §1's mapping table from a *returned*
|
|
13
|
+
* `AdjudicationResult` to a `{ targetState, nextActionKind }` disposition.
|
|
14
|
+
* - {@link runAdjudicationWithTimeout} - the timeout/try-catch wrapper around `adjudicate()`
|
|
15
|
+
* itself (Part 1 §2 state 9 / Part 3's "ADJUDICATION_FAILED" trigger condition), so a hung
|
|
16
|
+
* or throwing `adjudicate()` call never escapes uncaught and never produces a verdict to
|
|
17
|
+
* project in the first place.
|
|
18
|
+
* - {@link decideNextTransition} - the `DECLINED -> {RETRY_QUEUED | CLOSED | ESCALATED}`
|
|
19
|
+
* branch (Part 1 §2/§4, Part 3 §1), keyed on the per-packet attempt budget.
|
|
20
|
+
* - {@link handleAdjudicationFailure} - the `ADJUDICATION_FAILED -> {UNDER_REVIEW |
|
|
21
|
+
* ESCALATED}` branch (Part 1 §2 state 9), keyed on a *separate* bounded adjudication-call
|
|
22
|
+
* retry counter, never the packet's attempt/dispatch budget.
|
|
23
|
+
*
|
|
24
|
+
* What is explicitly out of scope for this file, per the design docs' own module boundaries
|
|
25
|
+
* (Part 1's introduction puts the integration boundary itself out of its scope; Part 3 §2
|
|
26
|
+
* assigns request assembly to the core-algorithm/loop side as an already-specified
|
|
27
|
+
* responsibility distinct from this projection):
|
|
28
|
+
* - The end-to-end AdaptOrch dispatch loop: `DRAFTED -> ROUTED -> AWAITING_APPROVAL ->
|
|
29
|
+
* DISPATCHED -> ACTIVE`, topology routing, actual `adaptorch_run` submission, and polling
|
|
30
|
+
* via `adaptorch_get_run`/reconciliation sweeps.
|
|
31
|
+
* - Assembling an `AdjudicationRequest` from a Work Packet's `dispatch_records[]` (Part 3
|
|
32
|
+
* §2's "one minor shape note") - callers of {@link runAdjudicationWithTimeout} supply an
|
|
33
|
+
* already-built `AdjudicationRequest`.
|
|
34
|
+
* - The on-disk persistence layer (`packet_index.json`, `run_map.json`, `loop_state.json`,
|
|
35
|
+
* the `adjudications/` archive, Part 1 §7) - the functions here are pure/async-pure and
|
|
36
|
+
* return decisions; they do not read or write any of those files themselves.
|
|
37
|
+
*/
|
|
38
|
+
import { adjudicate } from "./adjudicator.js";
|
|
39
|
+
/**
|
|
40
|
+
* Total mapping from every {@link AdjudicationReasonCode} to its CONTRADICTED disposition
|
|
41
|
+
* class. Totality is compile-time enforced: adding a new reason code without classifying
|
|
42
|
+
* it here is a type error, so a new code can never silently fall into an unintended
|
|
43
|
+
* disposition. Codes that cannot in practice accompany a `CONTRADICTED` verdict (e.g.
|
|
44
|
+
* `ALL_CHECKS_PASSED`, `RUN_NOT_TERMINAL`) still need a row; they take the conservative
|
|
45
|
+
* default (`retry_same_topology`).
|
|
46
|
+
*/
|
|
47
|
+
const CONTRADICTED_DISPOSITION_CLASS = {
|
|
48
|
+
// Part 3 §1: a scope violation is a safety signal that must never silently auto-retry.
|
|
49
|
+
SCOPE_VIOLATION: "escalate",
|
|
50
|
+
// Part 3 §1: recurring schema/shape drift means the topology/plan template itself may be
|
|
51
|
+
// structurally wrong for the payload - reroute instead of retrying the same topology.
|
|
52
|
+
SCHEMA_DRIFT: "reroute_on_recurrence",
|
|
53
|
+
CONTENT_CHECK_FAILED: "reroute_on_recurrence",
|
|
54
|
+
// Default CONTRADICTED row: same-topology retry (subject to the attempt budget).
|
|
55
|
+
NO_EVIDENCE_ON_SUCCESS: "retry_same_topology",
|
|
56
|
+
EMPTY_ARTIFACT_CONTENT: "retry_same_topology",
|
|
57
|
+
TRACE_CHECK_FAILED: "retry_same_topology",
|
|
58
|
+
TRACE_ERROR_SPAN: "retry_same_topology",
|
|
59
|
+
MIN_ACTIONS_UNMET: "retry_same_topology",
|
|
60
|
+
RUN_STATUS_UNPARSEABLE: "retry_same_topology",
|
|
61
|
+
RUN_FETCH_FAILED: "retry_same_topology",
|
|
62
|
+
MALFORMED_REQUEST: "retry_same_topology",
|
|
63
|
+
RUN_NOT_TERMINAL: "retry_same_topology",
|
|
64
|
+
EVIDENCE_EMPTY: "retry_same_topology",
|
|
65
|
+
FAILURE_REPORTED: "retry_same_topology",
|
|
66
|
+
ALL_CHECKS_PASSED: "retry_same_topology",
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Projects a Part 2 record-level {@link AdjudicationResult} onto a Part 1 packet
|
|
70
|
+
* disposition, per Part 3 §1's mapping table exactly:
|
|
71
|
+
*
|
|
72
|
+
* | verdict | condition | targetState | nextActionKind |
|
|
73
|
+
* |---|---|---|---|
|
|
74
|
+
* | `CONFIRMED` | - | `CONFIRMED` | `none` |
|
|
75
|
+
* | `CORROBORATED-FAILURE` | - | `DECLINED` | `retry_same_topology` |
|
|
76
|
+
* | `CONTRADICTED` | `reason_code` = `SCOPE_VIOLATION` | `DECLINED` | `escalate` |
|
|
77
|
+
* | `CONTRADICTED` | `reason_code` ∈ {`SCHEMA_DRIFT`, `CONTENT_CHECK_FAILED`} AND `packet.retry_count >= 1` | `DECLINED` | `reroute` |
|
|
78
|
+
* | `CONTRADICTED` | any other `reason_code` | `DECLINED` | `retry_same_topology` |
|
|
79
|
+
* | `INDETERMINATE` | - | `DECLINED` | `escalate` |
|
|
80
|
+
* | `VERIFIER-ERROR` | - | `DECLINED` | `escalate` |
|
|
81
|
+
*
|
|
82
|
+
* Branching is driven exclusively by the structured `reason_code`
|
|
83
|
+
* ({@link CONTRADICTED_DISPOSITION_CLASS}); the free-text `reason` is never inspected, so
|
|
84
|
+
* incidental wording in a check's human-readable explanation (e.g. "scoped variable")
|
|
85
|
+
* can neither trigger a false escalation nor mask a real one.
|
|
86
|
+
*
|
|
87
|
+
* `VERIFIER-ERROR` vs. `ADJUDICATION_FAILED` (Part 3's dedicated section, restated here
|
|
88
|
+
* because callers of this function must not conflate the two): `VERIFIER-ERROR` is a verdict
|
|
89
|
+
* the OA *returned normally* - `adjudicate()` completed, produced a full
|
|
90
|
+
* {@link AdjudicationResult}, and that result's own content says the OA's checks could not be
|
|
91
|
+
* trusted for this record. This function only ever runs on such a completed result, so it
|
|
92
|
+
* only ever needs to route `VERIFIER-ERROR` to `escalate` - never to a retry, and never
|
|
93
|
+
* through `ADJUDICATION_FAILED`. `ADJUDICATION_FAILED` (Part 1 §2 state 9), by contrast, is
|
|
94
|
+
* not a verdict at all: it is what happens when `adjudicate()` itself throws or times out and
|
|
95
|
+
* *never returns a verdict in the first place* - there is no `AdjudicationResult` for this
|
|
96
|
+
* function to project, because the mapping's input does not exist. That case is handled
|
|
97
|
+
* separately by {@link runAdjudicationWithTimeout} (detecting the failure) and
|
|
98
|
+
* {@link handleAdjudicationFailure} (deciding the packet's next state), never by this
|
|
99
|
+
* function.
|
|
100
|
+
*
|
|
101
|
+
* Declared `async` for interface consistency with the rest of the verdict-handling pipeline
|
|
102
|
+
* (all of whose other entry points are asynchronous); the projection logic itself is pure and
|
|
103
|
+
* synchronous.
|
|
104
|
+
*/
|
|
105
|
+
export async function projectVerdictToDisposition(result, packet) {
|
|
106
|
+
switch (result.verdict) {
|
|
107
|
+
case "CONFIRMED":
|
|
108
|
+
return { targetState: "CONFIRMED", nextActionKind: "none" };
|
|
109
|
+
case "CORROBORATED-FAILURE":
|
|
110
|
+
return { targetState: "DECLINED", nextActionKind: "retry_same_topology" };
|
|
111
|
+
case "CONTRADICTED": {
|
|
112
|
+
const dispositionClass = CONTRADICTED_DISPOSITION_CLASS[result.reason_code];
|
|
113
|
+
switch (dispositionClass) {
|
|
114
|
+
case "escalate":
|
|
115
|
+
return { targetState: "DECLINED", nextActionKind: "escalate" };
|
|
116
|
+
case "reroute_on_recurrence":
|
|
117
|
+
// "Recurring" is approximated as "this packet has already had at least
|
|
118
|
+
// one prior dispatch attempt" (packet.retry_count >= 1), per Part 3 §1's
|
|
119
|
+
// "recurring across >= 2 attempts for this packet" condition. A first
|
|
120
|
+
// occurrence still takes the default same-topology retry.
|
|
121
|
+
if (packet.retry_count >= 1) {
|
|
122
|
+
return { targetState: "DECLINED", nextActionKind: "reroute" };
|
|
123
|
+
}
|
|
124
|
+
return { targetState: "DECLINED", nextActionKind: "retry_same_topology" };
|
|
125
|
+
case "retry_same_topology":
|
|
126
|
+
return { targetState: "DECLINED", nextActionKind: "retry_same_topology" };
|
|
127
|
+
default: {
|
|
128
|
+
const exhaustive = dispositionClass;
|
|
129
|
+
throw new Error(`unhandled CONTRADICTED disposition class: ${String(exhaustive)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
case "INDETERMINATE":
|
|
134
|
+
return { targetState: "DECLINED", nextActionKind: "escalate" };
|
|
135
|
+
case "VERIFIER-ERROR":
|
|
136
|
+
// Hard rule, not a default: VERIFIER-ERROR must never be
|
|
137
|
+
// retry_same_topology or reroute. See the VERIFIER-ERROR vs.
|
|
138
|
+
// ADJUDICATION_FAILED discussion above.
|
|
139
|
+
return { targetState: "DECLINED", nextActionKind: "escalate" };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Runs `adjudicate()` under both a timeout and a try/catch, per Part 1 §2 state 9 / Part 3's
|
|
144
|
+
* "ADJUDICATION_FAILED" trigger condition. Returns `{ ok: true, result }` when `adjudicate()`
|
|
145
|
+
* returns a verdict within `timeoutMs`; returns `{ ok: false, error }` if it throws, rejects,
|
|
146
|
+
* or does not settle before the timeout - this is the ADJUDICATION_FAILED condition, and the
|
|
147
|
+
* only way this function reports failure. It never lets a hung or throwing `adjudicate()`
|
|
148
|
+
* call escape uncaught.
|
|
149
|
+
*
|
|
150
|
+
* Note the distinction from {@link projectVerdictToDisposition}'s `VERIFIER-ERROR` handling:
|
|
151
|
+
* a `VERIFIER-ERROR` result here still comes back as `{ ok: true, result }` (a verdict was
|
|
152
|
+
* produced), and must be routed through `projectVerdictToDisposition`, not through
|
|
153
|
+
* {@link handleAdjudicationFailure}.
|
|
154
|
+
*/
|
|
155
|
+
export async function runAdjudicationWithTimeout(request, client, registry, timeoutMs) {
|
|
156
|
+
let timer;
|
|
157
|
+
try {
|
|
158
|
+
const result = await Promise.race([
|
|
159
|
+
adjudicate(request, client, registry),
|
|
160
|
+
new Promise((_resolve, reject) => {
|
|
161
|
+
timer = setTimeout(() => {
|
|
162
|
+
reject(new Error(`adjudicate() call timed out after ${timeoutMs}ms`));
|
|
163
|
+
}, timeoutMs);
|
|
164
|
+
}),
|
|
165
|
+
]);
|
|
166
|
+
return { ok: true, result };
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
if (timer !== undefined)
|
|
173
|
+
clearTimeout(timer);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Decides the packet's next state given an already-projected {@link VerdictDisposition}
|
|
178
|
+
* (Part 1 §2/§4, Part 3 §1's `DECLINED -> {RETRY_QUEUED | CLOSED | ESCALATED}` branch). Pure
|
|
179
|
+
* and side-effect free; does not itself call {@link applyTransition} from
|
|
180
|
+
* `state-machine.ts` - callers apply the returned state via that function.
|
|
181
|
+
*
|
|
182
|
+
* - `nextActionKind === 'escalate'` always wins, regardless of remaining budget (Part 3 §1:
|
|
183
|
+
* escalation "takes priority over the attempt-budget check below, regardless of whether
|
|
184
|
+
* budget remains").
|
|
185
|
+
* - `targetState === 'CONFIRMED'` returns `'CONFIRMED'` directly; the caller is responsible
|
|
186
|
+
* for the separate `CONFIRMED -> CLOSED` transition (Part 1's success-close path), which is
|
|
187
|
+
* not this function's concern.
|
|
188
|
+
* - Otherwise (`retry_same_topology` or `reroute`), the per-packet attempt budget (Part 1 §4)
|
|
189
|
+
* decides between `RETRY_QUEUED` (budget remains) and `CLOSED` (budget exhausted -
|
|
190
|
+
* abandoned).
|
|
191
|
+
*/
|
|
192
|
+
export function decideNextTransition(packet, disposition) {
|
|
193
|
+
if (disposition.nextActionKind === "escalate") {
|
|
194
|
+
return "ESCALATED";
|
|
195
|
+
}
|
|
196
|
+
if (disposition.targetState === "CONFIRMED") {
|
|
197
|
+
return "CONFIRMED";
|
|
198
|
+
}
|
|
199
|
+
return packet.retry_count < packet.attempt_budget.max_dispatch_attempts ? "RETRY_QUEUED" : "CLOSED";
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Decides the packet's next state after an `ADJUDICATION_FAILED` outcome (Part 1 §2 state 9;
|
|
203
|
+
* see {@link runAdjudicationWithTimeout} for how that outcome is detected). This is the
|
|
204
|
+
* *other* failure surface from `VERIFIER-ERROR` - there is no `AdjudicationResult` here, so
|
|
205
|
+
* this function is never fed through {@link projectVerdictToDisposition} or
|
|
206
|
+
* {@link decideNextTransition}.
|
|
207
|
+
*
|
|
208
|
+
* `adjudicationCallAttempts` / `maxAdjudicationCallAttempts` track a bounded counter for
|
|
209
|
+
* retrying the `adjudicate()` *call itself* - distinct from, and never conflated with, the
|
|
210
|
+
* packet's `retry_count` / `attempt_budget.max_dispatch_attempts` (Part 1 §7 notes this as a
|
|
211
|
+
* separate persisted field, `adjudication_retry_count`, for exactly this purpose). Returns
|
|
212
|
+
* `'UNDER_REVIEW'` (retry the call, against the already-pulled evidence) while the counter has
|
|
213
|
+
* not been exhausted, else `'ESCALATED'` once it has.
|
|
214
|
+
*/
|
|
215
|
+
export function handleAdjudicationFailure(packet, adjudicationCallAttempts, maxAdjudicationCallAttempts) {
|
|
216
|
+
void packet;
|
|
217
|
+
return adjudicationCallAttempts < maxAdjudicationCallAttempts ? "UNDER_REVIEW" : "ESCALATED";
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=loop.js.map
|
package/dist/loop.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.js","sourceRoot":"","sources":["../src/loop.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Verdict-to-disposition integration layer (Part 3, `final-part3-integration.md`).\n *\n * This file implements exactly the bridge Part 3 defines between Part 1 (core algorithm,\n * `final-part1-core-algorithm.md`) and Part 2 (verification layer,\n * `final-part2-verification-layer.md`): projecting Part 2's five-state record-level\n * `AdjudicationResult.verdict` onto Part 1's packet disposition vocabulary, and the two\n * distinct failure-handling paths (`VERIFIER-ERROR` vs. `ADJUDICATION_FAILED`) Part 3's\n * \"VERIFIER-ERROR vs. ADJUDICATION_FAILED\" section keeps separate.\n *\n * What is implemented here (Part 3 §1, §2 note, §3 note):\n * - {@link projectVerdictToDisposition} - Part 3 §1's mapping table from a *returned*\n * `AdjudicationResult` to a `{ targetState, nextActionKind }` disposition.\n * - {@link runAdjudicationWithTimeout} - the timeout/try-catch wrapper around `adjudicate()`\n * itself (Part 1 §2 state 9 / Part 3's \"ADJUDICATION_FAILED\" trigger condition), so a hung\n * or throwing `adjudicate()` call never escapes uncaught and never produces a verdict to\n * project in the first place.\n * - {@link decideNextTransition} - the `DECLINED -> {RETRY_QUEUED | CLOSED | ESCALATED}`\n * branch (Part 1 §2/§4, Part 3 §1), keyed on the per-packet attempt budget.\n * - {@link handleAdjudicationFailure} - the `ADJUDICATION_FAILED -> {UNDER_REVIEW |\n * ESCALATED}` branch (Part 1 §2 state 9), keyed on a *separate* bounded adjudication-call\n * retry counter, never the packet's attempt/dispatch budget.\n *\n * What is explicitly out of scope for this file, per the design docs' own module boundaries\n * (Part 1's introduction puts the integration boundary itself out of its scope; Part 3 §2\n * assigns request assembly to the core-algorithm/loop side as an already-specified\n * responsibility distinct from this projection):\n * - The end-to-end AdaptOrch dispatch loop: `DRAFTED -> ROUTED -> AWAITING_APPROVAL ->\n * DISPATCHED -> ACTIVE`, topology routing, actual `adaptorch_run` submission, and polling\n * via `adaptorch_get_run`/reconciliation sweeps.\n * - Assembling an `AdjudicationRequest` from a Work Packet's `dispatch_records[]` (Part 3\n * §2's \"one minor shape note\") - callers of {@link runAdjudicationWithTimeout} supply an\n * already-built `AdjudicationRequest`.\n * - The on-disk persistence layer (`packet_index.json`, `run_map.json`, `loop_state.json`,\n * the `adjudications/` archive, Part 1 §7) - the functions here are pure/async-pure and\n * return decisions; they do not read or write any of those files themselves.\n */\n\nimport type { AdaptOrchClient } from \"./adaptorch-client.ts\";\nimport type { AdjudicationRequest, AdjudicationResult } from \"./adjudicator.ts\";\nimport { adjudicate } from \"./adjudicator.ts\";\nimport type { AdjudicationReasonCode, VerifierRegistryEntry } from \"./adjudicator-registry.ts\";\nimport type { WorkPacket, WorkPacketState } from \"./types.ts\";\n\n/**\n * How a `CONTRADICTED` verdict with a given reason code is dispositioned (Part 3 §1's\n * three CONTRADICTED rows). Kept as a named class so {@link CONTRADICTED_DISPOSITION_CLASS}\n * stays a *total* mapping over {@link AdjudicationReasonCode}.\n */\ntype ContradictedDispositionClass = \"escalate\" | \"reroute_on_recurrence\" | \"retry_same_topology\";\n\n/**\n * Total mapping from every {@link AdjudicationReasonCode} to its CONTRADICTED disposition\n * class. Totality is compile-time enforced: adding a new reason code without classifying\n * it here is a type error, so a new code can never silently fall into an unintended\n * disposition. Codes that cannot in practice accompany a `CONTRADICTED` verdict (e.g.\n * `ALL_CHECKS_PASSED`, `RUN_NOT_TERMINAL`) still need a row; they take the conservative\n * default (`retry_same_topology`).\n */\nconst CONTRADICTED_DISPOSITION_CLASS: Record<AdjudicationReasonCode, ContradictedDispositionClass> = {\n\t// Part 3 §1: a scope violation is a safety signal that must never silently auto-retry.\n\tSCOPE_VIOLATION: \"escalate\",\n\t// Part 3 §1: recurring schema/shape drift means the topology/plan template itself may be\n\t// structurally wrong for the payload - reroute instead of retrying the same topology.\n\tSCHEMA_DRIFT: \"reroute_on_recurrence\",\n\tCONTENT_CHECK_FAILED: \"reroute_on_recurrence\",\n\t// Default CONTRADICTED row: same-topology retry (subject to the attempt budget).\n\tNO_EVIDENCE_ON_SUCCESS: \"retry_same_topology\",\n\tEMPTY_ARTIFACT_CONTENT: \"retry_same_topology\",\n\tTRACE_CHECK_FAILED: \"retry_same_topology\",\n\tTRACE_ERROR_SPAN: \"retry_same_topology\",\n\tMIN_ACTIONS_UNMET: \"retry_same_topology\",\n\tRUN_STATUS_UNPARSEABLE: \"retry_same_topology\",\n\tRUN_FETCH_FAILED: \"retry_same_topology\",\n\tMALFORMED_REQUEST: \"retry_same_topology\",\n\tRUN_NOT_TERMINAL: \"retry_same_topology\",\n\tEVIDENCE_EMPTY: \"retry_same_topology\",\n\tFAILURE_REPORTED: \"retry_same_topology\",\n\tALL_CHECKS_PASSED: \"retry_same_topology\",\n};\n\n/**\n * Part 1 §6's `next_action` vocabulary, restricted to the values Part 3 §1's projection\n * table actually produces (`'none'` covers the `CONFIRMED` row, which carries no\n * `next_action` of its own).\n */\nexport type NextActionKind = \"retry_same_topology\" | \"reroute\" | \"escalate\" | \"none\";\n\n/**\n * The projected disposition for a single record-level `AdjudicationResult` (Part 3 §1):\n * the Part 1 §2 state the packet should move towards, and which further-attempt behavior\n * (if any) that implies.\n */\nexport interface VerdictDisposition {\n\ttargetState: WorkPacketState;\n\tnextActionKind: NextActionKind;\n}\n\n/**\n * Projects a Part 2 record-level {@link AdjudicationResult} onto a Part 1 packet\n * disposition, per Part 3 §1's mapping table exactly:\n *\n * | verdict | condition | targetState | nextActionKind |\n * |---|---|---|---|\n * | `CONFIRMED` | - | `CONFIRMED` | `none` |\n * | `CORROBORATED-FAILURE` | - | `DECLINED` | `retry_same_topology` |\n * | `CONTRADICTED` | `reason_code` = `SCOPE_VIOLATION` | `DECLINED` | `escalate` |\n * | `CONTRADICTED` | `reason_code` ∈ {`SCHEMA_DRIFT`, `CONTENT_CHECK_FAILED`} AND `packet.retry_count >= 1` | `DECLINED` | `reroute` |\n * | `CONTRADICTED` | any other `reason_code` | `DECLINED` | `retry_same_topology` |\n * | `INDETERMINATE` | - | `DECLINED` | `escalate` |\n * | `VERIFIER-ERROR` | - | `DECLINED` | `escalate` |\n *\n * Branching is driven exclusively by the structured `reason_code`\n * ({@link CONTRADICTED_DISPOSITION_CLASS}); the free-text `reason` is never inspected, so\n * incidental wording in a check's human-readable explanation (e.g. \"scoped variable\")\n * can neither trigger a false escalation nor mask a real one.\n *\n * `VERIFIER-ERROR` vs. `ADJUDICATION_FAILED` (Part 3's dedicated section, restated here\n * because callers of this function must not conflate the two): `VERIFIER-ERROR` is a verdict\n * the OA *returned normally* - `adjudicate()` completed, produced a full\n * {@link AdjudicationResult}, and that result's own content says the OA's checks could not be\n * trusted for this record. This function only ever runs on such a completed result, so it\n * only ever needs to route `VERIFIER-ERROR` to `escalate` - never to a retry, and never\n * through `ADJUDICATION_FAILED`. `ADJUDICATION_FAILED` (Part 1 §2 state 9), by contrast, is\n * not a verdict at all: it is what happens when `adjudicate()` itself throws or times out and\n * *never returns a verdict in the first place* - there is no `AdjudicationResult` for this\n * function to project, because the mapping's input does not exist. That case is handled\n * separately by {@link runAdjudicationWithTimeout} (detecting the failure) and\n * {@link handleAdjudicationFailure} (deciding the packet's next state), never by this\n * function.\n *\n * Declared `async` for interface consistency with the rest of the verdict-handling pipeline\n * (all of whose other entry points are asynchronous); the projection logic itself is pure and\n * synchronous.\n */\nexport async function projectVerdictToDisposition(\n\tresult: AdjudicationResult,\n\tpacket: WorkPacket,\n): Promise<VerdictDisposition> {\n\tswitch (result.verdict) {\n\t\tcase \"CONFIRMED\":\n\t\t\treturn { targetState: \"CONFIRMED\", nextActionKind: \"none\" };\n\n\t\tcase \"CORROBORATED-FAILURE\":\n\t\t\treturn { targetState: \"DECLINED\", nextActionKind: \"retry_same_topology\" };\n\n\t\tcase \"CONTRADICTED\": {\n\t\t\tconst dispositionClass = CONTRADICTED_DISPOSITION_CLASS[result.reason_code];\n\t\t\tswitch (dispositionClass) {\n\t\t\t\tcase \"escalate\":\n\t\t\t\t\treturn { targetState: \"DECLINED\", nextActionKind: \"escalate\" };\n\t\t\t\tcase \"reroute_on_recurrence\":\n\t\t\t\t\t// \"Recurring\" is approximated as \"this packet has already had at least\n\t\t\t\t\t// one prior dispatch attempt\" (packet.retry_count >= 1), per Part 3 §1's\n\t\t\t\t\t// \"recurring across >= 2 attempts for this packet\" condition. A first\n\t\t\t\t\t// occurrence still takes the default same-topology retry.\n\t\t\t\t\tif (packet.retry_count >= 1) {\n\t\t\t\t\t\treturn { targetState: \"DECLINED\", nextActionKind: \"reroute\" };\n\t\t\t\t\t}\n\t\t\t\t\treturn { targetState: \"DECLINED\", nextActionKind: \"retry_same_topology\" };\n\t\t\t\tcase \"retry_same_topology\":\n\t\t\t\t\treturn { targetState: \"DECLINED\", nextActionKind: \"retry_same_topology\" };\n\t\t\t\tdefault: {\n\t\t\t\t\tconst exhaustive: never = dispositionClass;\n\t\t\t\t\tthrow new Error(`unhandled CONTRADICTED disposition class: ${String(exhaustive)}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcase \"INDETERMINATE\":\n\t\t\treturn { targetState: \"DECLINED\", nextActionKind: \"escalate\" };\n\n\t\tcase \"VERIFIER-ERROR\":\n\t\t\t// Hard rule, not a default: VERIFIER-ERROR must never be\n\t\t\t// retry_same_topology or reroute. See the VERIFIER-ERROR vs.\n\t\t\t// ADJUDICATION_FAILED discussion above.\n\t\t\treturn { targetState: \"DECLINED\", nextActionKind: \"escalate\" };\n\t}\n}\n\n/**\n * Runs `adjudicate()` under both a timeout and a try/catch, per Part 1 §2 state 9 / Part 3's\n * \"ADJUDICATION_FAILED\" trigger condition. Returns `{ ok: true, result }` when `adjudicate()`\n * returns a verdict within `timeoutMs`; returns `{ ok: false, error }` if it throws, rejects,\n * or does not settle before the timeout - this is the ADJUDICATION_FAILED condition, and the\n * only way this function reports failure. It never lets a hung or throwing `adjudicate()`\n * call escape uncaught.\n *\n * Note the distinction from {@link projectVerdictToDisposition}'s `VERIFIER-ERROR` handling:\n * a `VERIFIER-ERROR` result here still comes back as `{ ok: true, result }` (a verdict was\n * produced), and must be routed through `projectVerdictToDisposition`, not through\n * {@link handleAdjudicationFailure}.\n */\nexport async function runAdjudicationWithTimeout(\n\trequest: AdjudicationRequest,\n\tclient: AdaptOrchClient,\n\tregistry: { get(kind: string): VerifierRegistryEntry },\n\ttimeoutMs: number,\n): Promise<{ ok: true; result: AdjudicationResult } | { ok: false; error: string }> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\ttry {\n\t\tconst result = await Promise.race([\n\t\t\tadjudicate(request, client, registry),\n\t\t\tnew Promise<never>((_resolve, reject) => {\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\treject(new Error(`adjudicate() call timed out after ${timeoutMs}ms`));\n\t\t\t\t}, timeoutMs);\n\t\t\t}),\n\t\t]);\n\t\treturn { ok: true, result };\n\t} catch (error) {\n\t\treturn { ok: false, error: error instanceof Error ? error.message : String(error) };\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t}\n}\n\n/**\n * Decides the packet's next state given an already-projected {@link VerdictDisposition}\n * (Part 1 §2/§4, Part 3 §1's `DECLINED -> {RETRY_QUEUED | CLOSED | ESCALATED}` branch). Pure\n * and side-effect free; does not itself call {@link applyTransition} from\n * `state-machine.ts` - callers apply the returned state via that function.\n *\n * - `nextActionKind === 'escalate'` always wins, regardless of remaining budget (Part 3 §1:\n * escalation \"takes priority over the attempt-budget check below, regardless of whether\n * budget remains\").\n * - `targetState === 'CONFIRMED'` returns `'CONFIRMED'` directly; the caller is responsible\n * for the separate `CONFIRMED -> CLOSED` transition (Part 1's success-close path), which is\n * not this function's concern.\n * - Otherwise (`retry_same_topology` or `reroute`), the per-packet attempt budget (Part 1 §4)\n * decides between `RETRY_QUEUED` (budget remains) and `CLOSED` (budget exhausted -\n * abandoned).\n */\nexport function decideNextTransition(packet: WorkPacket, disposition: VerdictDisposition): WorkPacketState {\n\tif (disposition.nextActionKind === \"escalate\") {\n\t\treturn \"ESCALATED\";\n\t}\n\n\tif (disposition.targetState === \"CONFIRMED\") {\n\t\treturn \"CONFIRMED\";\n\t}\n\n\treturn packet.retry_count < packet.attempt_budget.max_dispatch_attempts ? \"RETRY_QUEUED\" : \"CLOSED\";\n}\n\n/**\n * Decides the packet's next state after an `ADJUDICATION_FAILED` outcome (Part 1 §2 state 9;\n * see {@link runAdjudicationWithTimeout} for how that outcome is detected). This is the\n * *other* failure surface from `VERIFIER-ERROR` - there is no `AdjudicationResult` here, so\n * this function is never fed through {@link projectVerdictToDisposition} or\n * {@link decideNextTransition}.\n *\n * `adjudicationCallAttempts` / `maxAdjudicationCallAttempts` track a bounded counter for\n * retrying the `adjudicate()` *call itself* - distinct from, and never conflated with, the\n * packet's `retry_count` / `attempt_budget.max_dispatch_attempts` (Part 1 §7 notes this as a\n * separate persisted field, `adjudication_retry_count`, for exactly this purpose). Returns\n * `'UNDER_REVIEW'` (retry the call, against the already-pulled evidence) while the counter has\n * not been exhausted, else `'ESCALATED'` once it has.\n */\nexport function handleAdjudicationFailure(\n\tpacket: WorkPacket,\n\tadjudicationCallAttempts: number,\n\tmaxAdjudicationCallAttempts: number,\n): WorkPacketState {\n\tvoid packet;\n\treturn adjudicationCallAttempts < maxAdjudicationCallAttempts ? \"UNDER_REVIEW\" : \"ESCALATED\";\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdaptOrch transport that maps introspection tools to fixture or no-op (Wave 2 stub).
|
|
3
|
+
* Production MCP wiring replaces this adapter without changing evaluateCorrectnessWall.
|
|
4
|
+
*/
|
|
5
|
+
import { AdaptOrchClient } from "./adaptorch-client.ts";
|
|
6
|
+
import type { InMemoryAdaptOrchFixture } from "./in-memory-adaptorch.ts";
|
|
7
|
+
export type McpIntrospectionMode = "fixture" | "unavailable";
|
|
8
|
+
export interface CreateMcpIntrospectionTransportOptions {
|
|
9
|
+
mode: McpIntrospectionMode;
|
|
10
|
+
/** Required when mode is fixture. */
|
|
11
|
+
fixture?: InMemoryAdaptOrchFixture;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns an {@link AdaptOrchClient} backed by fixture runs, or one that fails introspection
|
|
15
|
+
* with a stable error when mode is unavailable (no network).
|
|
16
|
+
*/
|
|
17
|
+
export declare function createMcpIntrospectionClient(options: CreateMcpIntrospectionTransportOptions): AdaptOrchClient;
|
|
18
|
+
//# sourceMappingURL=mcp-introspection-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-introspection-transport.d.ts","sourceRoot":"","sources":["../src/mcp-introspection-transport.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAA2B,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAKzE,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,aAAa,CAAC;AAE7D,MAAM,WAAW,sCAAsC;IACtD,IAAI,EAAE,oBAAoB,CAAC;IAC3B,qCAAqC;IACrC,OAAO,CAAC,EAAE,wBAAwB,CAAC;CACnC;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,sCAAsC,GAAG,eAAe,CAgB7G","sourcesContent":["/**\n * AdaptOrch transport that maps introspection tools to fixture or no-op (Wave 2 stub).\n * Production MCP wiring replaces this adapter without changing evaluateCorrectnessWall.\n */\n\nimport { AdaptOrchClient, type AdaptOrchTransport } from \"./adaptorch-client.ts\";\nimport type { InMemoryAdaptOrchFixture } from \"./in-memory-adaptorch.ts\";\nimport { createInMemoryAdaptOrchClient } from \"./in-memory-adaptorch.ts\";\n\nconst INTROSPECTION_TOOLS = new Set([\"adaptorch_get_run\", \"adaptorch_get_artifacts\", \"adaptorch_get_traces\"]);\n\nexport type McpIntrospectionMode = \"fixture\" | \"unavailable\";\n\nexport interface CreateMcpIntrospectionTransportOptions {\n\tmode: McpIntrospectionMode;\n\t/** Required when mode is fixture. */\n\tfixture?: InMemoryAdaptOrchFixture;\n}\n\n/**\n * Returns an {@link AdaptOrchClient} backed by fixture runs, or one that fails introspection\n * with a stable error when mode is unavailable (no network).\n */\nexport function createMcpIntrospectionClient(options: CreateMcpIntrospectionTransportOptions): AdaptOrchClient {\n\tif (options.mode === \"fixture\") {\n\t\tif (options.fixture === undefined) {\n\t\t\tthrow new Error(\"fixture is required when mode is fixture\");\n\t\t}\n\t\treturn createInMemoryAdaptOrchClient(options.fixture);\n\t}\n\tconst transport: AdaptOrchTransport = {\n\t\tasync callTool(name: string, _args: Record<string, unknown>) {\n\t\t\tif (INTROSPECTION_TOOLS.has(name)) {\n\t\t\t\tthrow new Error(\"AdaptOrch MCP transport unavailable (configure MCP or use OA fixture)\");\n\t\t\t}\n\t\t\tthrow new Error(`unexpected AdaptOrch tool call in unavailable mode: ${name}`);\n\t\t},\n\t};\n\treturn new AdaptOrchClient(transport);\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdaptOrch transport that maps introspection tools to fixture or no-op (Wave 2 stub).
|
|
3
|
+
* Production MCP wiring replaces this adapter without changing evaluateCorrectnessWall.
|
|
4
|
+
*/
|
|
5
|
+
import { AdaptOrchClient } from "./adaptorch-client.js";
|
|
6
|
+
import { createInMemoryAdaptOrchClient } from "./in-memory-adaptorch.js";
|
|
7
|
+
const INTROSPECTION_TOOLS = new Set(["adaptorch_get_run", "adaptorch_get_artifacts", "adaptorch_get_traces"]);
|
|
8
|
+
/**
|
|
9
|
+
* Returns an {@link AdaptOrchClient} backed by fixture runs, or one that fails introspection
|
|
10
|
+
* with a stable error when mode is unavailable (no network).
|
|
11
|
+
*/
|
|
12
|
+
export function createMcpIntrospectionClient(options) {
|
|
13
|
+
if (options.mode === "fixture") {
|
|
14
|
+
if (options.fixture === undefined) {
|
|
15
|
+
throw new Error("fixture is required when mode is fixture");
|
|
16
|
+
}
|
|
17
|
+
return createInMemoryAdaptOrchClient(options.fixture);
|
|
18
|
+
}
|
|
19
|
+
const transport = {
|
|
20
|
+
async callTool(name, _args) {
|
|
21
|
+
if (INTROSPECTION_TOOLS.has(name)) {
|
|
22
|
+
throw new Error("AdaptOrch MCP transport unavailable (configure MCP or use OA fixture)");
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`unexpected AdaptOrch tool call in unavailable mode: ${name}`);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
return new AdaptOrchClient(transport);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=mcp-introspection-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-introspection-transport.js","sourceRoot":"","sources":["../src/mcp-introspection-transport.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAA2B,MAAM,uBAAuB,CAAC;AAEjF,OAAO,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAEzE,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,mBAAmB,EAAE,yBAAyB,EAAE,sBAAsB,CAAC,CAAC,CAAC;AAU9G;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAAC,OAA+C,EAAmB;IAC9G,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAChC,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,6BAA6B,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;IACD,MAAM,SAAS,GAAuB;QACrC,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,KAA8B,EAAE;YAC5D,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;YAC1F,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,uDAAuD,IAAI,EAAE,CAAC,CAAC;QAAA,CAC/E;KACD,CAAC;IACF,OAAO,IAAI,eAAe,CAAC,SAAS,CAAC,CAAC;AAAA,CACtC","sourcesContent":["/**\n * AdaptOrch transport that maps introspection tools to fixture or no-op (Wave 2 stub).\n * Production MCP wiring replaces this adapter without changing evaluateCorrectnessWall.\n */\n\nimport { AdaptOrchClient, type AdaptOrchTransport } from \"./adaptorch-client.ts\";\nimport type { InMemoryAdaptOrchFixture } from \"./in-memory-adaptorch.ts\";\nimport { createInMemoryAdaptOrchClient } from \"./in-memory-adaptorch.ts\";\n\nconst INTROSPECTION_TOOLS = new Set([\"adaptorch_get_run\", \"adaptorch_get_artifacts\", \"adaptorch_get_traces\"]);\n\nexport type McpIntrospectionMode = \"fixture\" | \"unavailable\";\n\nexport interface CreateMcpIntrospectionTransportOptions {\n\tmode: McpIntrospectionMode;\n\t/** Required when mode is fixture. */\n\tfixture?: InMemoryAdaptOrchFixture;\n}\n\n/**\n * Returns an {@link AdaptOrchClient} backed by fixture runs, or one that fails introspection\n * with a stable error when mode is unavailable (no network).\n */\nexport function createMcpIntrospectionClient(options: CreateMcpIntrospectionTransportOptions): AdaptOrchClient {\n\tif (options.mode === \"fixture\") {\n\t\tif (options.fixture === undefined) {\n\t\t\tthrow new Error(\"fixture is required when mode is fixture\");\n\t\t}\n\t\treturn createInMemoryAdaptOrchClient(options.fixture);\n\t}\n\tconst transport: AdaptOrchTransport = {\n\t\tasync callTool(name: string, _args: Record<string, unknown>) {\n\t\t\tif (INTROSPECTION_TOOLS.has(name)) {\n\t\t\t\tthrow new Error(\"AdaptOrch MCP transport unavailable (configure MCP or use OA fixture)\");\n\t\t\t}\n\t\t\tthrow new Error(`unexpected AdaptOrch tool call in unavailable mode: ${name}`);\n\t\t},\n\t};\n\treturn new AdaptOrchClient(transport);\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Fast Wall — diff scope and evidence stubs before or alongside OA adjudication.
|
|
3
|
+
*/
|
|
4
|
+
export declare const POLICY_FLAG: {
|
|
5
|
+
readonly NON_NEGOTIABLE_BLOCKING: "NON_NEGOTIABLE_BLOCKING";
|
|
6
|
+
/** Diff lines suggest credentials or env secrets (maps to blocking). */
|
|
7
|
+
readonly SECRET_SUSPECT: "SECRET_SUSPECT";
|
|
8
|
+
readonly CANDIDATE_LEAK_SUSPECT: "CANDIDATE_LEAK_SUSPECT";
|
|
9
|
+
readonly EVIDENCE_DAG_INCOMPLETE: "EVIDENCE_DAG_INCOMPLETE";
|
|
10
|
+
readonly REPRO_OVERFIT_SUSPECT: "REPRO_OVERFIT_SUSPECT";
|
|
11
|
+
readonly LOW_DISCRIMINATION: "LOW_DISCRIMINATION";
|
|
12
|
+
};
|
|
13
|
+
export type PolicyFlag = (typeof POLICY_FLAG)[keyof typeof POLICY_FLAG];
|
|
14
|
+
/** Scan added/changed diff lines for secret-like content. */
|
|
15
|
+
export declare function scanDiffLinesForSecrets(diffText: string | undefined): boolean;
|
|
16
|
+
export interface FastWallInput {
|
|
17
|
+
diffText?: string;
|
|
18
|
+
approvedWriteScope?: string[];
|
|
19
|
+
/** Non-empty run ids expected when not in preview-only mode. */
|
|
20
|
+
runIds?: string[];
|
|
21
|
+
previewOnly?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface FastWallResult {
|
|
24
|
+
flags: PolicyFlag[];
|
|
25
|
+
diffPaths: string[];
|
|
26
|
+
}
|
|
27
|
+
/** Parse changed file paths from a unified diff text. */
|
|
28
|
+
export declare function parseDiffPaths(diffText: string | undefined): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Simple glob: double-star suffix patterns and directory prefix rules.
|
|
31
|
+
*/
|
|
32
|
+
export declare function pathMatchesApprovedScope(filePath: string, approvedWriteScope: string[]): boolean;
|
|
33
|
+
export declare function runFastWall(input: FastWallInput): FastWallResult;
|
|
34
|
+
//# sourceMappingURL=policy-wall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-wall.d.ts","sourceRoot":"","sources":["../src/policy-wall.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,WAAW;;IAEvB,wEAAwE;;;;;;CAM/D,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAexE,6DAA6D;AAC7D,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAgB7E;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,yDAAyD;AACzD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAerE;AAMD;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,GAAG,OAAO,CAyBhG;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAmChE","sourcesContent":["/**\n * Local Fast Wall — diff scope and evidence stubs before or alongside OA adjudication.\n */\n\nexport const POLICY_FLAG = {\n\tNON_NEGOTIABLE_BLOCKING: \"NON_NEGOTIABLE_BLOCKING\",\n\t/** Diff lines suggest credentials or env secrets (maps to blocking). */\n\tSECRET_SUSPECT: \"SECRET_SUSPECT\",\n\tCANDIDATE_LEAK_SUSPECT: \"CANDIDATE_LEAK_SUSPECT\",\n\tEVIDENCE_DAG_INCOMPLETE: \"EVIDENCE_DAG_INCOMPLETE\",\n\tREPRO_OVERFIT_SUSPECT: \"REPRO_OVERFIT_SUSPECT\",\n\tLOW_DISCRIMINATION: \"LOW_DISCRIMINATION\",\n} as const;\n\nexport type PolicyFlag = (typeof POLICY_FLAG)[keyof typeof POLICY_FLAG];\n\nconst SECRET_LINE_PATTERNS: RegExp[] = [\n\t/\\.env(?:\\.|$|\\b)/i,\n\t/BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY/i,\n\t/api[_-]?key\\s*=/i,\n\t/secret[_-]?key\\s*=/i,\n\t/password\\s*=/i,\n\t// Common provider token shapes (discrimination vs plain identifiers).\n\t/\\bsk-(?:ant|proj|live|test)[-a-z0-9_]{8,}/i,\n\t/\\bsk-[a-z0-9]{20,}/i,\n\t/\\bghp_[A-Za-z0-9]{20,}/,\n\t/\\bxox[baprs]-[A-Za-z0-9-]{10,}/,\n];\n\n/** Scan added/changed diff lines for secret-like content. */\nexport function scanDiffLinesForSecrets(diffText: string | undefined): boolean {\n\tif (diffText === undefined || diffText.trim() === \"\") {\n\t\treturn false;\n\t}\n\tfor (const line of diffText.split(/\\r?\\n/)) {\n\t\tif (!line.startsWith(\"+\") || line.startsWith(\"+++\")) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst body = line.slice(1);\n\t\tfor (const pattern of SECRET_LINE_PATTERNS) {\n\t\t\tif (pattern.test(body)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\nexport interface FastWallInput {\n\tdiffText?: string;\n\tapprovedWriteScope?: string[];\n\t/** Non-empty run ids expected when not in preview-only mode. */\n\trunIds?: string[];\n\tpreviewOnly?: boolean;\n}\n\nexport interface FastWallResult {\n\tflags: PolicyFlag[];\n\tdiffPaths: string[];\n}\n\n/** Parse changed file paths from a unified diff text. */\nexport function parseDiffPaths(diffText: string | undefined): string[] {\n\tif (diffText === undefined || diffText.trim() === \"\") {\n\t\treturn [];\n\t}\n\tconst paths = new Set<string>();\n\tconst lines = diffText.split(/\\r?\\n/);\n\tfor (const line of lines) {\n\t\tif (line.startsWith(\"+++ \")) {\n\t\t\tconst raw = line.slice(4).trim();\n\t\t\tif (raw === \"/dev/null\") continue;\n\t\t\tconst path = raw.startsWith(\"b/\") ? raw.slice(2) : raw;\n\t\t\tif (path.length > 0) paths.add(path);\n\t\t}\n\t}\n\treturn [...paths].sort();\n}\n\nfunction normalizePathForMatch(path: string): string {\n\treturn path.replace(/\\\\/g, \"/\");\n}\n\n/**\n * Simple glob: double-star suffix patterns and directory prefix rules.\n */\nexport function pathMatchesApprovedScope(filePath: string, approvedWriteScope: string[]): boolean {\n\tconst normalized = normalizePathForMatch(filePath);\n\tfor (const pattern of approvedWriteScope) {\n\t\tconst p = normalizePathForMatch(pattern.trim());\n\t\tif (p.length === 0) continue;\n\t\tif (p.endsWith(\"/**\")) {\n\t\t\tconst prefix = p.slice(0, -3);\n\t\t\tif (prefix === \"\" || normalized === prefix || normalized.startsWith(`${prefix}/`)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (p.includes(\"**\")) {\n\t\t\tconst [head] = p.split(\"**\");\n\t\t\tconst prefix = head.endsWith(\"/\") ? head.slice(0, -1) : head;\n\t\t\tif (prefix === \"\" || normalized === prefix || normalized.startsWith(`${prefix}/`)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (normalized === p || normalized.startsWith(`${p}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nexport function runFastWall(input: FastWallInput): FastWallResult {\n\tconst flags: PolicyFlag[] = [];\n\tconst diffPaths = parseDiffPaths(input.diffText);\n\tconst scope = input.approvedWriteScope ?? [];\n\tconst previewOnly = input.previewOnly === true;\n\tconst runIds = input.runIds ?? [];\n\n\tif (diffPaths.length === 0 && input.diffText !== undefined && input.diffText.trim() !== \"\") {\n\t\t// diff present but no paths parsed — treat as inconclusive via caller, not a flag here\n\t}\n\n\tif (scope.length > 0 && diffPaths.length > 0) {\n\t\tconst outOfScope = diffPaths.filter((p) => !pathMatchesApprovedScope(p, scope));\n\t\tif (outOfScope.length > 0) {\n\t\t\tflags.push(POLICY_FLAG.CANDIDATE_LEAK_SUSPECT);\n\t\t\tflags.push(POLICY_FLAG.NON_NEGOTIABLE_BLOCKING);\n\t\t}\n\t}\n\n\tconst expectsRunEvidence = runIds.length > 0 && !previewOnly;\n\tif (expectsRunEvidence) {\n\t\tflags.push(POLICY_FLAG.EVIDENCE_DAG_INCOMPLETE);\n\t}\n\n\tif (previewOnly) {\n\t\tflags.push(POLICY_FLAG.REPRO_OVERFIT_SUSPECT);\n\t\tflags.push(POLICY_FLAG.LOW_DISCRIMINATION);\n\t}\n\n\tif (scanDiffLinesForSecrets(input.diffText)) {\n\t\tflags.push(POLICY_FLAG.SECRET_SUSPECT);\n\t\tflags.push(POLICY_FLAG.NON_NEGOTIABLE_BLOCKING);\n\t}\n\n\treturn { flags, diffPaths };\n}\n"]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Fast Wall — diff scope and evidence stubs before or alongside OA adjudication.
|
|
3
|
+
*/
|
|
4
|
+
export const POLICY_FLAG = {
|
|
5
|
+
NON_NEGOTIABLE_BLOCKING: "NON_NEGOTIABLE_BLOCKING",
|
|
6
|
+
/** Diff lines suggest credentials or env secrets (maps to blocking). */
|
|
7
|
+
SECRET_SUSPECT: "SECRET_SUSPECT",
|
|
8
|
+
CANDIDATE_LEAK_SUSPECT: "CANDIDATE_LEAK_SUSPECT",
|
|
9
|
+
EVIDENCE_DAG_INCOMPLETE: "EVIDENCE_DAG_INCOMPLETE",
|
|
10
|
+
REPRO_OVERFIT_SUSPECT: "REPRO_OVERFIT_SUSPECT",
|
|
11
|
+
LOW_DISCRIMINATION: "LOW_DISCRIMINATION",
|
|
12
|
+
};
|
|
13
|
+
const SECRET_LINE_PATTERNS = [
|
|
14
|
+
/\.env(?:\.|$|\b)/i,
|
|
15
|
+
/BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY/i,
|
|
16
|
+
/api[_-]?key\s*=/i,
|
|
17
|
+
/secret[_-]?key\s*=/i,
|
|
18
|
+
/password\s*=/i,
|
|
19
|
+
// Common provider token shapes (discrimination vs plain identifiers).
|
|
20
|
+
/\bsk-(?:ant|proj|live|test)[-a-z0-9_]{8,}/i,
|
|
21
|
+
/\bsk-[a-z0-9]{20,}/i,
|
|
22
|
+
/\bghp_[A-Za-z0-9]{20,}/,
|
|
23
|
+
/\bxox[baprs]-[A-Za-z0-9-]{10,}/,
|
|
24
|
+
];
|
|
25
|
+
/** Scan added/changed diff lines for secret-like content. */
|
|
26
|
+
export function scanDiffLinesForSecrets(diffText) {
|
|
27
|
+
if (diffText === undefined || diffText.trim() === "") {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
for (const line of diffText.split(/\r?\n/)) {
|
|
31
|
+
if (!line.startsWith("+") || line.startsWith("+++")) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const body = line.slice(1);
|
|
35
|
+
for (const pattern of SECRET_LINE_PATTERNS) {
|
|
36
|
+
if (pattern.test(body)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
/** Parse changed file paths from a unified diff text. */
|
|
44
|
+
export function parseDiffPaths(diffText) {
|
|
45
|
+
if (diffText === undefined || diffText.trim() === "") {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const paths = new Set();
|
|
49
|
+
const lines = diffText.split(/\r?\n/);
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
if (line.startsWith("+++ ")) {
|
|
52
|
+
const raw = line.slice(4).trim();
|
|
53
|
+
if (raw === "/dev/null")
|
|
54
|
+
continue;
|
|
55
|
+
const path = raw.startsWith("b/") ? raw.slice(2) : raw;
|
|
56
|
+
if (path.length > 0)
|
|
57
|
+
paths.add(path);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return [...paths].sort();
|
|
61
|
+
}
|
|
62
|
+
function normalizePathForMatch(path) {
|
|
63
|
+
return path.replace(/\\/g, "/");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Simple glob: double-star suffix patterns and directory prefix rules.
|
|
67
|
+
*/
|
|
68
|
+
export function pathMatchesApprovedScope(filePath, approvedWriteScope) {
|
|
69
|
+
const normalized = normalizePathForMatch(filePath);
|
|
70
|
+
for (const pattern of approvedWriteScope) {
|
|
71
|
+
const p = normalizePathForMatch(pattern.trim());
|
|
72
|
+
if (p.length === 0)
|
|
73
|
+
continue;
|
|
74
|
+
if (p.endsWith("/**")) {
|
|
75
|
+
const prefix = p.slice(0, -3);
|
|
76
|
+
if (prefix === "" || normalized === prefix || normalized.startsWith(`${prefix}/`)) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (p.includes("**")) {
|
|
82
|
+
const [head] = p.split("**");
|
|
83
|
+
const prefix = head.endsWith("/") ? head.slice(0, -1) : head;
|
|
84
|
+
if (prefix === "" || normalized === prefix || normalized.startsWith(`${prefix}/`)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (normalized === p || normalized.startsWith(`${p}/`)) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
export function runFastWall(input) {
|
|
96
|
+
const flags = [];
|
|
97
|
+
const diffPaths = parseDiffPaths(input.diffText);
|
|
98
|
+
const scope = input.approvedWriteScope ?? [];
|
|
99
|
+
const previewOnly = input.previewOnly === true;
|
|
100
|
+
const runIds = input.runIds ?? [];
|
|
101
|
+
if (diffPaths.length === 0 && input.diffText !== undefined && input.diffText.trim() !== "") {
|
|
102
|
+
// diff present but no paths parsed — treat as inconclusive via caller, not a flag here
|
|
103
|
+
}
|
|
104
|
+
if (scope.length > 0 && diffPaths.length > 0) {
|
|
105
|
+
const outOfScope = diffPaths.filter((p) => !pathMatchesApprovedScope(p, scope));
|
|
106
|
+
if (outOfScope.length > 0) {
|
|
107
|
+
flags.push(POLICY_FLAG.CANDIDATE_LEAK_SUSPECT);
|
|
108
|
+
flags.push(POLICY_FLAG.NON_NEGOTIABLE_BLOCKING);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const expectsRunEvidence = runIds.length > 0 && !previewOnly;
|
|
112
|
+
if (expectsRunEvidence) {
|
|
113
|
+
flags.push(POLICY_FLAG.EVIDENCE_DAG_INCOMPLETE);
|
|
114
|
+
}
|
|
115
|
+
if (previewOnly) {
|
|
116
|
+
flags.push(POLICY_FLAG.REPRO_OVERFIT_SUSPECT);
|
|
117
|
+
flags.push(POLICY_FLAG.LOW_DISCRIMINATION);
|
|
118
|
+
}
|
|
119
|
+
if (scanDiffLinesForSecrets(input.diffText)) {
|
|
120
|
+
flags.push(POLICY_FLAG.SECRET_SUSPECT);
|
|
121
|
+
flags.push(POLICY_FLAG.NON_NEGOTIABLE_BLOCKING);
|
|
122
|
+
}
|
|
123
|
+
return { flags, diffPaths };
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=policy-wall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-wall.js","sourceRoot":"","sources":["../src/policy-wall.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local Fast Wall — diff scope and evidence stubs before or alongside OA adjudication.\n */\n\nexport const POLICY_FLAG = {\n\tNON_NEGOTIABLE_BLOCKING: \"NON_NEGOTIABLE_BLOCKING\",\n\t/** Diff lines suggest credentials or env secrets (maps to blocking). */\n\tSECRET_SUSPECT: \"SECRET_SUSPECT\",\n\tCANDIDATE_LEAK_SUSPECT: \"CANDIDATE_LEAK_SUSPECT\",\n\tEVIDENCE_DAG_INCOMPLETE: \"EVIDENCE_DAG_INCOMPLETE\",\n\tREPRO_OVERFIT_SUSPECT: \"REPRO_OVERFIT_SUSPECT\",\n\tLOW_DISCRIMINATION: \"LOW_DISCRIMINATION\",\n} as const;\n\nexport type PolicyFlag = (typeof POLICY_FLAG)[keyof typeof POLICY_FLAG];\n\nconst SECRET_LINE_PATTERNS: RegExp[] = [\n\t/\\.env(?:\\.|$|\\b)/i,\n\t/BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY/i,\n\t/api[_-]?key\\s*=/i,\n\t/secret[_-]?key\\s*=/i,\n\t/password\\s*=/i,\n\t// Common provider token shapes (discrimination vs plain identifiers).\n\t/\\bsk-(?:ant|proj|live|test)[-a-z0-9_]{8,}/i,\n\t/\\bsk-[a-z0-9]{20,}/i,\n\t/\\bghp_[A-Za-z0-9]{20,}/,\n\t/\\bxox[baprs]-[A-Za-z0-9-]{10,}/,\n];\n\n/** Scan added/changed diff lines for secret-like content. */\nexport function scanDiffLinesForSecrets(diffText: string | undefined): boolean {\n\tif (diffText === undefined || diffText.trim() === \"\") {\n\t\treturn false;\n\t}\n\tfor (const line of diffText.split(/\\r?\\n/)) {\n\t\tif (!line.startsWith(\"+\") || line.startsWith(\"+++\")) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst body = line.slice(1);\n\t\tfor (const pattern of SECRET_LINE_PATTERNS) {\n\t\t\tif (pattern.test(body)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\treturn false;\n}\n\nexport interface FastWallInput {\n\tdiffText?: string;\n\tapprovedWriteScope?: string[];\n\t/** Non-empty run ids expected when not in preview-only mode. */\n\trunIds?: string[];\n\tpreviewOnly?: boolean;\n}\n\nexport interface FastWallResult {\n\tflags: PolicyFlag[];\n\tdiffPaths: string[];\n}\n\n/** Parse changed file paths from a unified diff text. */\nexport function parseDiffPaths(diffText: string | undefined): string[] {\n\tif (diffText === undefined || diffText.trim() === \"\") {\n\t\treturn [];\n\t}\n\tconst paths = new Set<string>();\n\tconst lines = diffText.split(/\\r?\\n/);\n\tfor (const line of lines) {\n\t\tif (line.startsWith(\"+++ \")) {\n\t\t\tconst raw = line.slice(4).trim();\n\t\t\tif (raw === \"/dev/null\") continue;\n\t\t\tconst path = raw.startsWith(\"b/\") ? raw.slice(2) : raw;\n\t\t\tif (path.length > 0) paths.add(path);\n\t\t}\n\t}\n\treturn [...paths].sort();\n}\n\nfunction normalizePathForMatch(path: string): string {\n\treturn path.replace(/\\\\/g, \"/\");\n}\n\n/**\n * Simple glob: double-star suffix patterns and directory prefix rules.\n */\nexport function pathMatchesApprovedScope(filePath: string, approvedWriteScope: string[]): boolean {\n\tconst normalized = normalizePathForMatch(filePath);\n\tfor (const pattern of approvedWriteScope) {\n\t\tconst p = normalizePathForMatch(pattern.trim());\n\t\tif (p.length === 0) continue;\n\t\tif (p.endsWith(\"/**\")) {\n\t\t\tconst prefix = p.slice(0, -3);\n\t\t\tif (prefix === \"\" || normalized === prefix || normalized.startsWith(`${prefix}/`)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (p.includes(\"**\")) {\n\t\t\tconst [head] = p.split(\"**\");\n\t\t\tconst prefix = head.endsWith(\"/\") ? head.slice(0, -1) : head;\n\t\t\tif (prefix === \"\" || normalized === prefix || normalized.startsWith(`${prefix}/`)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (normalized === p || normalized.startsWith(`${p}/`)) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nexport function runFastWall(input: FastWallInput): FastWallResult {\n\tconst flags: PolicyFlag[] = [];\n\tconst diffPaths = parseDiffPaths(input.diffText);\n\tconst scope = input.approvedWriteScope ?? [];\n\tconst previewOnly = input.previewOnly === true;\n\tconst runIds = input.runIds ?? [];\n\n\tif (diffPaths.length === 0 && input.diffText !== undefined && input.diffText.trim() !== \"\") {\n\t\t// diff present but no paths parsed — treat as inconclusive via caller, not a flag here\n\t}\n\n\tif (scope.length > 0 && diffPaths.length > 0) {\n\t\tconst outOfScope = diffPaths.filter((p) => !pathMatchesApprovedScope(p, scope));\n\t\tif (outOfScope.length > 0) {\n\t\t\tflags.push(POLICY_FLAG.CANDIDATE_LEAK_SUSPECT);\n\t\t\tflags.push(POLICY_FLAG.NON_NEGOTIABLE_BLOCKING);\n\t\t}\n\t}\n\n\tconst expectsRunEvidence = runIds.length > 0 && !previewOnly;\n\tif (expectsRunEvidence) {\n\t\tflags.push(POLICY_FLAG.EVIDENCE_DAG_INCOMPLETE);\n\t}\n\n\tif (previewOnly) {\n\t\tflags.push(POLICY_FLAG.REPRO_OVERFIT_SUSPECT);\n\t\tflags.push(POLICY_FLAG.LOW_DISCRIMINATION);\n\t}\n\n\tif (scanDiffLinesForSecrets(input.diffText)) {\n\t\tflags.push(POLICY_FLAG.SECRET_SUSPECT);\n\t\tflags.push(POLICY_FLAG.NON_NEGOTIABLE_BLOCKING);\n\t}\n\n\treturn { flags, diffPaths };\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional HMAC receipt attestation (Wave 3 / Pro). No secret material in payload.
|
|
3
|
+
*/
|
|
4
|
+
import type { VerificationDigest } from "./signed-receipt.ts";
|
|
5
|
+
export interface SignedWallReceipt {
|
|
6
|
+
algorithm: "hmac-sha256-hex";
|
|
7
|
+
digestCompositeHash: string;
|
|
8
|
+
wallVersion: string;
|
|
9
|
+
signature: string;
|
|
10
|
+
signedAt: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SignWallReceiptInput {
|
|
13
|
+
digest: VerificationDigest;
|
|
14
|
+
wallVersion: string;
|
|
15
|
+
secret: string;
|
|
16
|
+
signedAt?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Sign the composite hash of a verification digest. Caller must not log `secret`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function signWallReceipt(input: SignWallReceiptInput): SignedWallReceipt;
|
|
22
|
+
export interface VerifyWallReceiptInput {
|
|
23
|
+
signed: SignedWallReceipt;
|
|
24
|
+
secret: string;
|
|
25
|
+
}
|
|
26
|
+
/** Constant-time verify when lengths match. */
|
|
27
|
+
export declare function verifyWallReceipt(input: VerifyWallReceiptInput): boolean;
|
|
28
|
+
//# sourceMappingURL=receipt-signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receipt-signature.d.ts","sourceRoot":"","sources":["../src/receipt-signature.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,iBAAiB,CAW9E;AAED,MAAM,WAAW,sBAAsB;IACtC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CACf;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAOxE","sourcesContent":["/**\n * Optional HMAC receipt attestation (Wave 3 / Pro). No secret material in payload.\n */\n\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nimport type { VerificationDigest } from \"./signed-receipt.ts\";\n\nexport interface SignedWallReceipt {\n\talgorithm: \"hmac-sha256-hex\";\n\tdigestCompositeHash: string;\n\twallVersion: string;\n\tsignature: string;\n\tsignedAt: string;\n}\n\nexport interface SignWallReceiptInput {\n\tdigest: VerificationDigest;\n\twallVersion: string;\n\tsecret: string;\n\tsignedAt?: string;\n}\n\n/**\n * Sign the composite hash of a verification digest. Caller must not log `secret`.\n */\nexport function signWallReceipt(input: SignWallReceiptInput): SignedWallReceipt {\n\tconst signedAt = input.signedAt ?? new Date().toISOString();\n\tconst material = [input.wallVersion, input.digest.compositeHash, signedAt].join(\"|\");\n\tconst signature = createHmac(\"sha256\", input.secret).update(material, \"utf8\").digest(\"hex\");\n\treturn {\n\t\talgorithm: \"hmac-sha256-hex\",\n\t\tdigestCompositeHash: input.digest.compositeHash,\n\t\twallVersion: input.wallVersion,\n\t\tsignature,\n\t\tsignedAt,\n\t};\n}\n\nexport interface VerifyWallReceiptInput {\n\tsigned: SignedWallReceipt;\n\tsecret: string;\n}\n\n/** Constant-time verify when lengths match. */\nexport function verifyWallReceipt(input: VerifyWallReceiptInput): boolean {\n\tconst material = [input.signed.wallVersion, input.signed.digestCompositeHash, input.signed.signedAt].join(\"|\");\n\tconst expected = createHmac(\"sha256\", input.secret).update(material, \"utf8\").digest(\"hex\");\n\tconst a = Buffer.from(expected, \"utf8\");\n\tconst b = Buffer.from(input.signed.signature, \"utf8\");\n\tif (a.length !== b.length) return false;\n\treturn timingSafeEqual(a, b);\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional HMAC receipt attestation (Wave 3 / Pro). No secret material in payload.
|
|
3
|
+
*/
|
|
4
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
5
|
+
/**
|
|
6
|
+
* Sign the composite hash of a verification digest. Caller must not log `secret`.
|
|
7
|
+
*/
|
|
8
|
+
export function signWallReceipt(input) {
|
|
9
|
+
const signedAt = input.signedAt ?? new Date().toISOString();
|
|
10
|
+
const material = [input.wallVersion, input.digest.compositeHash, signedAt].join("|");
|
|
11
|
+
const signature = createHmac("sha256", input.secret).update(material, "utf8").digest("hex");
|
|
12
|
+
return {
|
|
13
|
+
algorithm: "hmac-sha256-hex",
|
|
14
|
+
digestCompositeHash: input.digest.compositeHash,
|
|
15
|
+
wallVersion: input.wallVersion,
|
|
16
|
+
signature,
|
|
17
|
+
signedAt,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Constant-time verify when lengths match. */
|
|
21
|
+
export function verifyWallReceipt(input) {
|
|
22
|
+
const material = [input.signed.wallVersion, input.signed.digestCompositeHash, input.signed.signedAt].join("|");
|
|
23
|
+
const expected = createHmac("sha256", input.secret).update(material, "utf8").digest("hex");
|
|
24
|
+
const a = Buffer.from(expected, "utf8");
|
|
25
|
+
const b = Buffer.from(input.signed.signature, "utf8");
|
|
26
|
+
if (a.length !== b.length)
|
|
27
|
+
return false;
|
|
28
|
+
return timingSafeEqual(a, b);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=receipt-signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receipt-signature.js","sourceRoot":"","sources":["../src/receipt-signature.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAmB1D;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,KAA2B,EAAqB;IAC/E,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5D,MAAM,QAAQ,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrF,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5F,OAAO;QACN,SAAS,EAAE,iBAAiB;QAC5B,mBAAmB,EAAE,KAAK,CAAC,MAAM,CAAC,aAAa;QAC/C,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,SAAS;QACT,QAAQ;KACR,CAAC;AAAA,CACF;AAOD,+CAA+C;AAC/C,MAAM,UAAU,iBAAiB,CAAC,KAA6B,EAAW;IACzE,MAAM,QAAQ,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,mBAAmB,EAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/G,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3F,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACtD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAAA,CAC7B","sourcesContent":["/**\n * Optional HMAC receipt attestation (Wave 3 / Pro). No secret material in payload.\n */\n\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\n\nimport type { VerificationDigest } from \"./signed-receipt.ts\";\n\nexport interface SignedWallReceipt {\n\talgorithm: \"hmac-sha256-hex\";\n\tdigestCompositeHash: string;\n\twallVersion: string;\n\tsignature: string;\n\tsignedAt: string;\n}\n\nexport interface SignWallReceiptInput {\n\tdigest: VerificationDigest;\n\twallVersion: string;\n\tsecret: string;\n\tsignedAt?: string;\n}\n\n/**\n * Sign the composite hash of a verification digest. Caller must not log `secret`.\n */\nexport function signWallReceipt(input: SignWallReceiptInput): SignedWallReceipt {\n\tconst signedAt = input.signedAt ?? new Date().toISOString();\n\tconst material = [input.wallVersion, input.digest.compositeHash, signedAt].join(\"|\");\n\tconst signature = createHmac(\"sha256\", input.secret).update(material, \"utf8\").digest(\"hex\");\n\treturn {\n\t\talgorithm: \"hmac-sha256-hex\",\n\t\tdigestCompositeHash: input.digest.compositeHash,\n\t\twallVersion: input.wallVersion,\n\t\tsignature,\n\t\tsignedAt,\n\t};\n}\n\nexport interface VerifyWallReceiptInput {\n\tsigned: SignedWallReceipt;\n\tsecret: string;\n}\n\n/** Constant-time verify when lengths match. */\nexport function verifyWallReceipt(input: VerifyWallReceiptInput): boolean {\n\tconst material = [input.signed.wallVersion, input.signed.digestCompositeHash, input.signed.signedAt].join(\"|\");\n\tconst expected = createHmac(\"sha256\", input.secret).update(material, \"utf8\").digest(\"hex\");\n\tconst a = Buffer.from(expected, \"utf8\");\n\tconst b = Buffer.from(input.signed.signature, \"utf8\");\n\tif (a.length !== b.length) return false;\n\treturn timingSafeEqual(a, b);\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured capped-regenerate packet (hints only — no LLM runner in Wave 3).
|
|
3
|
+
*/
|
|
4
|
+
import type { UserVerdict } from "./b2c-verdict.ts";
|
|
5
|
+
import type { RepairHintInput } from "./repair-loop.ts";
|
|
6
|
+
export interface RegeneratePacket {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
verdict: UserVerdict;
|
|
9
|
+
attemptCount: number;
|
|
10
|
+
budget: number;
|
|
11
|
+
budgetRemaining: number;
|
|
12
|
+
hints: string[];
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
export interface BuildRegeneratePacketInput extends RepairHintInput {
|
|
16
|
+
attemptCount: number;
|
|
17
|
+
budgetOverride?: number;
|
|
18
|
+
autoRegenerateEnabled: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* When auto-regenerate is enabled and verdict is BLOCKED with budget headroom,
|
|
22
|
+
* returns capped hints for operator or downstream agent — does not apply patches.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildRegeneratePacket(input: BuildRegeneratePacketInput): RegeneratePacket;
|
|
25
|
+
//# sourceMappingURL=regenerate-packet.d.ts.map
|