audit-tools 0.32.7 → 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.
Files changed (128) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +2 -0
  3. package/dist/audit/cli/dispatch/quotaPool.d.ts +20 -2
  4. package/dist/audit/cli/dispatch/quotaPool.d.ts.map +1 -1
  5. package/dist/audit/cli/dispatch/quotaPool.js +38 -7
  6. package/dist/audit/cli/dispatch/quotaPool.js.map +1 -1
  7. package/dist/audit/cli/dispatch/tierRouting.d.ts +4 -1
  8. package/dist/audit/cli/dispatch/tierRouting.d.ts.map +1 -1
  9. package/dist/audit/cli/dispatch/tierRouting.js +7 -4
  10. package/dist/audit/cli/dispatch/tierRouting.js.map +1 -1
  11. package/dist/audit/cli/dispatch/types.d.ts +9 -4
  12. package/dist/audit/cli/dispatch/types.d.ts.map +1 -1
  13. package/dist/audit/cli/dispatch.d.ts +7 -0
  14. package/dist/audit/cli/dispatch.d.ts.map +1 -1
  15. package/dist/audit/cli/dispatch.js +24 -12
  16. package/dist/audit/cli/dispatch.js.map +1 -1
  17. package/dist/audit/cli/mergeAndIngestCommand.d.ts +0 -9
  18. package/dist/audit/cli/mergeAndIngestCommand.d.ts.map +1 -1
  19. package/dist/audit/cli/mergeAndIngestCommand.js +29 -1
  20. package/dist/audit/cli/mergeAndIngestCommand.js.map +1 -1
  21. package/dist/audit/cli/prompts.d.ts.map +1 -1
  22. package/dist/audit/cli/prompts.js +4 -2
  23. package/dist/audit/cli/prompts.js.map +1 -1
  24. package/dist/audit/cli/rollingAuditDispatch.d.ts.map +1 -1
  25. package/dist/audit/cli/rollingAuditDispatch.js +3 -0
  26. package/dist/audit/cli/rollingAuditDispatch.js.map +1 -1
  27. package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
  28. package/dist/audit/cli/semanticReviewStep.js +10 -6
  29. package/dist/audit/cli/semanticReviewStep.js.map +1 -1
  30. package/dist/audit/cli/steps.d.ts +20 -10
  31. package/dist/audit/cli/steps.d.ts.map +1 -1
  32. package/dist/audit/cli/steps.js +4 -2
  33. package/dist/audit/cli/steps.js.map +1 -1
  34. package/dist/audit/orchestrator/planningExecutors.d.ts.map +1 -1
  35. package/dist/audit/orchestrator/planningExecutors.js +1 -0
  36. package/dist/audit/orchestrator/planningExecutors.js.map +1 -1
  37. package/dist/audit/orchestrator/rollingDispatch.d.ts +7 -6
  38. package/dist/audit/orchestrator/rollingDispatch.d.ts.map +1 -1
  39. package/dist/audit/orchestrator/rollingDispatch.js +37 -19
  40. package/dist/audit/orchestrator/rollingDispatch.js.map +1 -1
  41. package/dist/audit/quota/index.d.ts +137 -8
  42. package/dist/audit/quota/index.d.ts.map +1 -1
  43. package/dist/audit/quota/index.js +9 -8
  44. package/dist/audit/quota/index.js.map +1 -1
  45. package/dist/remediate/contractPipeline/derive.d.ts +20 -0
  46. package/dist/remediate/contractPipeline/derive.d.ts.map +1 -1
  47. package/dist/remediate/contractPipeline/derive.js +91 -0
  48. package/dist/remediate/contractPipeline/derive.js.map +1 -1
  49. package/dist/remediate/dispatch/ownershipRegistry.d.ts +2 -22
  50. package/dist/remediate/dispatch/ownershipRegistry.d.ts.map +1 -1
  51. package/dist/remediate/dispatch/ownershipRegistry.js +7 -45
  52. package/dist/remediate/dispatch/ownershipRegistry.js.map +1 -1
  53. package/dist/remediate/index.d.ts.map +1 -1
  54. package/dist/remediate/index.js +17 -1
  55. package/dist/remediate/index.js.map +1 -1
  56. package/dist/remediate/phases/close.d.ts.map +1 -1
  57. package/dist/remediate/phases/close.js +3 -2
  58. package/dist/remediate/phases/close.js.map +1 -1
  59. package/dist/remediate/riskSignal.d.ts +6 -3
  60. package/dist/remediate/riskSignal.d.ts.map +1 -1
  61. package/dist/remediate/riskSignal.js +6 -3
  62. package/dist/remediate/riskSignal.js.map +1 -1
  63. package/dist/remediate/steps/contractPipeline.d.ts +1 -2
  64. package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
  65. package/dist/remediate/steps/contractPipeline.js +80 -106
  66. package/dist/remediate/steps/contractPipeline.js.map +1 -1
  67. package/dist/remediate/steps/dispatch.d.ts +22 -1
  68. package/dist/remediate/steps/dispatch.d.ts.map +1 -1
  69. package/dist/remediate/steps/dispatch.js +108 -5
  70. package/dist/remediate/steps/dispatch.js.map +1 -1
  71. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  72. package/dist/remediate/steps/nextStep.js +89 -116
  73. package/dist/remediate/steps/nextStep.js.map +1 -1
  74. package/dist/remediate/steps/rollingSession.d.ts +82 -17
  75. package/dist/remediate/steps/rollingSession.d.ts.map +1 -1
  76. package/dist/remediate/steps/rollingSession.js +157 -58
  77. package/dist/remediate/steps/rollingSession.js.map +1 -1
  78. package/dist/remediate/steps/types.d.ts +12 -4
  79. package/dist/remediate/steps/types.d.ts.map +1 -1
  80. package/dist/remediate/steps/types.js +1 -1
  81. package/dist/shared/dispatch/admissionLoop.d.ts +245 -0
  82. package/dist/shared/dispatch/admissionLoop.d.ts.map +1 -0
  83. package/dist/shared/dispatch/admissionLoop.js +204 -0
  84. package/dist/shared/dispatch/admissionLoop.js.map +1 -0
  85. package/dist/{remediate → shared}/dispatch/ownershipScheduler.d.ts +23 -2
  86. package/dist/shared/dispatch/ownershipScheduler.d.ts.map +1 -0
  87. package/dist/{remediate → shared}/dispatch/ownershipScheduler.js +34 -6
  88. package/dist/shared/dispatch/ownershipScheduler.js.map +1 -0
  89. package/dist/shared/dispatch/pathIdentity.d.ts +26 -0
  90. package/dist/shared/dispatch/pathIdentity.d.ts.map +1 -0
  91. package/dist/shared/dispatch/pathIdentity.js +49 -0
  92. package/dist/shared/dispatch/pathIdentity.js.map +1 -0
  93. package/dist/shared/dispatch/rollingDispatch.d.ts +37 -0
  94. package/dist/shared/dispatch/rollingDispatch.d.ts.map +1 -1
  95. package/dist/shared/dispatch/rollingDispatch.js +117 -18
  96. package/dist/shared/dispatch/rollingDispatch.js.map +1 -1
  97. package/dist/shared/dispatch/unifiedRolling.d.ts +131 -0
  98. package/dist/shared/dispatch/unifiedRolling.d.ts.map +1 -0
  99. package/dist/shared/dispatch/unifiedRolling.js +161 -0
  100. package/dist/shared/dispatch/unifiedRolling.js.map +1 -0
  101. package/dist/shared/friction/stepBoundaryCapture.d.ts +4 -1
  102. package/dist/shared/friction/stepBoundaryCapture.d.ts.map +1 -1
  103. package/dist/shared/friction/stepBoundaryCapture.js.map +1 -1
  104. package/dist/shared/index.d.ts +12 -1
  105. package/dist/shared/index.d.ts.map +1 -1
  106. package/dist/shared/index.js +18 -1
  107. package/dist/shared/index.js.map +1 -1
  108. package/dist/shared/quota/packetCost.d.ts +29 -0
  109. package/dist/shared/quota/packetCost.d.ts.map +1 -0
  110. package/dist/shared/quota/packetCost.js +51 -0
  111. package/dist/shared/quota/packetCost.js.map +1 -0
  112. package/dist/shared/quota/reservationLedger.d.ts +100 -0
  113. package/dist/shared/quota/reservationLedger.d.ts.map +1 -0
  114. package/dist/shared/quota/reservationLedger.js +224 -0
  115. package/dist/shared/quota/reservationLedger.js.map +1 -0
  116. package/dist/shared/quota/state.d.ts +20 -0
  117. package/dist/shared/quota/state.d.ts.map +1 -1
  118. package/dist/shared/quota/state.js +50 -0
  119. package/dist/shared/quota/state.js.map +1 -1
  120. package/dist/shared/quota/types.d.ts +11 -0
  121. package/dist/shared/quota/types.d.ts.map +1 -1
  122. package/dist/shared/quota/types.js.map +1 -1
  123. package/docs/audit-pkg/contracts.md +3 -1
  124. package/docs/audit-pkg/development.md +5 -4
  125. package/docs/audit-pkg/release.md +2 -3
  126. package/package.json +3 -1
  127. package/dist/remediate/dispatch/ownershipScheduler.d.ts.map +0 -1
  128. package/dist/remediate/dispatch/ownershipScheduler.js.map +0 -1
@@ -0,0 +1,245 @@
1
+ import { z } from "zod";
2
+ import type { ReservationLedger } from "../quota/reservationLedger.js";
3
+ /** One packet the admission loop may grant this pass. */
4
+ export interface AdmissionCandidate {
5
+ id: string;
6
+ /** Reservation cost = input estimate + output envelope (estimatePacketCost). */
7
+ cost: number;
8
+ /** Complexity in [0, 1] — the default capability gate's routing signal. */
9
+ complexity: number;
10
+ }
11
+ /** One pool a packet may be routed to. */
12
+ export interface AdmissionPool {
13
+ poolId: string;
14
+ /** `provider#account/model` — the metered account the lease keys to. */
15
+ resourceKey: string;
16
+ /** Live remaining token budget for `resourceKey`. `+Infinity` ⇒ optimistic. */
17
+ budget: number;
18
+ /** Declared hard in-flight cap (e.g. Codex's 6), passed verbatim. null ⇒ none. */
19
+ declaredCap: number | null;
20
+ /** Cost rank — LOWER is cheaper; the loop routes cheapest-capable-first. */
21
+ costRank: number;
22
+ /** Capability rank — HIGHER is more capable; ties break toward more capable. */
23
+ capabilityRank: number;
24
+ /** Largest packet cost this pool can fit (context window − output). */
25
+ capacityTokens: number;
26
+ }
27
+ /** A successful admission: one packet leased to one pool. */
28
+ export interface AdmissionGrant {
29
+ packet_id: string;
30
+ pool_id: string;
31
+ resource_key: string;
32
+ lease_id: string;
33
+ cost: number;
34
+ }
35
+ export declare const AdmissionGrantSchema: z.ZodObject<{
36
+ packet_id: z.ZodString;
37
+ pool_id: z.ZodString;
38
+ resource_key: z.ZodString;
39
+ lease_id: z.ZodString;
40
+ cost: z.ZodNumber;
41
+ }, "strict", z.ZodTypeAny, {
42
+ packet_id: string;
43
+ cost: number;
44
+ pool_id: string;
45
+ resource_key: string;
46
+ lease_id: string;
47
+ }, {
48
+ packet_id: string;
49
+ cost: number;
50
+ pool_id: string;
51
+ resource_key: string;
52
+ lease_id: string;
53
+ }>;
54
+ /** Why a packet was admitted or blocked — the per-admission explain record. */
55
+ export declare const AdmissionExplainSchema: z.ZodObject<{
56
+ packet_id: z.ZodString;
57
+ /** null only when NO pool was capable of the packet at all. */
58
+ pool_id: z.ZodNullable<z.ZodString>;
59
+ resource_key: z.ZodNullable<z.ZodString>;
60
+ admitted: z.ZodBoolean;
61
+ reason: z.ZodEnum<["admitted", "no_capable_pool", "budget_exhausted", "cap_reached"]>;
62
+ /** Present on an admit attempt against a real pool (budget headroom before it). */
63
+ headroom_before: z.ZodOptional<z.ZodNumber>;
64
+ outstanding_before: z.ZodOptional<z.ZodNumber>;
65
+ cost: z.ZodNumber;
66
+ }, "strict", z.ZodTypeAny, {
67
+ reason: "admitted" | "no_capable_pool" | "budget_exhausted" | "cap_reached";
68
+ admitted: boolean;
69
+ packet_id: string;
70
+ cost: number;
71
+ pool_id: string | null;
72
+ resource_key: string | null;
73
+ headroom_before?: number | undefined;
74
+ outstanding_before?: number | undefined;
75
+ }, {
76
+ reason: "admitted" | "no_capable_pool" | "budget_exhausted" | "cap_reached";
77
+ admitted: boolean;
78
+ packet_id: string;
79
+ cost: number;
80
+ pool_id: string | null;
81
+ resource_key: string | null;
82
+ headroom_before?: number | undefined;
83
+ outstanding_before?: number | undefined;
84
+ }>;
85
+ export type AdmissionExplain = z.infer<typeof AdmissionExplainSchema>;
86
+ /**
87
+ * The admission artifact both orchestrators embed in their dispatch-quota contract,
88
+ * REPLACING the removed `max_concurrent_agents` scalar. `granted_packet_ids` is the
89
+ * set the host dispatches this step (its size is the emergent admission width);
90
+ * `declared_cap` is the verbatim per-environment hard in-flight cap (null when
91
+ * none); `leases` are reconciled (freed) at result-ingest; `explains` reconstruct
92
+ * why the fan-out was the width it was.
93
+ */
94
+ export declare const DispatchAdmissionSchema: z.ZodObject<{
95
+ granted_packet_ids: z.ZodArray<z.ZodString, "many">;
96
+ declared_cap: z.ZodNullable<z.ZodNumber>;
97
+ leases: z.ZodArray<z.ZodObject<{
98
+ packet_id: z.ZodString;
99
+ pool_id: z.ZodString;
100
+ resource_key: z.ZodString;
101
+ lease_id: z.ZodString;
102
+ cost: z.ZodNumber;
103
+ }, "strict", z.ZodTypeAny, {
104
+ packet_id: string;
105
+ cost: number;
106
+ pool_id: string;
107
+ resource_key: string;
108
+ lease_id: string;
109
+ }, {
110
+ packet_id: string;
111
+ cost: number;
112
+ pool_id: string;
113
+ resource_key: string;
114
+ lease_id: string;
115
+ }>, "many">;
116
+ explains: z.ZodArray<z.ZodObject<{
117
+ packet_id: z.ZodString;
118
+ /** null only when NO pool was capable of the packet at all. */
119
+ pool_id: z.ZodNullable<z.ZodString>;
120
+ resource_key: z.ZodNullable<z.ZodString>;
121
+ admitted: z.ZodBoolean;
122
+ reason: z.ZodEnum<["admitted", "no_capable_pool", "budget_exhausted", "cap_reached"]>;
123
+ /** Present on an admit attempt against a real pool (budget headroom before it). */
124
+ headroom_before: z.ZodOptional<z.ZodNumber>;
125
+ outstanding_before: z.ZodOptional<z.ZodNumber>;
126
+ cost: z.ZodNumber;
127
+ }, "strict", z.ZodTypeAny, {
128
+ reason: "admitted" | "no_capable_pool" | "budget_exhausted" | "cap_reached";
129
+ admitted: boolean;
130
+ packet_id: string;
131
+ cost: number;
132
+ pool_id: string | null;
133
+ resource_key: string | null;
134
+ headroom_before?: number | undefined;
135
+ outstanding_before?: number | undefined;
136
+ }, {
137
+ reason: "admitted" | "no_capable_pool" | "budget_exhausted" | "cap_reached";
138
+ admitted: boolean;
139
+ packet_id: string;
140
+ cost: number;
141
+ pool_id: string | null;
142
+ resource_key: string | null;
143
+ headroom_before?: number | undefined;
144
+ outstanding_before?: number | undefined;
145
+ }>, "many">;
146
+ }, "strict", z.ZodTypeAny, {
147
+ leases: {
148
+ packet_id: string;
149
+ cost: number;
150
+ pool_id: string;
151
+ resource_key: string;
152
+ lease_id: string;
153
+ }[];
154
+ granted_packet_ids: string[];
155
+ declared_cap: number | null;
156
+ explains: {
157
+ reason: "admitted" | "no_capable_pool" | "budget_exhausted" | "cap_reached";
158
+ admitted: boolean;
159
+ packet_id: string;
160
+ cost: number;
161
+ pool_id: string | null;
162
+ resource_key: string | null;
163
+ headroom_before?: number | undefined;
164
+ outstanding_before?: number | undefined;
165
+ }[];
166
+ }, {
167
+ leases: {
168
+ packet_id: string;
169
+ cost: number;
170
+ pool_id: string;
171
+ resource_key: string;
172
+ lease_id: string;
173
+ }[];
174
+ granted_packet_ids: string[];
175
+ declared_cap: number | null;
176
+ explains: {
177
+ reason: "admitted" | "no_capable_pool" | "budget_exhausted" | "cap_reached";
178
+ admitted: boolean;
179
+ packet_id: string;
180
+ cost: number;
181
+ pool_id: string | null;
182
+ resource_key: string | null;
183
+ headroom_before?: number | undefined;
184
+ outstanding_before?: number | undefined;
185
+ }[];
186
+ }>;
187
+ export type DispatchAdmission = z.infer<typeof DispatchAdmissionSchema>;
188
+ /** Outcome of one admission pass. */
189
+ export interface AdmitBatchResult {
190
+ granted: AdmissionGrant[];
191
+ explains: AdmissionExplain[];
192
+ /** Packet ids not admitted this pass — deferred to a later grant (re-invoke). */
193
+ blocked: string[];
194
+ }
195
+ export interface AdmitBatchInput {
196
+ /** Candidate packets in PRIORITY order (highest-priority first). */
197
+ packets: AdmissionCandidate[];
198
+ pools: AdmissionPool[];
199
+ ledger: ReservationLedger;
200
+ /**
201
+ * Capability gate: may this pool handle this packet? Defaults to a size fit
202
+ * (`pool.capacityTokens >= packet.cost`) — a grounded, provider-neutral gate. A
203
+ * caller refines the complexity/risk gate by supplying its own predicate (the
204
+ * intended extension point for cost/capability routing policy).
205
+ */
206
+ capable?: (pool: AdmissionPool, packet: AdmissionCandidate) => boolean;
207
+ leaseTtlMs?: number;
208
+ }
209
+ /**
210
+ * Admit as many packets as budget + declared caps allow this pass, routing each to
211
+ * the cheapest capable pool with headroom. Every admission RESERVES the packet's
212
+ * cost against the shared ledger under its lock, so co-located dispatch loops on one
213
+ * account cannot collectively over-admit. Returns the granted set (the host
214
+ * dispatches exactly these), the explain trail, and the blocked remainder.
215
+ *
216
+ * A pool's declared in-flight cap is enforced by COUNT of its outstanding ledger
217
+ * leases (cross-process) plus this batch's grants — the only place an explicit
218
+ * agent-count exists (a verbatim environment limit), never a computed concurrency.
219
+ */
220
+ export declare function admitBatch(input: AdmitBatchInput): Promise<AdmitBatchResult>;
221
+ /**
222
+ * Build the `DispatchAdmission` contract block both orchestrators embed in their
223
+ * dispatch-quota — the single-sourced host-path admission derivation (no per-tool
224
+ * copy that could drift). Computes each packet's envelope reservation
225
+ * (`estimatePacketCost` with the declared output cap), derives the surfaced declared
226
+ * cap (the most-constraining pool's in-flight cap), and either GRANTS via
227
+ * {@link admitBatch} (host-subagent path, `grantLeases: true` — leases persist for
228
+ * reconcile at ingest) or returns a plan-only block listing every candidate
229
+ * (`grantLeases: false` — the in-process rolling engine leases per-packet itself,
230
+ * so a host grant here would double-count).
231
+ */
232
+ export declare function computeDispatchAdmission(input: {
233
+ packets: {
234
+ id: string;
235
+ inputTokens: number;
236
+ complexity: number;
237
+ }[];
238
+ pools: AdmissionPool[];
239
+ /** Declared output cap for the packet envelope (cold-start; ratio refines later). */
240
+ outputCap: number;
241
+ grantLeases: boolean;
242
+ ledger: ReservationLedger;
243
+ capable?: (pool: AdmissionPool, packet: AdmissionCandidate) => boolean;
244
+ }): Promise<DispatchAdmission>;
245
+ //# sourceMappingURL=admissionLoop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admissionLoop.d.ts","sourceRoot":"","sources":["../../../src/shared/dispatch/admissionLoop.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAGvE,yDAAyD;AACzD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,0CAA0C;AAC1C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;EAQtB,CAAC;AAEZ,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB;;IAG/B,+DAA+D;;;;;IAK/D,mFAAmF;;;;;;;;;;;;;;;;;;;;;;EAK5E,CAAC;AACZ,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;QArBhC,+DAA+D;;;;;QAK/D,mFAAmF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuB5E,CAAC;AACZ,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,iFAAiF;IACjF,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAQD;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgGlF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,wBAAwB,CAAC,KAAK,EAAE;IACpD,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACnE,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC;CACxE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAyB7B"}
@@ -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 "./ownershipRegistry.js";
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
- // Empty-scope node admits only into an otherwise-empty sub-wave (solo).
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 (wave.length === 0) {
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
- // A real-scope node cannot share a wave with an empty-scope node, nor with
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"}