audit-tools 0.32.8 → 0.32.9
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/dist/audit/cli/dispatch/quotaPool.d.ts +20 -2
- package/dist/audit/cli/dispatch/quotaPool.d.ts.map +1 -1
- package/dist/audit/cli/dispatch/quotaPool.js +38 -7
- package/dist/audit/cli/dispatch/quotaPool.js.map +1 -1
- package/dist/audit/cli/dispatch/tierRouting.d.ts +4 -1
- package/dist/audit/cli/dispatch/tierRouting.d.ts.map +1 -1
- package/dist/audit/cli/dispatch/tierRouting.js +7 -4
- package/dist/audit/cli/dispatch/tierRouting.js.map +1 -1
- package/dist/audit/cli/dispatch/types.d.ts +9 -4
- package/dist/audit/cli/dispatch/types.d.ts.map +1 -1
- package/dist/audit/cli/dispatch.d.ts +7 -0
- package/dist/audit/cli/dispatch.d.ts.map +1 -1
- package/dist/audit/cli/dispatch.js +24 -12
- package/dist/audit/cli/dispatch.js.map +1 -1
- package/dist/audit/cli/mergeAndIngestCommand.d.ts +0 -9
- package/dist/audit/cli/mergeAndIngestCommand.d.ts.map +1 -1
- package/dist/audit/cli/mergeAndIngestCommand.js +29 -1
- package/dist/audit/cli/mergeAndIngestCommand.js.map +1 -1
- package/dist/audit/cli/prompts.d.ts.map +1 -1
- package/dist/audit/cli/prompts.js +4 -2
- package/dist/audit/cli/prompts.js.map +1 -1
- package/dist/audit/cli/rollingAuditDispatch.d.ts.map +1 -1
- package/dist/audit/cli/rollingAuditDispatch.js +3 -0
- package/dist/audit/cli/rollingAuditDispatch.js.map +1 -1
- package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
- package/dist/audit/cli/semanticReviewStep.js +10 -6
- package/dist/audit/cli/semanticReviewStep.js.map +1 -1
- package/dist/audit/cli/steps.d.ts +20 -10
- package/dist/audit/cli/steps.d.ts.map +1 -1
- package/dist/audit/cli/steps.js +4 -2
- package/dist/audit/cli/steps.js.map +1 -1
- package/dist/audit/orchestrator/rollingDispatch.d.ts +7 -6
- package/dist/audit/orchestrator/rollingDispatch.d.ts.map +1 -1
- package/dist/audit/orchestrator/rollingDispatch.js +37 -19
- package/dist/audit/orchestrator/rollingDispatch.js.map +1 -1
- package/dist/audit/quota/index.d.ts +137 -8
- package/dist/audit/quota/index.d.ts.map +1 -1
- package/dist/audit/quota/index.js +9 -8
- package/dist/audit/quota/index.js.map +1 -1
- package/dist/remediate/dispatch/ownershipRegistry.d.ts +2 -22
- package/dist/remediate/dispatch/ownershipRegistry.d.ts.map +1 -1
- package/dist/remediate/dispatch/ownershipRegistry.js +7 -45
- package/dist/remediate/dispatch/ownershipRegistry.js.map +1 -1
- package/dist/remediate/steps/dispatch.d.ts +22 -1
- package/dist/remediate/steps/dispatch.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch.js +89 -5
- package/dist/remediate/steps/dispatch.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +89 -116
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/rollingSession.d.ts +15 -17
- package/dist/remediate/steps/rollingSession.d.ts.map +1 -1
- package/dist/remediate/steps/rollingSession.js +33 -57
- package/dist/remediate/steps/rollingSession.js.map +1 -1
- package/dist/remediate/steps/types.d.ts +12 -4
- package/dist/remediate/steps/types.d.ts.map +1 -1
- package/dist/remediate/steps/types.js +1 -1
- package/dist/shared/dispatch/admissionLoop.d.ts +245 -0
- package/dist/shared/dispatch/admissionLoop.d.ts.map +1 -0
- package/dist/shared/dispatch/admissionLoop.js +204 -0
- package/dist/shared/dispatch/admissionLoop.js.map +1 -0
- package/dist/{remediate → shared}/dispatch/ownershipScheduler.d.ts +23 -2
- package/dist/shared/dispatch/ownershipScheduler.d.ts.map +1 -0
- package/dist/{remediate → shared}/dispatch/ownershipScheduler.js +34 -6
- package/dist/shared/dispatch/ownershipScheduler.js.map +1 -0
- package/dist/shared/dispatch/pathIdentity.d.ts +26 -0
- package/dist/shared/dispatch/pathIdentity.d.ts.map +1 -0
- package/dist/shared/dispatch/pathIdentity.js +49 -0
- package/dist/shared/dispatch/pathIdentity.js.map +1 -0
- package/dist/shared/dispatch/rollingDispatch.d.ts +37 -0
- package/dist/shared/dispatch/rollingDispatch.d.ts.map +1 -1
- package/dist/shared/dispatch/rollingDispatch.js +117 -18
- package/dist/shared/dispatch/rollingDispatch.js.map +1 -1
- package/dist/shared/dispatch/unifiedRolling.d.ts +131 -0
- package/dist/shared/dispatch/unifiedRolling.d.ts.map +1 -0
- package/dist/shared/dispatch/unifiedRolling.js +161 -0
- package/dist/shared/dispatch/unifiedRolling.js.map +1 -0
- package/dist/shared/index.d.ts +12 -1
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +18 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/quota/packetCost.d.ts +29 -0
- package/dist/shared/quota/packetCost.d.ts.map +1 -0
- package/dist/shared/quota/packetCost.js +51 -0
- package/dist/shared/quota/packetCost.js.map +1 -0
- package/dist/shared/quota/reservationLedger.d.ts +100 -0
- package/dist/shared/quota/reservationLedger.d.ts.map +1 -0
- package/dist/shared/quota/reservationLedger.js +224 -0
- package/dist/shared/quota/reservationLedger.js.map +1 -0
- package/dist/shared/quota/state.d.ts +20 -0
- package/dist/shared/quota/state.d.ts.map +1 -1
- package/dist/shared/quota/state.js +50 -0
- package/dist/shared/quota/state.js.map +1 -1
- package/dist/shared/quota/types.d.ts +11 -0
- package/dist/shared/quota/types.d.ts.map +1 -1
- package/dist/shared/quota/types.js.map +1 -1
- package/package.json +1 -1
- package/dist/remediate/dispatch/ownershipScheduler.d.ts.map +0 -1
- package/dist/remediate/dispatch/ownershipScheduler.js.map +0 -1
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Host-path admission loop — the tool-side "grant the admitted set" primitive of
|
|
2
|
+
// the dispatch admission-control model (spec/audit/dispatch-admission-control.md).
|
|
3
|
+
//
|
|
4
|
+
// The in-process rolling engine (rollingDispatch.ts) admits ONE packet at a time
|
|
5
|
+
// continuously; this is its host-subagent-path analog: at a dispatch step the tool
|
|
6
|
+
// admits a BATCH — as many packets as budget (and any declared in-flight cap) allow
|
|
7
|
+
// right now — reserving each against the shared ReservationLedger, and hands the
|
|
8
|
+
// host EXACTLY that granted set. The host dispatches the set, reports at
|
|
9
|
+
// result-ingest (merge-and-ingest), and the next `next-step` re-admits the
|
|
10
|
+
// remainder. The granted set's size is the instantaneous admission width — there is
|
|
11
|
+
// no computed concurrency number (spec: "concurrency is not a computed quantity").
|
|
12
|
+
//
|
|
13
|
+
// COST-FIRST ROUTING is a first-class property: each packet is routed to the
|
|
14
|
+
// CHEAPEST pool CAPABLE of it (cost-ascending among capable pools) that still has
|
|
15
|
+
// budget + in-flight-cap headroom, so lower-cost providers fill before pricier ones
|
|
16
|
+
// and overflow spills to the next-cheapest-capable pool. The capability gate and
|
|
17
|
+
// cost rank are inputs, so refining the complexity/risk gate is a one-predicate
|
|
18
|
+
// change, never a re-architecture.
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { estimatePacketCost } from "../quota/packetCost.js";
|
|
21
|
+
export const AdmissionGrantSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
packet_id: z.string(),
|
|
24
|
+
pool_id: z.string(),
|
|
25
|
+
resource_key: z.string(),
|
|
26
|
+
lease_id: z.string(),
|
|
27
|
+
cost: z.number(),
|
|
28
|
+
})
|
|
29
|
+
.strict();
|
|
30
|
+
/** Why a packet was admitted or blocked — the per-admission explain record. */
|
|
31
|
+
export const AdmissionExplainSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
packet_id: z.string(),
|
|
34
|
+
/** null only when NO pool was capable of the packet at all. */
|
|
35
|
+
pool_id: z.string().nullable(),
|
|
36
|
+
resource_key: z.string().nullable(),
|
|
37
|
+
admitted: z.boolean(),
|
|
38
|
+
reason: z.enum(["admitted", "no_capable_pool", "budget_exhausted", "cap_reached"]),
|
|
39
|
+
/** Present on an admit attempt against a real pool (budget headroom before it). */
|
|
40
|
+
headroom_before: z.number().optional(),
|
|
41
|
+
outstanding_before: z.number().optional(),
|
|
42
|
+
cost: z.number(),
|
|
43
|
+
})
|
|
44
|
+
.strict();
|
|
45
|
+
/**
|
|
46
|
+
* The admission artifact both orchestrators embed in their dispatch-quota contract,
|
|
47
|
+
* REPLACING the removed `max_concurrent_agents` scalar. `granted_packet_ids` is the
|
|
48
|
+
* set the host dispatches this step (its size is the emergent admission width);
|
|
49
|
+
* `declared_cap` is the verbatim per-environment hard in-flight cap (null when
|
|
50
|
+
* none); `leases` are reconciled (freed) at result-ingest; `explains` reconstruct
|
|
51
|
+
* why the fan-out was the width it was.
|
|
52
|
+
*/
|
|
53
|
+
export const DispatchAdmissionSchema = z
|
|
54
|
+
.object({
|
|
55
|
+
granted_packet_ids: z.array(z.string()),
|
|
56
|
+
declared_cap: z.number().int().min(1).nullable(),
|
|
57
|
+
leases: z.array(AdmissionGrantSchema),
|
|
58
|
+
explains: z.array(AdmissionExplainSchema),
|
|
59
|
+
})
|
|
60
|
+
.strict();
|
|
61
|
+
/** Default capability gate: the pool's window must fit the packet's reservation. */
|
|
62
|
+
function defaultCapable(pool, packet) {
|
|
63
|
+
if (!Number.isFinite(pool.capacityTokens) || pool.capacityTokens <= 0)
|
|
64
|
+
return true;
|
|
65
|
+
return pool.capacityTokens >= packet.cost;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Admit as many packets as budget + declared caps allow this pass, routing each to
|
|
69
|
+
* the cheapest capable pool with headroom. Every admission RESERVES the packet's
|
|
70
|
+
* cost against the shared ledger under its lock, so co-located dispatch loops on one
|
|
71
|
+
* account cannot collectively over-admit. Returns the granted set (the host
|
|
72
|
+
* dispatches exactly these), the explain trail, and the blocked remainder.
|
|
73
|
+
*
|
|
74
|
+
* A pool's declared in-flight cap is enforced by COUNT of its outstanding ledger
|
|
75
|
+
* leases (cross-process) plus this batch's grants — the only place an explicit
|
|
76
|
+
* agent-count exists (a verbatim environment limit), never a computed concurrency.
|
|
77
|
+
*/
|
|
78
|
+
export async function admitBatch(input) {
|
|
79
|
+
const capable = input.capable ?? defaultCapable;
|
|
80
|
+
const granted = [];
|
|
81
|
+
const explains = [];
|
|
82
|
+
const blocked = [];
|
|
83
|
+
// Seed per-pool in-flight COUNT from the ledger (other consumers' live leases),
|
|
84
|
+
// so a declared cap accounts for cross-process in-flight, then add this batch's.
|
|
85
|
+
const countByPool = new Map();
|
|
86
|
+
try {
|
|
87
|
+
const snapshot = await input.ledger.snapshot();
|
|
88
|
+
for (const leases of Object.values(snapshot)) {
|
|
89
|
+
for (const lease of leases) {
|
|
90
|
+
countByPool.set(lease.poolId, (countByPool.get(lease.poolId) ?? 0) + 1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Degrade-safe: an unreadable ledger just means cap counting starts at 0.
|
|
96
|
+
}
|
|
97
|
+
for (const packet of input.packets) {
|
|
98
|
+
// Cost-first-capable: cheapest capable pool first; ties break toward the more
|
|
99
|
+
// capable pool so equal-cost lanes prefer the one with more headroom.
|
|
100
|
+
const candidates = input.pools
|
|
101
|
+
.filter((pool) => capable(pool, packet))
|
|
102
|
+
.sort((a, b) => a.costRank - b.costRank || b.capabilityRank - a.capabilityRank);
|
|
103
|
+
if (candidates.length === 0) {
|
|
104
|
+
explains.push({
|
|
105
|
+
packet_id: packet.id,
|
|
106
|
+
pool_id: null,
|
|
107
|
+
resource_key: null,
|
|
108
|
+
admitted: false,
|
|
109
|
+
reason: "no_capable_pool",
|
|
110
|
+
cost: packet.cost,
|
|
111
|
+
});
|
|
112
|
+
blocked.push(packet.id);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
let placed = false;
|
|
116
|
+
let lastReason = "budget_exhausted";
|
|
117
|
+
let lastPool = candidates[0];
|
|
118
|
+
for (const pool of candidates) {
|
|
119
|
+
if (pool.declaredCap != null && (countByPool.get(pool.poolId) ?? 0) >= pool.declaredCap) {
|
|
120
|
+
lastReason = "cap_reached";
|
|
121
|
+
lastPool = pool;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const decision = await input.ledger.admit({
|
|
125
|
+
resourceKey: pool.resourceKey,
|
|
126
|
+
cost: packet.cost,
|
|
127
|
+
budget: pool.budget,
|
|
128
|
+
poolId: pool.poolId,
|
|
129
|
+
...(input.leaseTtlMs != null ? { leaseTtlMs: input.leaseTtlMs } : {}),
|
|
130
|
+
});
|
|
131
|
+
if (decision.admitted && decision.leaseId) {
|
|
132
|
+
granted.push({
|
|
133
|
+
packet_id: packet.id,
|
|
134
|
+
pool_id: pool.poolId,
|
|
135
|
+
resource_key: pool.resourceKey,
|
|
136
|
+
lease_id: decision.leaseId,
|
|
137
|
+
cost: packet.cost,
|
|
138
|
+
});
|
|
139
|
+
countByPool.set(pool.poolId, (countByPool.get(pool.poolId) ?? 0) + 1);
|
|
140
|
+
explains.push({
|
|
141
|
+
packet_id: packet.id,
|
|
142
|
+
pool_id: pool.poolId,
|
|
143
|
+
resource_key: pool.resourceKey,
|
|
144
|
+
admitted: true,
|
|
145
|
+
reason: "admitted",
|
|
146
|
+
headroom_before: decision.headroomBefore,
|
|
147
|
+
outstanding_before: decision.outstandingBefore,
|
|
148
|
+
cost: packet.cost,
|
|
149
|
+
});
|
|
150
|
+
placed = true;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
lastReason = "budget_exhausted";
|
|
154
|
+
lastPool = pool;
|
|
155
|
+
}
|
|
156
|
+
if (!placed) {
|
|
157
|
+
explains.push({
|
|
158
|
+
packet_id: packet.id,
|
|
159
|
+
pool_id: lastPool.poolId,
|
|
160
|
+
resource_key: lastPool.resourceKey,
|
|
161
|
+
admitted: false,
|
|
162
|
+
reason: lastReason,
|
|
163
|
+
cost: packet.cost,
|
|
164
|
+
});
|
|
165
|
+
blocked.push(packet.id);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { granted, explains, blocked };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Build the `DispatchAdmission` contract block both orchestrators embed in their
|
|
172
|
+
* dispatch-quota — the single-sourced host-path admission derivation (no per-tool
|
|
173
|
+
* copy that could drift). Computes each packet's envelope reservation
|
|
174
|
+
* (`estimatePacketCost` with the declared output cap), derives the surfaced declared
|
|
175
|
+
* cap (the most-constraining pool's in-flight cap), and either GRANTS via
|
|
176
|
+
* {@link admitBatch} (host-subagent path, `grantLeases: true` — leases persist for
|
|
177
|
+
* reconcile at ingest) or returns a plan-only block listing every candidate
|
|
178
|
+
* (`grantLeases: false` — the in-process rolling engine leases per-packet itself,
|
|
179
|
+
* so a host grant here would double-count).
|
|
180
|
+
*/
|
|
181
|
+
export async function computeDispatchAdmission(input) {
|
|
182
|
+
const candidates = input.packets.map((p) => ({
|
|
183
|
+
id: p.id,
|
|
184
|
+
cost: estimatePacketCost({ inputEstimate: p.inputTokens, declaredOutputCap: input.outputCap }).cost,
|
|
185
|
+
complexity: p.complexity,
|
|
186
|
+
}));
|
|
187
|
+
const declaredCap = input.pools.reduce((min, p) => (p.declaredCap == null ? min : min == null ? p.declaredCap : Math.min(min, p.declaredCap)), null);
|
|
188
|
+
if (!input.grantLeases) {
|
|
189
|
+
return { granted_packet_ids: candidates.map((c) => c.id), declared_cap: declaredCap, leases: [], explains: [] };
|
|
190
|
+
}
|
|
191
|
+
const admit = await admitBatch({
|
|
192
|
+
packets: candidates,
|
|
193
|
+
pools: input.pools,
|
|
194
|
+
ledger: input.ledger,
|
|
195
|
+
...(input.capable ? { capable: input.capable } : {}),
|
|
196
|
+
});
|
|
197
|
+
return {
|
|
198
|
+
granted_packet_ids: admit.granted.map((g) => g.packet_id),
|
|
199
|
+
declared_cap: declaredCap,
|
|
200
|
+
leases: admit.granted,
|
|
201
|
+
explains: admit.explains,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=admissionLoop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admissionLoop.js","sourceRoot":"","sources":["../../../src/shared/dispatch/admissionLoop.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,6 +22,17 @@
|
|
|
22
22
|
* region-disjoint upstream). If either lacks the flag they serialize into
|
|
23
23
|
* successive sub-waves exactly as before — the conservative default is unchanged.
|
|
24
24
|
*
|
|
25
|
+
* Three disjointness cases, not two (the read-only distinction): (1) a node that is
|
|
26
|
+
* provably READ-ONLY (`read_only === true`, no writes at all) conflicts with nothing
|
|
27
|
+
* and admits into any sub-wave — all read-only nodes collapse into the first maximal
|
|
28
|
+
* sub-wave (this is how an auditor, which writes nothing, runs fully parallel as a
|
|
29
|
+
* degenerate case of the same scheduler); (2) a node with a DECLARED write-scope
|
|
30
|
+
* batches file-disjointly (the `cofile_parallel_safe` relaxation above); (3) a node
|
|
31
|
+
* with an EMPTY/undeclared scope is conservatively non-disjoint and admits solo among
|
|
32
|
+
* writers. Case (1) is distinct from case (3): an empty `write_paths` means "scope
|
|
33
|
+
* unresolved, might touch anything" (serialize), whereas `read_only` means "provably
|
|
34
|
+
* touches nothing" (always parallel).
|
|
35
|
+
*
|
|
25
36
|
* Pure and deterministic — no I/O, no Set/Map iteration-order leak — so the
|
|
26
37
|
* admission order is reproducible and unit-testable against a precomputed level.
|
|
27
38
|
*/
|
|
@@ -36,6 +47,15 @@ export interface OwnershipSchedulerNode {
|
|
|
36
47
|
* Absent/false ⇒ the node serializes vs. any same-file peer exactly as before.
|
|
37
48
|
*/
|
|
38
49
|
cofile_parallel_safe?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* The node is provably READ-ONLY — it performs no writes to the target tree, so it
|
|
52
|
+
* can never conflict with any peer at merge and admits into any sub-wave in full
|
|
53
|
+
* parallel. This is DISTINCT from an empty/undeclared `write_paths`: empty means the
|
|
54
|
+
* write-scope is *unresolved* (conservatively serial); `read_only` means the node is
|
|
55
|
+
* *known* to write nothing (an auditor). Absent/false ⇒ the node is treated as a
|
|
56
|
+
* writer and goes through the normal file-disjointness gating.
|
|
57
|
+
*/
|
|
58
|
+
read_only?: boolean;
|
|
39
59
|
}
|
|
40
60
|
/**
|
|
41
61
|
* Canonicalize a node's declared write-scope to physical-file identity keys.
|
|
@@ -52,8 +72,9 @@ export declare function canonicalScopeKeys(node: OwnershipSchedulerNode, root?:
|
|
|
52
72
|
*
|
|
53
73
|
* A node with an EMPTY canonical scope (unresolved/undeclared) is treated as
|
|
54
74
|
* conservatively NON-disjoint: it only enters a sub-wave by itself (it blocks and
|
|
55
|
-
* is blocked-by every peer), so it never batches with another writer
|
|
56
|
-
* (INV-SOO-01 / CE-008).
|
|
75
|
+
* is blocked-by every peer writer), so it never batches with another writer
|
|
76
|
+
* (INV-SOO-01 / CE-008). A `read_only` node is the opposite — it writes nothing, so
|
|
77
|
+
* it admits into any sub-wave in full parallel (see the three-case note above).
|
|
57
78
|
*/
|
|
58
79
|
export declare function ownershipSubWaves(level: OwnershipSchedulerNode[], root?: string): OwnershipSchedulerNode[][];
|
|
59
80
|
//# sourceMappingURL=ownershipScheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownershipScheduler.d.ts","sourceRoot":"","sources":["../../../src/shared/dispatch/ownershipScheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAIH,mFAAmF;AACnF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,sBAAsB,EAC5B,IAAI,CAAC,EAAE,MAAM,GACZ,GAAG,CAAC,MAAM,CAAC,CAIb;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,sBAAsB,EAAE,EAC/B,IAAI,CAAC,EAAE,MAAM,GACZ,sBAAsB,EAAE,EAAE,CAoF5B"}
|
|
@@ -22,10 +22,21 @@
|
|
|
22
22
|
* region-disjoint upstream). If either lacks the flag they serialize into
|
|
23
23
|
* successive sub-waves exactly as before — the conservative default is unchanged.
|
|
24
24
|
*
|
|
25
|
+
* Three disjointness cases, not two (the read-only distinction): (1) a node that is
|
|
26
|
+
* provably READ-ONLY (`read_only === true`, no writes at all) conflicts with nothing
|
|
27
|
+
* and admits into any sub-wave — all read-only nodes collapse into the first maximal
|
|
28
|
+
* sub-wave (this is how an auditor, which writes nothing, runs fully parallel as a
|
|
29
|
+
* degenerate case of the same scheduler); (2) a node with a DECLARED write-scope
|
|
30
|
+
* batches file-disjointly (the `cofile_parallel_safe` relaxation above); (3) a node
|
|
31
|
+
* with an EMPTY/undeclared scope is conservatively non-disjoint and admits solo among
|
|
32
|
+
* writers. Case (1) is distinct from case (3): an empty `write_paths` means "scope
|
|
33
|
+
* unresolved, might touch anything" (serialize), whereas `read_only` means "provably
|
|
34
|
+
* touches nothing" (always parallel).
|
|
35
|
+
*
|
|
25
36
|
* Pure and deterministic — no I/O, no Set/Map iteration-order leak — so the
|
|
26
37
|
* admission order is reproducible and unit-testable against a precomputed level.
|
|
27
38
|
*/
|
|
28
|
-
import { canonicalizeFilePath } from "./
|
|
39
|
+
import { canonicalizeFilePath } from "./pathIdentity.js";
|
|
29
40
|
/**
|
|
30
41
|
* Canonicalize a node's declared write-scope to physical-file identity keys.
|
|
31
42
|
* Empty input ⇒ empty set (handled conservatively by the disjointness gate).
|
|
@@ -46,8 +57,9 @@ export function canonicalScopeKeys(node, root) {
|
|
|
46
57
|
*
|
|
47
58
|
* A node with an EMPTY canonical scope (unresolved/undeclared) is treated as
|
|
48
59
|
* conservatively NON-disjoint: it only enters a sub-wave by itself (it blocks and
|
|
49
|
-
* is blocked-by every peer), so it never batches with another writer
|
|
50
|
-
* (INV-SOO-01 / CE-008).
|
|
60
|
+
* is blocked-by every peer writer), so it never batches with another writer
|
|
61
|
+
* (INV-SOO-01 / CE-008). A `read_only` node is the opposite — it writes nothing, so
|
|
62
|
+
* it admits into any sub-wave in full parallel (see the three-case note above).
|
|
51
63
|
*/
|
|
52
64
|
export function ownershipSubWaves(level, root) {
|
|
53
65
|
const ordered = [...level].sort((a, b) => a.block_id.localeCompare(b.block_id));
|
|
@@ -63,13 +75,28 @@ export function ownershipSubWaves(level, root) {
|
|
|
63
75
|
// co-batch on a shared key IFF both are `cofile_parallel_safe`.
|
|
64
76
|
const claimant = new Map();
|
|
65
77
|
let waveHasEmptyScopeNode = false;
|
|
78
|
+
// A writer or empty-scope node has been admitted to this sub-wave. Gates the
|
|
79
|
+
// empty-scope node's solo admission: it may enter only when no writer and no
|
|
80
|
+
// prior empty-scope node is present. A `read_only` co-resident is NOT a writer,
|
|
81
|
+
// so it never trips this — read-only nodes stay fully inert to the conflict logic.
|
|
82
|
+
let waveHasWriter = false;
|
|
66
83
|
const leftover = [];
|
|
67
84
|
for (const node of remaining) {
|
|
68
85
|
const scope = scopeOf.get(node.block_id);
|
|
86
|
+
// Case 1: provably READ-ONLY (writes nothing) ⇒ conflicts with nothing. Admit to
|
|
87
|
+
// the current sub-wave unconditionally; never claim a path, never set or consult
|
|
88
|
+
// the writer/empty-scope flags. All read-only nodes therefore collapse into the
|
|
89
|
+
// first (maximal) sub-wave — an auditor's full-parallel degenerate case.
|
|
90
|
+
if (node.read_only === true) {
|
|
91
|
+
wave.push(node);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
69
94
|
const isEmpty = scope.size === 0;
|
|
70
|
-
//
|
|
95
|
+
// Case 3: empty/undeclared scope ⇒ conservatively non-disjoint. Admits only into
|
|
96
|
+
// a sub-wave with no other writer and no prior empty-scope node (solo among
|
|
97
|
+
// writers; read-only co-residents don't count).
|
|
71
98
|
if (isEmpty) {
|
|
72
|
-
if (
|
|
99
|
+
if (!waveHasWriter && !waveHasEmptyScopeNode) {
|
|
73
100
|
wave.push(node);
|
|
74
101
|
waveHasEmptyScopeNode = true;
|
|
75
102
|
}
|
|
@@ -78,7 +105,7 @@ export function ownershipSubWaves(level, root) {
|
|
|
78
105
|
}
|
|
79
106
|
continue;
|
|
80
107
|
}
|
|
81
|
-
//
|
|
108
|
+
// Case 2: a real-scope node cannot share a wave with an empty-scope node, nor with
|
|
82
109
|
// any node it shares a canonical path with — UNLESS both this node and every
|
|
83
110
|
// same-file incumbent it collides with are `cofile_parallel_safe`.
|
|
84
111
|
if (waveHasEmptyScopeNode) {
|
|
@@ -104,6 +131,7 @@ export function ownershipSubWaves(level, root) {
|
|
|
104
131
|
for (const key of scope)
|
|
105
132
|
claimant.set(key, node);
|
|
106
133
|
wave.push(node);
|
|
134
|
+
waveHasWriter = true;
|
|
107
135
|
}
|
|
108
136
|
subWaves.push(wave);
|
|
109
137
|
remaining.length = 0;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ownershipScheduler.js","sourceRoot":"","sources":["../../../src/shared/dispatch/ownershipScheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single-sourced path canonicalization for ownership identity (INV-SOO-09).
|
|
3
|
+
*
|
|
4
|
+
* Resolves a path to a stable physical-file key: absolute (against `root` when
|
|
5
|
+
* supplied, else cwd), `..`/`.`-collapsed (via `resolve`), separators normalized
|
|
6
|
+
* to `/`, and case-folded on a case-insensitive filesystem. All spellings of one
|
|
7
|
+
* file therefore collide, closing the rel/abs/case/`..` mismatch (CE-004).
|
|
8
|
+
*
|
|
9
|
+
* Symlink identity is a RECORDED RESIDUAL, not silently treated as disjoint
|
|
10
|
+
* (INV-SOO-09 / fail-3): when `resolveSymlinks` is set and the path exists, the
|
|
11
|
+
* realpath is folded in so `link.ts → x.ts` collides; when realpath is
|
|
12
|
+
* unavailable (path absent, or FS symlink resolution out of scope) the lexical
|
|
13
|
+
* canonical key is used and the unresolved case degrades to the downstream merge
|
|
14
|
+
* guard rather than being asserted disjoint here.
|
|
15
|
+
*
|
|
16
|
+
* This is the ONLY normalization scheme for ownership identity — callers must
|
|
17
|
+
* not introduce a second one (failure-mode: path-identity mismatch). It lives in
|
|
18
|
+
* `audit-tools/shared` so BOTH orchestrators' ownership scheduling resolves file
|
|
19
|
+
* identity identically (a remediate write-scope and an audit read-scope key the
|
|
20
|
+
* same file the same way).
|
|
21
|
+
*/
|
|
22
|
+
export declare function canonicalizeFilePath(path: string, opts?: {
|
|
23
|
+
root?: string;
|
|
24
|
+
resolveSymlinks?: boolean;
|
|
25
|
+
}): string;
|
|
26
|
+
//# sourceMappingURL=pathIdentity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathIdentity.d.ts","sourceRoot":"","sources":["../../../src/shared/dispatch/pathIdentity.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CAAO,GACtD,MAAM,CAgBR"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, resolve, sep } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Whether the host filesystem is case-insensitive for path identity. win32 and
|
|
5
|
+
* darwin default to case-insensitive volumes; linux is case-sensitive. Used so
|
|
6
|
+
* `src/A.ts` and `src/a.ts` collide on a Windows/macOS volume (one physical
|
|
7
|
+
* file) but stay distinct on Linux — INV-SOO-09 canonical physical-file identity.
|
|
8
|
+
*/
|
|
9
|
+
const CASE_INSENSITIVE_FS = process.platform === "win32" || process.platform === "darwin";
|
|
10
|
+
/**
|
|
11
|
+
* The single-sourced path canonicalization for ownership identity (INV-SOO-09).
|
|
12
|
+
*
|
|
13
|
+
* Resolves a path to a stable physical-file key: absolute (against `root` when
|
|
14
|
+
* supplied, else cwd), `..`/`.`-collapsed (via `resolve`), separators normalized
|
|
15
|
+
* to `/`, and case-folded on a case-insensitive filesystem. All spellings of one
|
|
16
|
+
* file therefore collide, closing the rel/abs/case/`..` mismatch (CE-004).
|
|
17
|
+
*
|
|
18
|
+
* Symlink identity is a RECORDED RESIDUAL, not silently treated as disjoint
|
|
19
|
+
* (INV-SOO-09 / fail-3): when `resolveSymlinks` is set and the path exists, the
|
|
20
|
+
* realpath is folded in so `link.ts → x.ts` collides; when realpath is
|
|
21
|
+
* unavailable (path absent, or FS symlink resolution out of scope) the lexical
|
|
22
|
+
* canonical key is used and the unresolved case degrades to the downstream merge
|
|
23
|
+
* guard rather than being asserted disjoint here.
|
|
24
|
+
*
|
|
25
|
+
* This is the ONLY normalization scheme for ownership identity — callers must
|
|
26
|
+
* not introduce a second one (failure-mode: path-identity mismatch). It lives in
|
|
27
|
+
* `audit-tools/shared` so BOTH orchestrators' ownership scheduling resolves file
|
|
28
|
+
* identity identically (a remediate write-scope and an audit read-scope key the
|
|
29
|
+
* same file the same way).
|
|
30
|
+
*/
|
|
31
|
+
export function canonicalizeFilePath(path, opts = {}) {
|
|
32
|
+
const { root, resolveSymlinks = false } = opts;
|
|
33
|
+
let abs = isAbsolute(path)
|
|
34
|
+
? resolve(path)
|
|
35
|
+
: resolve(root ?? process.cwd(), path);
|
|
36
|
+
if (resolveSymlinks) {
|
|
37
|
+
try {
|
|
38
|
+
abs = realpathSync(abs);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Symlink/realpath unavailable (path absent or FS resolution out of scope):
|
|
42
|
+
// keep the lexical key — the unresolved symlink case is a recorded residual
|
|
43
|
+
// handled by the downstream merge guard, never asserted disjoint here.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const slashed = abs.split(sep).join("/");
|
|
47
|
+
return CASE_INSENSITIVE_FS ? slashed.toLowerCase() : slashed;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=pathIdentity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathIdentity.js","sourceRoot":"","sources":["../../../src/shared/dispatch/pathIdentity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErD;;;;;GAKG;AACH,MAAM,mBAAmB,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC;AAE1F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,OAAqD,EAAE;IAEvD,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC;IAC/C,IAAI,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC;QACxB,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;QACf,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;IACzC,IAAI,eAAe,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,4EAA4E;YAC5E,4EAA4E;YAC5E,uEAAuE;QACzE,CAAC;IACH,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,mBAAmB,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;AAC/D,CAAC"}
|
|
@@ -34,6 +34,7 @@ import type { SessionConfig } from "../types/sessionConfig.js";
|
|
|
34
34
|
import type { CapacityPool, PartialCompletionTerminal } from "../quota/capacity.js";
|
|
35
35
|
import type { QuotaStateEntry } from "../quota/types.js";
|
|
36
36
|
import type { WorkerOutputChannel } from "../quota/errorParsing.js";
|
|
37
|
+
import type { ReservationLedger } from "../quota/reservationLedger.js";
|
|
37
38
|
/** A dispatchable unit of work. TPacket is opaque to the engine. */
|
|
38
39
|
export interface RollingDispatchPacket<TPacket> {
|
|
39
40
|
/** Stable, unique identifier for this packet within a run. */
|
|
@@ -98,6 +99,14 @@ export interface InFlightEntry<TPacket> {
|
|
|
98
99
|
startedAt: number;
|
|
99
100
|
estimatedTokens: number;
|
|
100
101
|
promise: Promise<RollingDispatchResult<TPacket>>;
|
|
102
|
+
/**
|
|
103
|
+
* Reservation-ledger lease held for this in-flight packet (spec admission model).
|
|
104
|
+
* Set only when a `reservationLedger` was configured; reconciled (freed) in
|
|
105
|
+
* `handleResult` when the packet completes. `resourceKey` is the pool id the lease
|
|
106
|
+
* was taken against, needed to reconcile. Null/undefined when no ledger is wired.
|
|
107
|
+
*/
|
|
108
|
+
leaseId?: string | null;
|
|
109
|
+
resourceKey?: string;
|
|
101
110
|
}
|
|
102
111
|
/** Mutable state of the rolling dispatcher. */
|
|
103
112
|
export interface RollingDispatchState<TPacket> {
|
|
@@ -168,6 +177,34 @@ export interface RollingDispatchConfig<TPacket> {
|
|
|
168
177
|
* leave the source unfed (no escalation can ever fire).
|
|
169
178
|
*/
|
|
170
179
|
recordRateLimit?: (packet: RollingDispatchPacket<TPacket>, result: RollingDispatchResult<TPacket>) => void;
|
|
180
|
+
/**
|
|
181
|
+
* Shared token-reservation ledger (spec/audit/dispatch-admission-control.md, the
|
|
182
|
+
* proactive admission layer). When supplied, every dispatched packet LEASES its
|
|
183
|
+
* output-envelope cost against the account-keyed ledger BEFORE dispatch and
|
|
184
|
+
* reconciles the lease on completion — so two co-located dispatch loops pointed at
|
|
185
|
+
* the SAME ledger file (same `provider#account/model` meter) each see the other's
|
|
186
|
+
* outstanding reservations and cannot both optimistically assume the full budget.
|
|
187
|
+
* The resourceKey is the pool id (`pool.id` is already `provider#account/model`).
|
|
188
|
+
* Omit to leave the in-process `InFlightTokenTracker` as the sole accounting
|
|
189
|
+
* (behaviour identical to before this field existed). The reactive 429/backoff
|
|
190
|
+
* floor still catches any residual under-reservation regardless.
|
|
191
|
+
*/
|
|
192
|
+
reservationLedger?: ReservationLedger;
|
|
193
|
+
/**
|
|
194
|
+
* Live remaining token budget for a pool's resourceKey (its `pool.id`). Consulted
|
|
195
|
+
* only when `reservationLedger` is set — it is the budget the ledger admits
|
|
196
|
+
* against (`budget - Σ outstanding_leases >= cost`). Return a non-finite value
|
|
197
|
+
* (the default) for an optimistic/unbounded budget, in which case the ledger only
|
|
198
|
+
* prevents co-located double-counting and never gates on an absolute token ceiling.
|
|
199
|
+
*/
|
|
200
|
+
resolvePoolBudget?: (poolId: string) => number;
|
|
201
|
+
/**
|
|
202
|
+
* Output-token envelope (spec Resolved decision 1) added to a packet's INPUT
|
|
203
|
+
* `estimatedTokens` to form the reservation cost. Consulted only when
|
|
204
|
+
* `reservationLedger` is set. Default 0 → the lease reserves the input estimate
|
|
205
|
+
* alone; the reactive floor still catches output under-reservation.
|
|
206
|
+
*/
|
|
207
|
+
resolveOutputReservation?: (packet: RollingDispatchPacket<TPacket>, poolId: string) => number;
|
|
171
208
|
}
|
|
172
209
|
/** Options for tuning the dispatcher (intentionally minimal per INV-S05). */
|
|
173
210
|
export interface RollingDispatchOptions {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rollingDispatch.d.ts","sourceRoot":"","sources":["../../../src/shared/dispatch/rollingDispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,KAAK,EAAE,eAAe,EAA4B,MAAM,mBAAmB,CAAC;AAanF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"rollingDispatch.d.ts","sourceRoot":"","sources":["../../../src/shared/dispatch/rollingDispatch.ts"],"names":[],"mappings":"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"}
|