@vercel/factory 0.0.9 → 0.0.10

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/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { a as StoredEntry, i as StoreDriver, n as RecordExistsError, o as StoredRecord, r as RecordNotFoundError, s as VersionConflictError, t as CollectionName } from "./driver-CrZsy7sm.mjs";
2
2
  import { a as factoryConfigSchema, i as factoryConfigRepositorySchema, n as FactoryConfigInput, r as defineFactory, t as FactoryConfig } from "./factory-config-Bu4mklm9.mjs";
3
3
  import { a as signalSchema, c as ReplyTo, d as replyToSchema, f as schemaVersionSchema, i as signalOutcomeSchema, l as SCHEMA_VERSION, m as vocabularySchema, n as SignalOutcome, o as signalSourceSchema, p as usdSchema, r as SignalSource, s as standardSignalSources, t as Signal, u as isoDateTimeSchema } from "./signal-CHVEmzvI.mjs";
4
- import { $ as ReceiptPhase, A as TaskPatch, B as taskDevboxSchema, C as ChangeTransitionOptions, Ct as changeIdSchema, D as CreateTaskInput, Dt as repositoryIdsSchema, E as CreateStoresOptions, Et as repositoryIdSchema, F as TaskKind, G as taskStateSchema, H as taskOriginSchema, I as TaskState, J as repositorySchema, K as Repository, L as harnessSchema, M as createStores, N as Task, O as DecideSignalOptions, Ot as signalIdSchema, P as TaskApproval, Q as ReceiptKind, R as standardTaskKinds, S as ChangePatch, St as TaskId, T as CreateSignalInput, Tt as receiptIdSchema, U as taskQuestionSchema, V as taskKindSchema, W as taskSchema, X as Cost, Y as repositorySlugSchema, Z as Receipt, _ as findingVerificationMethodSchema, _t as ChangeId, a as approvalRuleSchema, at as receiptStateSchema, b as ApprovalDecision, bt as RepositoryId, c as Finding, ct as standardReceiptStates, d as FindingVerification, dt as ChangeState, et as ReceiptState, f as findingCategorySchema, ft as Level, g as findingVerificationLevelSchema, gt as levelSchema, h as findingSchema, ht as changeStateSchema, i as approvalPolicySchema, it as receiptSchema, j as TaskTransitionOptions, k as FactoryStores, kt as taskIdSchema, l as FindingCategory, lt as Change, m as findingPrioritySchema, mt as changeSchema, n as ApprovalRule, nt as receiptKindSchema, o as defaultApprovalPolicy, ot as standardReceiptKinds, p as findingFingerprint, pt as changeEvidenceSchema, q as RepositorySlug, r as ApprovalSubject, rt as receiptPhaseSchema, s as requiresApproval, st as standardReceiptPhases, t as ApprovalPolicy, tt as costSchema, u as FindingPriority, ut as ChangeEvidence, v as findingVerificationSchema, vt as FactoryId, w as CreateChangeInput, wt as factoryIdSchema, x as ApprovalError, xt as SignalId, y as AppendReceiptInput, yt as ReceiptId, z as taskApprovalSchema } from "./approval-MoiwGpkh.mjs";
4
+ import { $ as Cost, A as FactoryStores, At as repositoryIdsSchema, B as standardTaskKinds, C as ChangePatch, Ct as RepositoryId, D as CreateStoresOptions, Dt as factoryIdSchema, E as CreateSignalInput, Et as changeIdSchema, F as TaskApproval, G as taskOriginSchema, H as taskDevboxSchema, I as TaskKind, J as taskStateSchema, K as taskQuestionSchema, L as TaskState, M as TaskTransitionOptions, Mt as taskIdSchema, N as createStores, O as CreateTaskInput, Ot as receiptIdSchema, P as Task, Q as repositorySlugSchema, R as coordinationRepositoryIdsSchema, S as ChangeAssociationError, St as ReceiptId, T as CreateChangeInput, Tt as TaskId, U as taskExecutionRepositoryIdsSchema, V as taskApprovalSchema, W as taskKindSchema, X as RepositorySlug, Y as Repository, Z as repositorySchema, _ as findingVerificationMethodSchema, _t as changeSchema, a as approvalRuleSchema, at as receiptKindSchema, b as ApprovalDecision, bt as ChangeId, c as Finding, ct as receiptStateSchema, d as FindingVerification, dt as standardReceiptStates, et as Receipt, f as findingCategorySchema, ft as Change, g as findingVerificationLevelSchema, gt as changeEvidenceSchema, h as findingSchema, ht as Level, i as approvalPolicySchema, it as costSchema, j as TaskPatch, jt as signalIdSchema, k as DecideSignalOptions, kt as repositoryIdSchema, l as FindingCategory, lt as standardReceiptKinds, m as findingPrioritySchema, mt as ChangeState, n as ApprovalRule, nt as ReceiptPhase, o as defaultApprovalPolicy, ot as receiptPhaseSchema, p as findingFingerprint, pt as ChangeEvidence, q as taskSchema, r as ApprovalSubject, rt as ReceiptState, s as requiresApproval, st as receiptSchema, t as ApprovalPolicy, tt as ReceiptKind, u as FindingPriority, ut as standardReceiptPhases, v as findingVerificationSchema, vt as changeStateSchema, w as ChangeTransitionOptions, wt as SignalId, x as ApprovalError, xt as FactoryId, y as AppendReceiptInput, yt as levelSchema, z as harnessSchema } from "./approval-CDiBco2V.mjs";
5
5
  import { z } from "zod";
6
6
  //#region src/briefs.d.ts
7
7
  /** The finding behind a task, when its signal carries one. */
@@ -50,6 +50,75 @@ declare const DEFAULT_MAX_LAUNCH_ATTEMPTS = 45;
50
50
  */
51
51
  declare function dispatchTask(stores: FactoryStores, taskId: TaskId, options: DispatchOptions): Promise<Task>;
52
52
  //#endregion
53
+ //#region src/metrics.d.ts
54
+ /**
55
+ * The derived review bucket from the default risk x confidence table. Derived
56
+ * at read time, never stored; "blocked" is a bucket, not a change state.
57
+ */
58
+ type ReviewBucket = "stamp" | "review" | "blocked";
59
+ declare function reviewBucket(risk: Level, confidence: Level): ReviewBucket;
60
+ interface LedgerRows {
61
+ changes: readonly Change[];
62
+ signals: readonly Signal[];
63
+ receipts: readonly Receipt[];
64
+ tasks: readonly Task[];
65
+ }
66
+ interface MetricsOptions {
67
+ /** Evaluation time; defaults to the current time. */
68
+ now?: Date;
69
+ /** Days a landed change must hold without a rollback. Default 7. */
70
+ holdDays?: number;
71
+ }
72
+ interface LedgerMetrics {
73
+ /**
74
+ * Changes a human merged that reached deployed (rolled_out for high risk)
75
+ * and did not roll back within the hold window. Includes provisional ones.
76
+ */
77
+ landed: number;
78
+ /** Landed changes still inside the hold window; the number may drop. */
79
+ provisional: number;
80
+ /** landed / (landed + rolled back + closed after ready). Null when nothing decided. */
81
+ stickRate: number | null;
82
+ /**
83
+ * Not computable from ledger rows: needs a record of human commits pushed
84
+ * to the pull request after ready, which nothing writes today.
85
+ */
86
+ stampedClean: null;
87
+ /** Median ms from the ready receipt to the merged receipt, per review bucket. */
88
+ humanTimeMs: {
89
+ stamp: number | null;
90
+ review: number | null;
91
+ };
92
+ /** Sum of settled task cost over landed changes / landed. Null until budgets settle. */
93
+ costPerLandedUsd: number | null;
94
+ /** Signals triage turned away plus changes closed in verification. */
95
+ filtered: number;
96
+ }
97
+ /**
98
+ * The six product metrics from ledger rows alone. One rule governs them: a
99
+ * change counts only after a human merged it and it stayed. Activity that
100
+ * never landed is not a result. Compute over rows already filtered to the
101
+ * period and repositories you are reporting on.
102
+ */
103
+ declare function computeLedgerMetrics(rows: LedgerRows, options?: MetricsOptions): LedgerMetrics;
104
+ type HealthColor = "green" | "yellow" | "red";
105
+ interface HealthOptions {
106
+ now?: Date;
107
+ /** Open-change cap from factory configuration; omitted disables that check. */
108
+ maxOpenChanges?: number;
109
+ }
110
+ interface RepositoryHealth {
111
+ color: HealthColor;
112
+ /** Every rule that fired, so the color is explainable from rows. */
113
+ reasons: string[];
114
+ }
115
+ /**
116
+ * Green, yellow, or red from rows, never from a model. Budget and onboarding
117
+ * signals are not evaluated yet: budgets do not settle and onboarding
118
+ * investigations are not recorded, so those spec rules have no rows to read.
119
+ */
120
+ declare function computeRepositoryHealth(rows: LedgerRows, options?: HealthOptions): RepositoryHealth;
121
+ //#endregion
53
122
  //#region src/schema/budget.d.ts
54
123
  declare const budgetMonthSchema: z.ZodString;
55
124
  declare const budgetSchema: z.ZodObject<{
@@ -139,5 +208,5 @@ interface SweepResult {
139
208
  */
140
209
  declare function sweepQueued(stores: FactoryStores, options: SweepOptions): Promise<SweepResult>;
141
210
  //#endregion
142
- export { AdoptPullRequestInput, AppendReceiptInput, ApprovalDecision, ApprovalError, ApprovalPolicy, ApprovalRule, ApprovalSubject, Budget, Change, ChangeEvidence, ChangeId, ChangePatch, ChangeState, ChangeTransitionOptions, CollectionName, Cost, CreateChangeInput, CreateSignalInput, CreateStoresOptions, CreateTaskInput, DEFAULT_MAX_LAUNCH_ATTEMPTS, DecideSignalOptions, DispatchOptions, DispatchRefusedError, FactoryConfig, FactoryConfigInput, FactoryId, FactoryStores, Finding, FindingCategory, FindingPriority, FindingVerification, InvalidTransitionError, Level, Receipt, ReceiptId, ReceiptKind, ReceiptPhase, ReceiptState, RecordExistsError, RecordNotFoundError, ReplyTo, Repository, RepositoryId, RepositorySlug, SCHEMA_VERSION, Signal, SignalCreator, SignalId, SignalOutcome, SignalSource, StoreDriver, StoredEntry, StoredRecord, SweepOptions, SweepResult, Task, TaskApproval, TaskId, TaskKind, TaskLauncher, TaskPatch, TaskState, TaskTransitionOptions, Transition, TransitionActor, VersionConflictError, adoptPullRequest, approvalPolicySchema, approvalRuleSchema, assertTransition, budgetMonthSchema, budgetSchema, changeEvidenceSchema, changeIdSchema, changeInitialState, changeSchema, changeStateSchema, changeTransitions, costSchema, createInMemoryDriver, createSignalCreator, createStores, defaultApprovalPolicy, defineFactory, dispatchTask, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, findingCategorySchema, findingFingerprint, findingForTask, findingPrioritySchema, findingSchema, findingVerificationLevelSchema, findingVerificationMethodSchema, findingVerificationSchema, fixBrief, harnessSchema, isTransitionAllowed, isoDateTimeSchema, legalNextStates, levelSchema, receiptIdSchema, receiptKindSchema, receiptPhaseSchema, receiptSchema, receiptStateSchema, replyToSchema, repositoryIdSchema, repositoryIdsSchema, repositorySchema, repositorySlugSchema, requiresApproval, scanBrief, schemaVersionSchema, signalIdSchema, signalInitialOutcome, signalOutcomeSchema, signalOutcomeTransitions, signalSchema, signalSourceSchema, standardReceiptKinds, standardReceiptPhases, standardReceiptStates, standardSignalSources, standardTaskKinds, sweepQueued, taskApprovalSchema, taskDevboxSchema, taskIdSchema, taskInitialState, taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, terminalStates, usdSchema, vocabularySchema };
211
+ export { AdoptPullRequestInput, AppendReceiptInput, ApprovalDecision, ApprovalError, ApprovalPolicy, ApprovalRule, ApprovalSubject, Budget, Change, ChangeAssociationError, ChangeEvidence, ChangeId, ChangePatch, ChangeState, ChangeTransitionOptions, CollectionName, Cost, CreateChangeInput, CreateSignalInput, CreateStoresOptions, CreateTaskInput, DEFAULT_MAX_LAUNCH_ATTEMPTS, DecideSignalOptions, DispatchOptions, DispatchRefusedError, FactoryConfig, FactoryConfigInput, FactoryId, FactoryStores, Finding, FindingCategory, FindingPriority, FindingVerification, HealthColor, HealthOptions, InvalidTransitionError, LedgerMetrics, LedgerRows, Level, MetricsOptions, Receipt, ReceiptId, ReceiptKind, ReceiptPhase, ReceiptState, RecordExistsError, RecordNotFoundError, ReplyTo, Repository, RepositoryHealth, RepositoryId, RepositorySlug, ReviewBucket, SCHEMA_VERSION, Signal, SignalCreator, SignalId, SignalOutcome, SignalSource, StoreDriver, StoredEntry, StoredRecord, SweepOptions, SweepResult, Task, TaskApproval, TaskId, TaskKind, TaskLauncher, TaskPatch, TaskState, TaskTransitionOptions, Transition, TransitionActor, VersionConflictError, adoptPullRequest, approvalPolicySchema, approvalRuleSchema, assertTransition, budgetMonthSchema, budgetSchema, changeEvidenceSchema, changeIdSchema, changeInitialState, changeSchema, changeStateSchema, changeTransitions, computeLedgerMetrics, computeRepositoryHealth, coordinationRepositoryIdsSchema, costSchema, createInMemoryDriver, createSignalCreator, createStores, defaultApprovalPolicy, defineFactory, dispatchTask, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, findingCategorySchema, findingFingerprint, findingForTask, findingPrioritySchema, findingSchema, findingVerificationLevelSchema, findingVerificationMethodSchema, findingVerificationSchema, fixBrief, harnessSchema, isTransitionAllowed, isoDateTimeSchema, legalNextStates, levelSchema, receiptIdSchema, receiptKindSchema, receiptPhaseSchema, receiptSchema, receiptStateSchema, replyToSchema, repositoryIdSchema, repositoryIdsSchema, repositorySchema, repositorySlugSchema, requiresApproval, reviewBucket, scanBrief, schemaVersionSchema, signalIdSchema, signalInitialOutcome, signalOutcomeSchema, signalOutcomeTransitions, signalSchema, signalSourceSchema, standardReceiptKinds, standardReceiptPhases, standardReceiptStates, standardSignalSources, standardTaskKinds, sweepQueued, taskApprovalSchema, taskDevboxSchema, taskExecutionRepositoryIdsSchema, taskIdSchema, taskInitialState, taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, terminalStates, usdSchema, vocabularySchema };
143
212
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { a as repositoryIdsSchema, c as SCHEMA_VERSION, d as schemaVersionSchema, f as usdSchema, i as repositoryIdSchema, l as isoDateTimeSchema, n as factoryIdSchema, o as signalIdSchema, p as vocabularySchema, r as receiptIdSchema, s as taskIdSchema, t as changeIdSchema, u as replyToSchema } from "./id-DwCihQr4.mjs";
2
2
  import { a as findingCategorySchema, c as findingSchema, d as findingVerificationSchema, i as requiresApproval, l as findingVerificationLevelSchema, n as approvalRuleSchema, o as findingFingerprint, r as defaultApprovalPolicy, s as findingPrioritySchema, t as approvalPolicySchema, u as findingVerificationMethodSchema } from "./approval-DdvEKLiE.mjs";
3
+ import { _ as taskSchema, a as DEFAULT_MAX_LAUNCH_ATTEMPTS, c as coordinationRepositoryIdsSchema, d as taskApprovalSchema, f as taskDevboxSchema, g as taskQuestionSchema, h as taskOriginSchema, i as levelSchema, l as harnessSchema, m as taskKindSchema, n as changeSchema, o as DispatchRefusedError, p as taskExecutionRepositoryIdsSchema, r as changeStateSchema, s as dispatchTask, t as changeEvidenceSchema, u as standardTaskKinds, v as taskStateSchema } from "./change-BPYhWKuL.mjs";
3
4
  import { n as RecordNotFoundError, r as VersionConflictError, t as RecordExistsError } from "./driver-Bbd25rNM.mjs";
4
- import { a as taskKindSchema, c as taskSchema, d as changeSchema, f as changeStateSchema, g as dispatchTask, h as DispatchRefusedError, i as taskDevboxSchema, l as taskStateSchema, m as DEFAULT_MAX_LAUNCH_ATTEMPTS, n as standardTaskKinds, o as taskOriginSchema, p as levelSchema, r as taskApprovalSchema, s as taskQuestionSchema, t as harnessSchema, u as changeEvidenceSchema } from "./task-DMHuvBI0.mjs";
5
5
  import { a as repositorySlugSchema, i as repositorySchema, n as factoryConfigRepositorySchema, r as factoryConfigSchema, t as defineFactory } from "./factory-config-ifd3JxiU.mjs";
6
6
  import { z } from "zod";
7
7
  import { createHash, randomUUID } from "node:crypto";
@@ -56,6 +56,168 @@ async function adoptPullRequest(stores, input) {
56
56
  });
57
57
  }
58
58
  //#endregion
59
+ //#region src/metrics.ts
60
+ const bucketTable = {
61
+ low: {
62
+ low: "review",
63
+ medium: "stamp",
64
+ high: "stamp"
65
+ },
66
+ medium: {
67
+ low: "review",
68
+ medium: "review",
69
+ high: "stamp"
70
+ },
71
+ high: {
72
+ low: "blocked",
73
+ medium: "review",
74
+ high: "review"
75
+ }
76
+ };
77
+ function reviewBucket(risk, confidence) {
78
+ return bucketTable[risk][confidence];
79
+ }
80
+ function collectFacts(rows) {
81
+ const byChange = /* @__PURE__ */ new Map();
82
+ for (const receipt of rows.receipts) {
83
+ if (receipt.kind !== "change" || receipt.changeId === void 0) continue;
84
+ const list = byChange.get(receipt.changeId) ?? [];
85
+ list.push(receipt);
86
+ byChange.set(receipt.changeId, list);
87
+ }
88
+ const at = (receipts, state) => {
89
+ const found = receipts?.find((receipt) => receipt.state === state);
90
+ return found === void 0 ? null : Date.parse(found.recordedAt);
91
+ };
92
+ return rows.changes.map((change) => {
93
+ const receipts = byChange.get(change.id);
94
+ const requiredState = change.risk === "high" ? "rolled_out" : "deployed";
95
+ return {
96
+ change,
97
+ readyAt: at(receipts, "ready"),
98
+ mergedAt: at(receipts, "merged"),
99
+ reachedAt: at(receipts, requiredState),
100
+ rolledBackAt: at(receipts, "rolled_back")
101
+ };
102
+ });
103
+ }
104
+ /** True for a change a human saw as ready and then closed unmerged. */
105
+ function closedAfterReady(facts) {
106
+ return facts.change.state === "closed" && (facts.readyAt !== null || facts.change.decidedBy !== void 0);
107
+ }
108
+ function median(values) {
109
+ if (values.length === 0) return null;
110
+ const sorted = [...values].sort((a, b) => a - b);
111
+ const mid = Math.floor(sorted.length / 2);
112
+ return sorted.length % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
113
+ }
114
+ /**
115
+ * The six product metrics from ledger rows alone. One rule governs them: a
116
+ * change counts only after a human merged it and it stayed. Activity that
117
+ * never landed is not a result. Compute over rows already filtered to the
118
+ * period and repositories you are reporting on.
119
+ */
120
+ function computeLedgerMetrics(rows, options = {}) {
121
+ const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
122
+ const holdMs = (options.holdDays ?? 7) * 24 * 60 * 60 * 1e3;
123
+ const facts = collectFacts(rows);
124
+ let landed = 0;
125
+ let provisional = 0;
126
+ let rolledBack = 0;
127
+ let closedAfterReadyCount = 0;
128
+ let closedInVerification = 0;
129
+ const landedChangeIds = /* @__PURE__ */ new Set();
130
+ for (const fact of facts) {
131
+ const { change } = fact;
132
+ if (fact.rolledBackAt !== null) {
133
+ rolledBack += 1;
134
+ continue;
135
+ }
136
+ if (change.state === "closed") {
137
+ if (closedAfterReady(fact)) closedAfterReadyCount += 1;
138
+ else closedInVerification += 1;
139
+ continue;
140
+ }
141
+ if (change.decidedBy !== void 0 && fact.reachedAt !== null) {
142
+ landed += 1;
143
+ landedChangeIds.add(change.id);
144
+ if (now - fact.reachedAt < holdMs) provisional += 1;
145
+ }
146
+ }
147
+ const stickDenominator = landed + rolledBack + closedAfterReadyCount;
148
+ const humanTimes = {
149
+ stamp: [],
150
+ review: []
151
+ };
152
+ for (const fact of facts) {
153
+ if (fact.readyAt === null || fact.mergedAt === null) continue;
154
+ const bucket = reviewBucket(fact.change.risk, fact.change.confidence);
155
+ if (bucket === "blocked") continue;
156
+ humanTimes[bucket].push(fact.mergedAt - fact.readyAt);
157
+ }
158
+ let settledOverLanded = 0;
159
+ let sawSettledCost = false;
160
+ for (const task of rows.tasks) {
161
+ if (task.settledUsd === void 0) continue;
162
+ if (task.kind === "code_change" && task.changeId !== void 0 && landedChangeIds.has(task.changeId)) {
163
+ settledOverLanded += task.settledUsd;
164
+ sawSettledCost = true;
165
+ }
166
+ }
167
+ const rejectedSignals = rows.signals.filter((signal) => signal.outcome !== "pending" && signal.outcome !== "accepted").length;
168
+ return {
169
+ landed,
170
+ provisional,
171
+ stickRate: stickDenominator === 0 ? null : landed / stickDenominator,
172
+ stampedClean: null,
173
+ humanTimeMs: {
174
+ stamp: median(humanTimes.stamp),
175
+ review: median(humanTimes.review)
176
+ },
177
+ costPerLandedUsd: sawSettledCost && landed > 0 ? settledOverLanded / landed : null,
178
+ filtered: rejectedSignals + closedInVerification
179
+ };
180
+ }
181
+ const openChangeStates = /* @__PURE__ */ new Set(["verifying", "ready"]);
182
+ /**
183
+ * Green, yellow, or red from rows, never from a model. Budget and onboarding
184
+ * signals are not evaluated yet: budgets do not settle and onboarding
185
+ * investigations are not recorded, so those spec rules have no rows to read.
186
+ */
187
+ function computeRepositoryHealth(rows, options = {}) {
188
+ const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
189
+ const dayMs = 864e5;
190
+ const red = [];
191
+ const yellow = [];
192
+ for (const receipt of rows.receipts) {
193
+ if (now - Date.parse(receipt.recordedAt) >= dayMs) continue;
194
+ if (receipt.state === "rolled_back") red.push(`rollback in the last 24h (${receipt.receiptId})`);
195
+ if (receipt.state === "cancelled" && receipt.reason?.toLowerCase().includes("kill")) red.push(`kill in the last 24h (${receipt.receiptId})`);
196
+ }
197
+ const openQuestions = rows.tasks.filter((task) => task.kind === "question" && task.state === "needs_human").length;
198
+ if (openQuestions > 0) yellow.push(`${openQuestions} open question(s)`);
199
+ const needsHuman = rows.tasks.filter((task) => task.kind !== "question" && task.state === "needs_human").length;
200
+ if (needsHuman > 0) yellow.push(`${needsHuman} task(s) waiting on a human`);
201
+ const blocked = rows.changes.filter((change) => openChangeStates.has(change.state) && reviewBucket(change.risk, change.confidence) === "blocked").length;
202
+ if (blocked > 0) yellow.push(`${blocked} change(s) in the blocked bucket`);
203
+ if (options.maxOpenChanges !== void 0) {
204
+ const open = rows.changes.filter((change) => openChangeStates.has(change.state)).length;
205
+ if (open >= options.maxOpenChanges) yellow.push(`open changes at the cap (${open}/${options.maxOpenChanges})`);
206
+ }
207
+ if (red.length > 0) return {
208
+ color: "red",
209
+ reasons: red
210
+ };
211
+ if (yellow.length > 0) return {
212
+ color: "yellow",
213
+ reasons: yellow
214
+ };
215
+ return {
216
+ color: "green",
217
+ reasons: []
218
+ };
219
+ }
220
+ //#endregion
59
221
  //#region src/schema/budget.ts
60
222
  const budgetMonthSchema = z.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/);
61
223
  const budgetSchema = z.strictObject({
@@ -86,6 +248,7 @@ const standardReceiptStates = [
86
248
  "needs_human",
87
249
  "cancelled",
88
250
  "settled",
251
+ "ready",
89
252
  "merged",
90
253
  "deployed",
91
254
  "rolled_out",
@@ -438,6 +601,18 @@ var ApprovalError = class extends Error {
438
601
  this.reason = reason;
439
602
  }
440
603
  };
604
+ var ChangeAssociationError = class extends Error {
605
+ taskId;
606
+ repositoryId;
607
+ reason;
608
+ constructor(taskId, repositoryId, reason) {
609
+ super(`change for task "${taskId}" refused: ${reason}`);
610
+ this.name = "ChangeAssociationError";
611
+ this.taskId = taskId;
612
+ this.repositoryId = repositoryId;
613
+ this.reason = reason;
614
+ }
615
+ };
441
616
  const taskReceiptStates = {
442
617
  running: "dispatched",
443
618
  succeeded: "succeeded",
@@ -446,6 +621,7 @@ const taskReceiptStates = {
446
621
  cancelled: "cancelled"
447
622
  };
448
623
  const changeReceiptStates = {
624
+ ready: "ready",
449
625
  merged: "merged",
450
626
  deployed: "deployed",
451
627
  rolled_out: "rolled_out",
@@ -688,6 +864,13 @@ function createStores(options) {
688
864
  createdAt: timestamp,
689
865
  updatedAt: timestamp
690
866
  });
867
+ await mustRead("repositories", change.repositoryId, repositorySchema);
868
+ if (change.taskId !== void 0) {
869
+ const task = (await mustRead("tasks", change.taskId, taskSchema)).value;
870
+ if (task.kind !== "code_change") throw new ChangeAssociationError(change.taskId, change.repositoryId, "changes can only be produced by code_change tasks");
871
+ const taskRepositoryId = task.repositoryIds[0];
872
+ if (taskRepositoryId !== change.repositoryId) throw new ChangeAssociationError(change.taskId, change.repositoryId, `repository "${change.repositoryId}" does not match task repository "${taskRepositoryId}"`);
873
+ }
691
874
  await driver.insert("changes", change.id, change);
692
875
  return change;
693
876
  },
@@ -805,6 +988,21 @@ async function sweepQueued(stores, options) {
805
988
  };
806
989
  const queued = (await stores.tasks.list()).filter((task) => task.state === "queued" && task.kind !== "question").sort((a, b) => a.createdAt.localeCompare(b.createdAt));
807
990
  for (const task of queued) {
991
+ if (task.kind === "coordination") {
992
+ result.skipped.push({
993
+ taskId: task.id,
994
+ reason: "coordination tasks are reconciled, not dispatched"
995
+ });
996
+ continue;
997
+ }
998
+ const executionRepositoryIds = taskExecutionRepositoryIdsSchema.safeParse(task.repositoryIds);
999
+ if (!executionRepositoryIds.success) {
1000
+ result.skipped.push({
1001
+ taskId: task.id,
1002
+ reason: "task execution requires exactly one repository"
1003
+ });
1004
+ continue;
1005
+ }
808
1006
  if (task.approval === "required") {
809
1007
  result.skipped.push({
810
1008
  taskId: task.id,
@@ -820,8 +1018,7 @@ async function sweepQueued(stores, options) {
820
1018
  });
821
1019
  continue;
822
1020
  }
823
- const repositoryId = task.repositoryIds?.[0];
824
- const repository = repositoryId === void 0 ? null : await stores.repositories.get(repositoryId);
1021
+ const repository = await stores.repositories.get(executionRepositoryIds.data[0]);
825
1022
  if (repository === null) {
826
1023
  result.skipped.push({
827
1024
  taskId: task.id,
@@ -849,6 +1046,6 @@ async function sweepQueued(stores, options) {
849
1046
  return result;
850
1047
  }
851
1048
  //#endregion
852
- export { ApprovalError, DEFAULT_MAX_LAUNCH_ATTEMPTS, DispatchRefusedError, InvalidTransitionError, RecordExistsError, RecordNotFoundError, SCHEMA_VERSION, VersionConflictError, adoptPullRequest, approvalPolicySchema, approvalRuleSchema, assertTransition, budgetMonthSchema, budgetSchema, changeEvidenceSchema, changeIdSchema, changeInitialState, changeSchema, changeStateSchema, changeTransitions, costSchema, createInMemoryDriver, createSignalCreator, createStores, defaultApprovalPolicy, defineFactory, dispatchTask, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, findingCategorySchema, findingFingerprint, findingForTask, findingPrioritySchema, findingSchema, findingVerificationLevelSchema, findingVerificationMethodSchema, findingVerificationSchema, fixBrief, harnessSchema, isTransitionAllowed, isoDateTimeSchema, legalNextStates, levelSchema, receiptIdSchema, receiptKindSchema, receiptPhaseSchema, receiptSchema, receiptStateSchema, replyToSchema, repositoryIdSchema, repositoryIdsSchema, repositorySchema, repositorySlugSchema, requiresApproval, scanBrief, schemaVersionSchema, signalIdSchema, signalInitialOutcome, signalOutcomeSchema, signalOutcomeTransitions, signalSchema, signalSourceSchema, standardReceiptKinds, standardReceiptPhases, standardReceiptStates, standardSignalSources, standardTaskKinds, sweepQueued, taskApprovalSchema, taskDevboxSchema, taskIdSchema, taskInitialState, taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, terminalStates, usdSchema, vocabularySchema };
1049
+ export { ApprovalError, ChangeAssociationError, DEFAULT_MAX_LAUNCH_ATTEMPTS, DispatchRefusedError, InvalidTransitionError, RecordExistsError, RecordNotFoundError, SCHEMA_VERSION, VersionConflictError, adoptPullRequest, approvalPolicySchema, approvalRuleSchema, assertTransition, budgetMonthSchema, budgetSchema, changeEvidenceSchema, changeIdSchema, changeInitialState, changeSchema, changeStateSchema, changeTransitions, computeLedgerMetrics, computeRepositoryHealth, coordinationRepositoryIdsSchema, costSchema, createInMemoryDriver, createSignalCreator, createStores, defaultApprovalPolicy, defineFactory, dispatchTask, factoryConfigRepositorySchema, factoryConfigSchema, factoryIdSchema, findingCategorySchema, findingFingerprint, findingForTask, findingPrioritySchema, findingSchema, findingVerificationLevelSchema, findingVerificationMethodSchema, findingVerificationSchema, fixBrief, harnessSchema, isTransitionAllowed, isoDateTimeSchema, legalNextStates, levelSchema, receiptIdSchema, receiptKindSchema, receiptPhaseSchema, receiptSchema, receiptStateSchema, replyToSchema, repositoryIdSchema, repositoryIdsSchema, repositorySchema, repositorySlugSchema, requiresApproval, reviewBucket, scanBrief, schemaVersionSchema, signalIdSchema, signalInitialOutcome, signalOutcomeSchema, signalOutcomeTransitions, signalSchema, signalSourceSchema, standardReceiptKinds, standardReceiptPhases, standardReceiptStates, standardSignalSources, standardTaskKinds, sweepQueued, taskApprovalSchema, taskDevboxSchema, taskExecutionRepositoryIdsSchema, taskIdSchema, taskInitialState, taskKindSchema, taskOriginSchema, taskQuestionSchema, taskSchema, taskStateSchema, taskTransitions, terminalStates, usdSchema, vocabularySchema };
853
1050
 
854
1051
  //# sourceMappingURL=index.mjs.map