@boxxy/model 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/dist/closeouts/build-close-out-schema.d.ts +31 -0
- package/dist/closeouts/close-out-answer-schema.d.ts +3 -0
- package/dist/closeouts/close-out-schema.d.ts +3 -0
- package/dist/closeouts/design-close-out-schema.d.ts +30 -0
- package/dist/closeouts/friction-schema.d.ts +14 -0
- package/dist/closeouts/open-item-schema.d.ts +3 -0
- package/dist/closeouts/review-close-out-schema.d.ts +40 -0
- package/dist/closeouts/triage-close-out-schema.d.ts +40 -0
- package/dist/closeouts/types.d.ts +221 -0
- package/dist/dispatch/build-applied-change.d.ts +6 -0
- package/dist/dispatch/collect-seat-sync.d.ts +14 -0
- package/dist/dispatch/pick-seat-take.d.ts +10 -0
- package/dist/dispatch/types.d.ts +11 -0
- package/dist/{outbox-fact-schema.d.ts → facts/outbox-fact-schema.d.ts} +1 -1
- package/dist/facts/parse-new-fact.d.ts +2 -0
- package/dist/facts/types.d.ts +191 -0
- package/dist/facts/work-fact-schemas.d.ts +293 -0
- package/dist/index.d.ts +64 -23
- package/dist/items/build-item-views.d.ts +4 -0
- package/dist/items/collect-retries.d.ts +3 -0
- package/dist/items/collect-settled.d.ts +3 -0
- package/dist/items/derive-item-state.d.ts +17 -0
- package/dist/{live-levers.d.ts → items/live-levers.d.ts} +1 -1
- package/dist/items/types.d.ts +12 -0
- package/dist/pipeline/advance.d.ts +10 -0
- package/dist/pipeline/constants.d.ts +2 -0
- package/dist/pipeline/count-trailing.d.ts +1 -0
- package/dist/pipeline/find-position.d.ts +9 -0
- package/dist/pipeline/find-step.d.ts +3 -0
- package/dist/pipeline/merge-by-time.d.ts +9 -0
- package/dist/pipeline/position-schema.d.ts +3 -0
- package/dist/pipeline/types.d.ts +28 -0
- package/dist/planning/define-admission.d.ts +3 -0
- package/dist/planning/define-gate.d.ts +3 -0
- package/dist/planning/define-signal.d.ts +3 -0
- package/dist/planning/is-filter-match.d.ts +3 -0
- package/dist/planning/limit-fill-schema.d.ts +3 -0
- package/dist/{signal-key.d.ts → planning/signal-key.d.ts} +1 -1
- package/dist/planning/types.d.ts +155 -0
- package/dist/ports/types.d.ts +289 -0
- package/dist/reports/constants.d.ts +3 -0
- package/dist/reports/cost-to-land-schema.d.ts +29 -0
- package/dist/reports/outbox-status.d.ts +16 -0
- package/dist/reports/stage-time-schema.d.ts +25 -0
- package/dist/reports/types.d.ts +5 -0
- package/dist/rows/actor-ref-schema.d.ts +2 -0
- package/dist/rows/attempt-row-schema.d.ts +3 -0
- package/dist/rows/attempt-session-row-schema.d.ts +3 -0
- package/dist/{brand-schemas.d.ts → rows/brand-schemas.d.ts} +5 -2
- package/dist/rows/caller-identity-schema.d.ts +3 -0
- package/dist/rows/criterion-schema.d.ts +3 -0
- package/dist/rows/decision-row-schema.d.ts +34 -0
- package/dist/rows/evidence-row-schema.d.ts +3 -0
- package/dist/rows/item-row-schema.d.ts +28 -0
- package/dist/rows/item-view-schema.d.ts +3 -0
- package/dist/rows/lease-row-schema.d.ts +3 -0
- package/dist/rows/lever-row-schema.d.ts +12 -0
- package/dist/rows/limit-schema.d.ts +3 -0
- package/dist/rows/limit-seat-row-schema.d.ts +3 -0
- package/dist/rows/note-row-schema.d.ts +3 -0
- package/dist/rows/notice-row-schema.d.ts +3 -0
- package/dist/rows/pull-request-row-schema.d.ts +3 -0
- package/dist/rows/relation-row-schema.d.ts +3 -0
- package/dist/{row-schemas.d.ts → rows/row-schemas.d.ts} +2 -2
- package/dist/rows/runner-row-schema.d.ts +3 -0
- package/dist/rows/spawn-request-schema.d.ts +3 -0
- package/dist/rules/define-phase.d.ts +3 -0
- package/dist/rules/load-rejection-schema.d.ts +3 -0
- package/dist/rules/types.d.ts +244 -0
- package/dist/test-utils/build-mock-cost-to-land.d.ts +2 -0
- package/dist/test-utils/build-mock-evidence-row.d.ts +1 -1
- package/dist/test-utils/build-mock-fact-id.d.ts +1 -1
- package/dist/test-utils/build-mock-frame-item.d.ts +1 -1
- package/dist/test-utils/build-mock-frame.d.ts +2 -0
- package/dist/test-utils/build-mock-item-rules.d.ts +2 -0
- package/dist/test-utils/build-mock-lease-row.d.ts +1 -1
- package/dist/test-utils/build-mock-outbox-fact.d.ts +2 -0
- package/dist/test-utils/build-mock-phase-kind.d.ts +7 -0
- package/dist/test-utils/build-mock-pipeline.d.ts +7 -0
- package/dist/test-utils/build-mock-relation-row.d.ts +2 -0
- package/dist/test-utils/build-mock-settled.d.ts +9 -0
- package/dist/test-utils/build-mock-snapshot.d.ts +1 -1
- package/dist/test-utils/build-mock-v1-attempt-row.d.ts +2 -0
- package/dist/test-utils/build-mock-v1-item-row.d.ts +2 -0
- package/dist/test-utils/build-mock-v1-item-view.d.ts +2 -0
- package/dist/test-utils/build-mock-v1-lever-row.d.ts +2 -0
- package/dist/test-utils/build-mock-v1-pull-request-row.d.ts +2 -0
- package/dist/test-utils/build-mock-work-fact.d.ts +12 -0
- package/dist/test-utils/create-fake-notifier.d.ts +9 -0
- package/dist/test-utils/create-fake-pepys-reader.d.ts +13 -0
- package/dist/test-utils/fact-sink-contract.d.ts +14 -0
- package/dist/test-utils/pepys-reader-contract.d.ts +17 -0
- package/dist/{types/core.d.ts → types.d.ts} +93 -43
- package/package.json +13 -3
- package/src/{build-close-out-schema.ts → closeouts/build-close-out-schema.ts} +5 -10
- package/src/closeouts/close-out-answer-schema.ts +42 -0
- package/src/closeouts/close-out-schema.ts +13 -0
- package/src/closeouts/design-close-out-schema.ts +20 -0
- package/src/closeouts/friction-schema.ts +11 -0
- package/src/closeouts/open-item-schema.ts +8 -0
- package/src/closeouts/review-close-out-schema.ts +38 -0
- package/src/closeouts/triage-close-out-schema.ts +29 -0
- package/src/closeouts/types.ts +230 -0
- package/src/dispatch/build-applied-change.ts +57 -0
- package/src/dispatch/collect-seat-sync.ts +86 -0
- package/src/dispatch/pick-seat-take.ts +39 -0
- package/src/dispatch/types.ts +14 -0
- package/src/facts/outbox-fact-schema.ts +22 -0
- package/src/facts/parse-new-fact.ts +16 -0
- package/src/facts/types.ts +235 -0
- package/src/facts/work-fact-schemas.ts +201 -0
- package/src/index.ts +147 -55
- package/src/items/build-item-views.ts +146 -0
- package/src/items/collect-retries.ts +12 -0
- package/src/items/collect-settled.ts +20 -0
- package/src/items/derive-item-state.ts +95 -0
- package/src/{live-levers.ts → items/live-levers.ts} +1 -1
- package/src/items/types.ts +26 -0
- package/src/pipeline/advance.ts +78 -0
- package/src/pipeline/constants.ts +12 -0
- package/src/pipeline/count-trailing.ts +16 -0
- package/src/pipeline/find-position.ts +74 -0
- package/src/pipeline/find-step.ts +6 -0
- package/src/pipeline/merge-by-time.ts +33 -0
- package/src/pipeline/position-schema.ts +27 -0
- package/src/pipeline/types.ts +47 -0
- package/src/{define-admission.ts → planning/define-admission.ts} +2 -2
- package/src/{define-gate.ts → planning/define-gate.ts} +2 -2
- package/src/{define-signal.ts → planning/define-signal.ts} +2 -2
- package/src/planning/is-filter-match.ts +10 -0
- package/src/planning/limit-fill-schema.ts +12 -0
- package/src/{signal-key.ts → planning/signal-key.ts} +1 -1
- package/src/planning/types.ts +216 -0
- package/src/ports/types.ts +412 -0
- package/src/reports/constants.ts +6 -0
- package/src/reports/cost-to-land-schema.ts +47 -0
- package/src/reports/outbox-status.ts +22 -0
- package/src/reports/stage-time-schema.ts +21 -0
- package/src/reports/types.ts +7 -0
- package/src/rows/actor-ref-schema.ts +17 -0
- package/src/rows/attempt-row-schema.ts +69 -0
- package/src/rows/attempt-session-row-schema.ts +12 -0
- package/src/{brand-schemas.ts → rows/brand-schemas.ts} +5 -2
- package/src/rows/caller-identity-schema.ts +7 -0
- package/src/rows/criterion-schema.ts +9 -0
- package/src/rows/decision-row-schema.ts +34 -0
- package/src/rows/evidence-row-schema.ts +13 -0
- package/src/rows/item-row-schema.ts +32 -0
- package/src/rows/item-view-schema.ts +31 -0
- package/src/rows/lease-row-schema.ts +12 -0
- package/src/rows/lever-row-schema.ts +17 -0
- package/src/rows/limit-schema.ts +12 -0
- package/src/rows/limit-seat-row-schema.ts +12 -0
- package/src/rows/note-row-schema.ts +12 -0
- package/src/rows/notice-row-schema.ts +17 -0
- package/src/rows/pull-request-row-schema.ts +15 -0
- package/src/rows/relation-row-schema.ts +13 -0
- package/src/rows/row-schemas.ts +30 -0
- package/src/rows/runner-row-schema.ts +9 -0
- package/src/rows/spawn-request-schema.ts +15 -0
- package/src/rules/define-phase.ts +12 -0
- package/src/rules/load-rejection-schema.ts +51 -0
- package/src/rules/types.ts +291 -0
- package/src/test-utils/build-mock-cost-to-land.ts +31 -0
- package/src/test-utils/build-mock-evidence-row.ts +2 -2
- package/src/test-utils/build-mock-fact-id.ts +3 -3
- package/src/test-utils/build-mock-frame-item.ts +10 -5
- package/src/test-utils/build-mock-frame.ts +13 -0
- package/src/test-utils/build-mock-item-rules.ts +17 -0
- package/src/test-utils/build-mock-lease-row.ts +2 -2
- package/src/test-utils/build-mock-outbox-fact.ts +22 -0
- package/src/test-utils/build-mock-phase-kind.ts +29 -0
- package/src/test-utils/build-mock-pipeline.ts +52 -0
- package/src/test-utils/{build-mock-link-row.ts → build-mock-relation-row.ts} +4 -4
- package/src/test-utils/build-mock-settled.ts +18 -0
- package/src/test-utils/build-mock-snapshot.ts +10 -2
- package/src/test-utils/{build-mock-attempt-row.ts → build-mock-v1-attempt-row.ts} +28 -9
- package/src/test-utils/{build-mock-item-row.ts → build-mock-v1-item-row.ts} +7 -3
- package/src/test-utils/build-mock-v1-item-view.ts +29 -0
- package/src/test-utils/{build-mock-lever-row.ts → build-mock-v1-lever-row.ts} +5 -3
- package/src/test-utils/build-mock-v1-pull-request-row.ts +26 -0
- package/src/test-utils/build-mock-work-fact.ts +179 -0
- package/src/test-utils/create-fake-notifier.ts +54 -0
- package/src/test-utils/create-fake-pepys-reader.ts +73 -0
- package/src/test-utils/fact-sink-contract.ts +103 -0
- package/src/test-utils/pepys-reader-contract.ts +117 -0
- package/src/{types/core.ts → types.ts} +183 -59
- package/dist/actor-ref-schema.d.ts +0 -2
- package/dist/build-applied-change.d.ts +0 -13
- package/dist/build-close-out-schema.d.ts +0 -3
- package/dist/build-item-views.d.ts +0 -2
- package/dist/define-admission.d.ts +0 -3
- package/dist/define-gate.d.ts +0 -3
- package/dist/define-signal.d.ts +0 -3
- package/dist/derive-item-state.d.ts +0 -2
- package/dist/fact-schemas.d.ts +0 -5
- package/dist/find-slot-take.d.ts +0 -5
- package/dist/friction-schema.d.ts +0 -3
- package/dist/test-utils/build-mock-attempt-row.d.ts +0 -2
- package/dist/test-utils/build-mock-item-row.d.ts +0 -2
- package/dist/test-utils/build-mock-item-rows.d.ts +0 -2
- package/dist/test-utils/build-mock-item-view.d.ts +0 -2
- package/dist/test-utils/build-mock-lever-row.d.ts +0 -2
- package/dist/test-utils/build-mock-link-row.d.ts +0 -2
- package/dist/test-utils/build-mock-pull-request-row.d.ts +0 -2
- package/dist/types/dispatch.d.ts +0 -82
- package/dist/types/facts.d.ts +0 -154
- package/dist/types/planning.d.ts +0 -94
- package/dist/types/ports.d.ts +0 -218
- package/dist/types/registries.d.ts +0 -150
- package/src/actor-ref-schema.ts +0 -9
- package/src/build-applied-change.ts +0 -71
- package/src/build-item-views.ts +0 -128
- package/src/derive-item-state.ts +0 -76
- package/src/fact-schemas.ts +0 -132
- package/src/find-slot-take.ts +0 -12
- package/src/friction-schema.ts +0 -10
- package/src/outbox-fact-schema.ts +0 -25
- package/src/row-schemas.ts +0 -89
- package/src/test-utils/build-mock-item-rows.ts +0 -14
- package/src/test-utils/build-mock-item-view.ts +0 -8
- package/src/test-utils/build-mock-pull-request-row.ts +0 -16
- package/src/types/dispatch.ts +0 -95
- package/src/types/facts.ts +0 -166
- package/src/types/planning.ts +0 -136
- package/src/types/ports.ts +0 -262
- package/src/types/registries.ts +0 -185
- /package/dist/test-utils/{seed-faker.d.ts → pin-faker-seed.d.ts} +0 -0
- /package/dist/{format-key-path.d.ts → utils/format-key-path.d.ts} +0 -0
- /package/dist/{to-json-value.d.ts → utils/to-json-value.d.ts} +0 -0
- /package/src/test-utils/{seed-faker.ts → pin-faker-seed.ts} +0 -0
- /package/src/{format-key-path.ts → utils/format-key-path.ts} +0 -0
- /package/src/{to-json-value.ts → utils/to-json-value.ts} +0 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import type { JsonValue } from '@boxxy/content-hash';
|
|
2
|
+
import type { AnyNewFact, OutboxFact } from '../facts/types.ts';
|
|
3
|
+
import type { ItemWithLevers } from '../items/types.ts';
|
|
4
|
+
import type { HoldReason } from '../pipeline/types.ts';
|
|
5
|
+
import type { Limit, LimitFill, SeatRelease, SeatTake, Snapshot } from '../planning/types.ts';
|
|
6
|
+
import type { CostToLand, StageTime } from '../reports/types.ts';
|
|
7
|
+
import type { ItemRules } from '../rules/types.ts';
|
|
8
|
+
import type {
|
|
9
|
+
AgentSessionId,
|
|
10
|
+
AtcSessionId,
|
|
11
|
+
AttemptId,
|
|
12
|
+
AttemptRow,
|
|
13
|
+
AttemptSessionRow,
|
|
14
|
+
CallerIdentity,
|
|
15
|
+
CommitSha,
|
|
16
|
+
DecisionRow,
|
|
17
|
+
Effort,
|
|
18
|
+
EvidenceRow,
|
|
19
|
+
FactId,
|
|
20
|
+
Instant,
|
|
21
|
+
ItemId,
|
|
22
|
+
ItemRow,
|
|
23
|
+
ItemState,
|
|
24
|
+
ItemView,
|
|
25
|
+
KeptSession,
|
|
26
|
+
LeaseRow,
|
|
27
|
+
LeverRow,
|
|
28
|
+
LimitName,
|
|
29
|
+
LimitSeatRow,
|
|
30
|
+
NoteRow,
|
|
31
|
+
NoticeRow,
|
|
32
|
+
PhaseName,
|
|
33
|
+
PoolName,
|
|
34
|
+
PullRequestRef,
|
|
35
|
+
PullRequestRow,
|
|
36
|
+
RelationRow,
|
|
37
|
+
RepoName,
|
|
38
|
+
RunnerId,
|
|
39
|
+
RunnerRow,
|
|
40
|
+
} from '../types.ts';
|
|
41
|
+
|
|
42
|
+
export interface ClaimStore {
|
|
43
|
+
exclusive: <T>(work: (tx: StoreTx) => Promise<T>) => Promise<T>;
|
|
44
|
+
read: <T>(work: (tx: StoreReader) => Promise<T>) => Promise<T>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The item reads derive each item's position and state at `now`, from its rows and the rules given.
|
|
48
|
+
export interface StoreReader extends OutboxReader {
|
|
49
|
+
readSnapshot: (now: Instant, rules: ItemRules) => Promise<Snapshot>;
|
|
50
|
+
readItem: (id: ItemId, now: Instant, rules: ItemRules) => Promise<ItemView | null>;
|
|
51
|
+
readItemDetail: (id: ItemId, now: Instant, rules: ItemRules) => Promise<ItemDetailRows | null>;
|
|
52
|
+
listItems: (filter: ItemFilter, now: Instant, rules: ItemRules) => Promise<ItemWithLevers[]>;
|
|
53
|
+
listOpenIssues: (repo: RepoName, now: Instant, rules: ItemRules) => Promise<ItemView[]>;
|
|
54
|
+
|
|
55
|
+
// 0 when the item phase has no lease
|
|
56
|
+
readLastLeaseGen: (item: ItemId, phase: PhaseName) => Promise<number>;
|
|
57
|
+
readAttempt: (id: AttemptId) => Promise<AttemptRow | null>;
|
|
58
|
+
listEvidence: (attempt: AttemptId) => Promise<EvidenceRow[]>;
|
|
59
|
+
|
|
60
|
+
// oldest first
|
|
61
|
+
listItemAttempts: (item: ItemId) => Promise<AttemptRow[]>;
|
|
62
|
+
|
|
63
|
+
// every attempt that has not settled, oldest first
|
|
64
|
+
listLiveAttempts: () => Promise<AttemptRow[]>;
|
|
65
|
+
|
|
66
|
+
// settled by the give-up rule at `since` or later, with the session not yet seen stopped
|
|
67
|
+
listStrayAttempts: (since: Instant) => Promise<AttemptRow[]>;
|
|
68
|
+
listSucceededAttemptsWithoutPullRequest: () => Promise<AttemptRow[]>;
|
|
69
|
+
|
|
70
|
+
// the latest attempt that the session carries, live or settled
|
|
71
|
+
findAttemptBySession: (session: AtcSessionId) => Promise<AttemptRow | null>;
|
|
72
|
+
|
|
73
|
+
// the latest attempt bound to the agent session
|
|
74
|
+
findAttemptByAgentSession: (session: AgentSessionId) => Promise<AttemptId | null>;
|
|
75
|
+
|
|
76
|
+
// The session whose latest attempt holds the caller; an older attempt of a session never
|
|
77
|
+
// counts. `ambiguous`: the latest attempts of two sessions hold it.
|
|
78
|
+
findSessionByCaller: (caller: CallerIdentity) => Promise<AtcSessionId | 'ambiguous' | null>;
|
|
79
|
+
|
|
80
|
+
// the row of the pull request in any state, whichever attempt it links
|
|
81
|
+
readPullRequestRow: (ref: PullRequestRef) => Promise<PullRequestRow | null>;
|
|
82
|
+
|
|
83
|
+
// the rows in state `open`
|
|
84
|
+
listWatchedPullRequests: () => Promise<PullRequestRow[]>;
|
|
85
|
+
|
|
86
|
+
// the limits of the last sync, by name, each with its held seats counted
|
|
87
|
+
listLimits: () => Promise<LimitFill[]>;
|
|
88
|
+
|
|
89
|
+
// the held seats, by limit and then by attempt
|
|
90
|
+
listLimitSeats: () => Promise<LimitSeatRow[]>;
|
|
91
|
+
|
|
92
|
+
// oldest first
|
|
93
|
+
listItemNotes: (item: ItemId) => Promise<NoteRow[]>;
|
|
94
|
+
|
|
95
|
+
// the rows not yet sent, whatever their tries, oldest first
|
|
96
|
+
listUnsentNotices: (limit: number) => Promise<NoticeRow[]>;
|
|
97
|
+
|
|
98
|
+
// oldest kept first
|
|
99
|
+
listKeptSessions: () => Promise<{ readonly item: ItemId; readonly kept: KeptSession }[]>;
|
|
100
|
+
|
|
101
|
+
// The settled attempts whose atc session cleanup forgets now: a succeeded attempt at once, a
|
|
102
|
+
// failed one that ended at `failedEndedBefore` or earlier. Only the latest attempt of a session
|
|
103
|
+
// counts; a kept session and a stray session that has not stopped are left out.
|
|
104
|
+
listSessionsToForget: (failedEndedBefore: Instant) => Promise<AttemptRow[]>;
|
|
105
|
+
|
|
106
|
+
// null before the runner's first heartbeat, which reads as not paused
|
|
107
|
+
readRunner: (id: RunnerId) => Promise<RunnerRow | null>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// The outbox part of the store, for the outbox sender. "Unsent" leaves out a parked row.
|
|
111
|
+
export interface OutboxReader {
|
|
112
|
+
// oldest first
|
|
113
|
+
readUnsentFacts: (limit: number) => Promise<OutboxFact[]>;
|
|
114
|
+
readOutboxCounts: () => Promise<{
|
|
115
|
+
readonly unsent: number;
|
|
116
|
+
readonly parked: number;
|
|
117
|
+
readonly oldestUnsentAt: Instant | null;
|
|
118
|
+
}>;
|
|
119
|
+
|
|
120
|
+
// the rows of the item not yet sent, parked rows included
|
|
121
|
+
readItemBehind: (
|
|
122
|
+
item: ItemId,
|
|
123
|
+
) => Promise<{ readonly facts: number; readonly oldestAt: Instant | null }>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface ItemDetailRows {
|
|
127
|
+
readonly item: ItemWithLevers;
|
|
128
|
+
readonly attempts: readonly AttemptRow[];
|
|
129
|
+
readonly relations: readonly RelationRow[];
|
|
130
|
+
readonly evidence: readonly EvidenceRow[];
|
|
131
|
+
readonly pullRequests: readonly PullRequestRow[];
|
|
132
|
+
readonly notes: readonly NoteRow[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The bookkeeping writes run outside `StoreChange`: they write no fact and move no store version.
|
|
136
|
+
export interface StoreTx extends StoreReader {
|
|
137
|
+
applyChange: (change: StoreChange) => Promise<ApplyResult>;
|
|
138
|
+
|
|
139
|
+
// a row already sent, or parked, stays as it is
|
|
140
|
+
markFactsSent: (ids: readonly FactId[], at: Instant) => Promise<void>;
|
|
141
|
+
|
|
142
|
+
// a row already sent stays as it is
|
|
143
|
+
parkFacts: (ids: readonly FactId[], reason: string, at: Instant) => Promise<void>;
|
|
144
|
+
|
|
145
|
+
// clears every park mark of a row not sent, and returns how many it cleared
|
|
146
|
+
unparkFacts: () => Promise<number>;
|
|
147
|
+
writeHeartbeat: (runner: RunnerId, at: Instant) => Promise<void>;
|
|
148
|
+
|
|
149
|
+
// Makes the stored limits the loaded ones, and the held seats follow: a seat of a limit that
|
|
150
|
+
// went, or that no longer counts its attempt's stored fields, is released; a live attempt that is
|
|
151
|
+
// not asleep gets one in each limit that newly counts it, past `max` when the limit is full.
|
|
152
|
+
syncLimits: (limits: readonly Limit[], at: Instant) => Promise<void>;
|
|
153
|
+
markPullRequestChecked: (ref: PullRequestRef, at: Instant) => Promise<void>;
|
|
154
|
+
|
|
155
|
+
// one more try of the notice; `sent` also sets its `sentAt`
|
|
156
|
+
markNoticeTried: (id: number, at: Instant, sent: boolean) => Promise<void>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// A change may write no fact, such as the suspend mark; such a change moves no store version. The
|
|
160
|
+
// attempt builder's change is one of these.
|
|
161
|
+
export interface StoreChange {
|
|
162
|
+
readonly at: Instant;
|
|
163
|
+
|
|
164
|
+
// the live lease the change was built under; none or null applies under no lease. `holdGen`:
|
|
165
|
+
// the hold generation of the lease's attempt, checked too when it is given.
|
|
166
|
+
readonly under?: {
|
|
167
|
+
readonly item: ItemId;
|
|
168
|
+
readonly phase: PhaseName;
|
|
169
|
+
readonly gen: number;
|
|
170
|
+
readonly holdGen?: number;
|
|
171
|
+
} | null;
|
|
172
|
+
readonly writes: readonly RowWrite[];
|
|
173
|
+
readonly facts: readonly AnyNewFact[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export type ApplyResult =
|
|
177
|
+
| { readonly kind: 'applied' }
|
|
178
|
+
| { readonly kind: 'stale-generation' }
|
|
179
|
+
| { readonly kind: 'lease-held' }
|
|
180
|
+
| { readonly kind: 'limit-full'; readonly limit: LimitName }
|
|
181
|
+
|
|
182
|
+
// the take names a limit that the last sync did not store
|
|
183
|
+
| { readonly kind: 'unknown-limit'; readonly limit: LimitName }
|
|
184
|
+
| { readonly kind: 'guard-mismatch'; readonly table: TableName; readonly key: JsonValue };
|
|
185
|
+
|
|
186
|
+
export interface ItemFilter {
|
|
187
|
+
readonly state?: ItemState;
|
|
188
|
+
readonly repo?: RepoName;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// The tables a plain row write may name. The seats are written only through `take-seats` and
|
|
192
|
+
// `release-seats`, so no write skips the all-or-none take.
|
|
193
|
+
export interface Tables {
|
|
194
|
+
items: ItemRow;
|
|
195
|
+
relations: RelationRow;
|
|
196
|
+
levers: LeverRow;
|
|
197
|
+
attempts: AttemptRow;
|
|
198
|
+
attempt_sessions: AttemptSessionRow;
|
|
199
|
+
leases: LeaseRow;
|
|
200
|
+
decisions: DecisionRow;
|
|
201
|
+
evidence: EvidenceRow;
|
|
202
|
+
pull_requests: PullRequestRow;
|
|
203
|
+
runners: RunnerRow;
|
|
204
|
+
notes: NoteRow;
|
|
205
|
+
notices: NoticeRow;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export type TableName = keyof Tables;
|
|
209
|
+
|
|
210
|
+
export type RowWrite =
|
|
211
|
+
| {
|
|
212
|
+
[T in TableName]: { readonly op: 'insert'; readonly table: T; readonly row: Tables[T] };
|
|
213
|
+
}[TableName]
|
|
214
|
+
| {
|
|
215
|
+
[T in TableName]: {
|
|
216
|
+
readonly op: 'update';
|
|
217
|
+
readonly table: T;
|
|
218
|
+
readonly key: Partial<Tables[T]>;
|
|
219
|
+
readonly expect: Partial<Tables[T]>;
|
|
220
|
+
readonly set: Partial<Tables[T]>;
|
|
221
|
+
};
|
|
222
|
+
}[TableName]
|
|
223
|
+
|
|
224
|
+
// a seat in every named limit, or the change is refused
|
|
225
|
+
| { readonly op: 'take-seats'; readonly take: SeatTake }
|
|
226
|
+
|
|
227
|
+
// the attempt's held seats of that hold generation; none held is no refusal
|
|
228
|
+
| { readonly op: 'release-seats'; readonly release: SeatRelease };
|
|
229
|
+
|
|
230
|
+
// The worker port: one adapter per kind of worker host, such as atc.
|
|
231
|
+
export interface WorkerKind {
|
|
232
|
+
start: (request: SpawnRequest) => Promise<StartOutcome>;
|
|
233
|
+
reconcile: (attempt: AttemptRow) => Promise<SessionReading>;
|
|
234
|
+
|
|
235
|
+
// `active` only when the session shows activity after `ifIdleSince`
|
|
236
|
+
suspend: (attempt: AttemptRow, ifIdleSince: Instant | null) => Promise<SuspendOutcome>;
|
|
237
|
+
resume: (attempt: AttemptRow) => Promise<StartOutcome>;
|
|
238
|
+
|
|
239
|
+
// stops the session and keeps it listed; safe to send twice
|
|
240
|
+
stop: (attempt: AttemptRow) => Promise<StopOutcome>;
|
|
241
|
+
|
|
242
|
+
// removes a stopped session; safe to send twice
|
|
243
|
+
forget: (session: AtcSessionId) => Promise<ForgetOutcome>;
|
|
244
|
+
|
|
245
|
+
// wakes a kept session and sends the request's prompt as a message, in place of a spawn
|
|
246
|
+
resumeKept: (kept: KeptSession, request: SpawnRequest) => Promise<StartOutcome>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export type StartOutcome =
|
|
250
|
+
| {
|
|
251
|
+
readonly kind: 'started';
|
|
252
|
+
readonly atcSession: AtcSessionId;
|
|
253
|
+
readonly agentSession: AgentSessionId | null;
|
|
254
|
+
readonly caller: CallerIdentity | null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// `capacity`: the target has no free slot for a session
|
|
258
|
+
| { readonly kind: 'refused'; readonly cause: 'capacity' | 'other'; readonly reason: string }
|
|
259
|
+
| { readonly kind: 'unknown'; readonly reason: string };
|
|
260
|
+
|
|
261
|
+
export type SuspendOutcome =
|
|
262
|
+
| { readonly kind: 'suspended' }
|
|
263
|
+
| { readonly kind: 'active' }
|
|
264
|
+
| { readonly kind: 'refused' | 'unknown'; readonly reason: string };
|
|
265
|
+
|
|
266
|
+
export type StopOutcome =
|
|
267
|
+
| { readonly kind: 'stopped' | 'absent' }
|
|
268
|
+
| { readonly kind: 'refused' | 'unknown'; readonly reason: string };
|
|
269
|
+
|
|
270
|
+
export type ForgetOutcome =
|
|
271
|
+
| { readonly kind: 'forgotten' | 'absent' }
|
|
272
|
+
| { readonly kind: 'refused' | 'unknown'; readonly reason: string };
|
|
273
|
+
|
|
274
|
+
// `running` is a live agent process, whether it works, waits for input or ended its turn.
|
|
275
|
+
export type SessionReading =
|
|
276
|
+
| {
|
|
277
|
+
readonly kind: 'running';
|
|
278
|
+
readonly atcSession: AtcSessionId;
|
|
279
|
+
readonly agentSession: AgentSessionId | null;
|
|
280
|
+
readonly caller: CallerIdentity | null;
|
|
281
|
+
readonly attention: Attention;
|
|
282
|
+
|
|
283
|
+
// the later of `lastOwnerAt` and the host's last trail event
|
|
284
|
+
readonly lastActivityAt: Instant;
|
|
285
|
+
|
|
286
|
+
// the last time the owner attached to the session
|
|
287
|
+
readonly lastOwnerAt: Instant;
|
|
288
|
+
|
|
289
|
+
// set when the session stopped at the account's usage limit: when the limit resets, or the
|
|
290
|
+
// time the limit was seen when the reset time is unknown
|
|
291
|
+
readonly usageLimitResetsAt: Instant | null;
|
|
292
|
+
}
|
|
293
|
+
| {
|
|
294
|
+
readonly kind: 'suspended';
|
|
295
|
+
readonly atcSession: AtcSessionId;
|
|
296
|
+
readonly agentSession: AgentSessionId | null;
|
|
297
|
+
readonly asked: boolean;
|
|
298
|
+
}
|
|
299
|
+
| {
|
|
300
|
+
readonly kind: 'ended';
|
|
301
|
+
readonly atcSession: AtcSessionId;
|
|
302
|
+
readonly agentSession: AgentSessionId | null;
|
|
303
|
+
}
|
|
304
|
+
| { readonly kind: 'absent' };
|
|
305
|
+
|
|
306
|
+
export type Attention = 'working' | 'needs-input' | 'turn-ended';
|
|
307
|
+
|
|
308
|
+
export interface SpawnRequest {
|
|
309
|
+
// the worker target, such as a host name of the worker kind
|
|
310
|
+
readonly target: string;
|
|
311
|
+
|
|
312
|
+
// the repo's configured path on the target
|
|
313
|
+
readonly cwd: string;
|
|
314
|
+
readonly prompt: string;
|
|
315
|
+
readonly agent: string;
|
|
316
|
+
readonly model: string;
|
|
317
|
+
readonly effort: Effort;
|
|
318
|
+
readonly name: `boxxy/${string}`;
|
|
319
|
+
readonly idempotencyKey: AttemptId;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// `command`: `boxxy item wake`. `session-ran`: a reading shows the session running while the
|
|
323
|
+
// attempt sleeps. `host-slept-unasked`: the host slept with no request, so the attempt wakes.
|
|
324
|
+
export type WakeSource = 'command' | 'session-ran' | 'host-slept-unasked';
|
|
325
|
+
|
|
326
|
+
// Called after the store change that puts an item on hold, never inside it.
|
|
327
|
+
export interface Notifier {
|
|
328
|
+
tellHold: (notice: HoldNotice) => Promise<NoticeOutcome>;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export interface HoldNotice {
|
|
332
|
+
readonly item: ItemId;
|
|
333
|
+
readonly reason: HoldReason;
|
|
334
|
+
readonly at: Instant;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export type NoticeOutcome =
|
|
338
|
+
| { readonly kind: 'sent' }
|
|
339
|
+
| { readonly kind: 'failed'; readonly reason: string };
|
|
340
|
+
|
|
341
|
+
// Proves who sent a request to the server. The server's root middleware calls it for every
|
|
342
|
+
// session and hook procedure, and knows no header and no token itself. null: no proof, so the
|
|
343
|
+
// call is refused.
|
|
344
|
+
export interface CallerProof {
|
|
345
|
+
prove: (request: Request) => Promise<CallerIdentity | null>;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// The pools that the runner gives no pick in, after the worker refused a session for capacity.
|
|
349
|
+
export interface CapacityBackoff {
|
|
350
|
+
hold: (pool: PoolName, until: Instant) => void;
|
|
351
|
+
|
|
352
|
+
// each pool whose `until` is after `now`
|
|
353
|
+
listHeld: (now: Instant) => readonly PoolName[];
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export interface SessionFault {
|
|
357
|
+
readonly kind: 'fault';
|
|
358
|
+
readonly fault: 'agent-session-owned';
|
|
359
|
+
readonly agentSession: AgentSessionId;
|
|
360
|
+
readonly attempt: AttemptId;
|
|
361
|
+
readonly holder: AttemptId;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// What one batch sent to the sink got back. `retry` may or may not have stored the batch.
|
|
365
|
+
export type SinkAnswer =
|
|
366
|
+
| { readonly kind: 'stored' }
|
|
367
|
+
| { readonly kind: 'retry'; readonly reason: string }
|
|
368
|
+
| {
|
|
369
|
+
readonly kind: 'refused-batch';
|
|
370
|
+
readonly status: 400 | 413 | 422;
|
|
371
|
+
readonly reason: string;
|
|
372
|
+
}
|
|
373
|
+
| { readonly kind: 'refused-sink'; readonly status: number; readonly reason: string };
|
|
374
|
+
|
|
375
|
+
export interface FactSink {
|
|
376
|
+
// Sends the rows as one batch, in the order given. It never splits and never reorders.
|
|
377
|
+
send: (facts: readonly OutboxFact[]) => Promise<SinkAnswer>;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// The reports of pepys about one item.
|
|
381
|
+
export interface PepysReader {
|
|
382
|
+
readCostToLand: (item: ItemId) => Promise<PepysAnswer<CostToLand>>;
|
|
383
|
+
|
|
384
|
+
// the environment is the adapter's own
|
|
385
|
+
readStageTime: (item: ItemId) => Promise<PepysAnswer<StageTime>>;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export type PepysAnswer<Value> =
|
|
389
|
+
| { readonly kind: 'value'; readonly value: Value }
|
|
390
|
+
| { readonly kind: 'unavailable'; readonly reason: string };
|
|
391
|
+
|
|
392
|
+
export interface Forge {
|
|
393
|
+
readPullRequest: (ref: PullRequestRef) => Promise<PullRequestReading | 'not-found'>;
|
|
394
|
+
listOpenPullRequests: (repo: RepoName) => Promise<PullRequestRef[]>;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export interface PullRequestReading {
|
|
398
|
+
readonly url: string;
|
|
399
|
+
|
|
400
|
+
// GitHub's own `id` of the pull request
|
|
401
|
+
readonly githubId: number;
|
|
402
|
+
readonly state: 'open' | 'closed' | 'merged';
|
|
403
|
+
readonly mergedAt: Instant | null;
|
|
404
|
+
readonly mergeCommit: CommitSha | null;
|
|
405
|
+
readonly commits: readonly { readonly sha: CommitSha; readonly message: string }[];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export interface Clock {
|
|
409
|
+
now: () => Instant;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export type NewId = () => string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const phaseSteps = ['triage', 'design', 'build', 'review'] as const;
|
|
2
|
+
export const stageSteps = [...phaseSteps, 'merge', 'release', 'deploy'] as const;
|
|
3
|
+
|
|
4
|
+
// `reported` and `none` are the values of the telemetry table's `cost_basis`. `turn` is the
|
|
5
|
+
// cost query's own: the line is a sum of turn cost rows.
|
|
6
|
+
export const costBases = ['reported', 'turn', 'none'] as const;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { costBases } from './constants.ts';
|
|
3
|
+
|
|
4
|
+
const tokenCountSchema = z.number().int();
|
|
5
|
+
|
|
6
|
+
const costLineSchema = z.object({
|
|
7
|
+
phase: z.string(),
|
|
8
|
+
|
|
9
|
+
// the attempt's round is above 1
|
|
10
|
+
rework: z.boolean(),
|
|
11
|
+
product: z.string(),
|
|
12
|
+
|
|
13
|
+
// null only on a `turn` line whose record carried no model
|
|
14
|
+
model: z.string().nullable(),
|
|
15
|
+
requests: tokenCountSchema,
|
|
16
|
+
inputTokens: tokenCountSchema,
|
|
17
|
+
cacheReadTokens: tokenCountSchema,
|
|
18
|
+
cacheCreationTokens: tokenCountSchema,
|
|
19
|
+
outputTokens: tokenCountSchema,
|
|
20
|
+
|
|
21
|
+
// null: not known, never 0
|
|
22
|
+
costUSD: z.number().nullable(),
|
|
23
|
+
costBasis: z.enum(costBases),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const totalsSchema = z.object({
|
|
27
|
+
requests: tokenCountSchema,
|
|
28
|
+
|
|
29
|
+
// the sum of the lines that have a cost
|
|
30
|
+
costUSD: z.number(),
|
|
31
|
+
reworkCostUSD: z.number(),
|
|
32
|
+
|
|
33
|
+
// requests whose cost no table holds; above 0, `costUSD` is a lower bound
|
|
34
|
+
requestsWithoutCost: tokenCountSchema,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// The output of the pepys query `boxxy_cost_to_land`: a plain object with no transform, as the
|
|
38
|
+
// query kit asks of an output.
|
|
39
|
+
export const costToLandSchema = z.object({
|
|
40
|
+
item: z.string(),
|
|
41
|
+
|
|
42
|
+
// the earliest merge of a pull request the item's attempts produced; null: not landed yet
|
|
43
|
+
landedAt: z.string().nullable(),
|
|
44
|
+
attempts: tokenCountSchema,
|
|
45
|
+
lines: z.array(costLineSchema),
|
|
46
|
+
totals: totalsSchema,
|
|
47
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// What `runner.status` shows of the outbox. It has no readonly and plain numbers, because the wire schema
|
|
2
|
+
// in `@boxxy/contract` holds plain numbers; a test there keeps the two equal.
|
|
3
|
+
export interface OutboxStatus {
|
|
4
|
+
sink: 'pepys' | 'none';
|
|
5
|
+
state: 'idle' | 'sending' | 'backing-off' | 'blocked' | 'off';
|
|
6
|
+
unsent: number;
|
|
7
|
+
parked: number;
|
|
8
|
+
|
|
9
|
+
// minutes since the oldest unsent row was written; null when no row is unsent
|
|
10
|
+
lag: number | null;
|
|
11
|
+
|
|
12
|
+
// the first answer that was not `stored`, since the last `stored`
|
|
13
|
+
failingSince: number | null;
|
|
14
|
+
nextTryAt: number | null;
|
|
15
|
+
lastError: {
|
|
16
|
+
kind: 'retry' | 'refused-batch' | 'refused-sink';
|
|
17
|
+
status: number | null;
|
|
18
|
+
message: string;
|
|
19
|
+
at: number;
|
|
20
|
+
} | null;
|
|
21
|
+
lastStoredAt: number | null;
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { stageSteps } from './constants.ts';
|
|
3
|
+
|
|
4
|
+
const stepSchema = z.object({
|
|
5
|
+
step: z.enum(stageSteps),
|
|
6
|
+
minutes: z.number().nullable(),
|
|
7
|
+
endedAt: z.string().nullable(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
// The output of the pepys query `boxxy_stage_time`.
|
|
11
|
+
export const stageTimeSchema = z.object({
|
|
12
|
+
item: z.string(),
|
|
13
|
+
environment: z.string(),
|
|
14
|
+
|
|
15
|
+
// always one per stage step, in the order of `stageSteps`
|
|
16
|
+
steps: z.array(stepSchema),
|
|
17
|
+
rework: z.object({ count: z.number().int(), minutes: z.number() }),
|
|
18
|
+
reviewWaitMinutes: z.number().nullable(),
|
|
19
|
+
sleepMinutes: z.number(),
|
|
20
|
+
pullRequests: z.array(z.string()),
|
|
21
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { costToLandSchema } from './cost-to-land-schema.ts';
|
|
3
|
+
import type { stageTimeSchema } from './stage-time-schema.ts';
|
|
4
|
+
|
|
5
|
+
export type CostToLand = z.output<typeof costToLandSchema>;
|
|
6
|
+
|
|
7
|
+
export type StageTime = z.output<typeof stageTimeSchema>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ActorRef } from '../types.ts';
|
|
3
|
+
|
|
4
|
+
const runnerIdSchema = z.string().regex(/^[a-z0-9-]+$/u);
|
|
5
|
+
|
|
6
|
+
const attemptIdSchema = z
|
|
7
|
+
.string()
|
|
8
|
+
.regex(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u);
|
|
9
|
+
|
|
10
|
+
// The parts are unbranded strings, so that the output is `ActorRef` itself: a branded part
|
|
11
|
+
// would give a narrower template type than the one the store and the facts declare.
|
|
12
|
+
export const actorRefSchema = z.union([
|
|
13
|
+
z.literal('boxxy:owner:local'),
|
|
14
|
+
z.templateLiteral(['boxxy:runner:', runnerIdSchema]),
|
|
15
|
+
z.templateLiteral(['boxxy:attempt:', attemptIdSchema]),
|
|
16
|
+
z.templateLiteral(['boxxy:hook:', attemptIdSchema]),
|
|
17
|
+
]) satisfies z.ZodType<ActorRef>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { contentHashSchema } from '@boxxy/content-hash';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { closeOutSchema } from '../closeouts/close-out-schema.ts';
|
|
4
|
+
import type { AttemptRow } from '../types.ts';
|
|
5
|
+
import { brandSchemas } from './brand-schemas.ts';
|
|
6
|
+
import { callerIdentitySchema } from './caller-identity-schema.ts';
|
|
7
|
+
|
|
8
|
+
const countSchema = z.number().int().nonnegative();
|
|
9
|
+
const instantOrNullSchema = brandSchemas.instant.nullable();
|
|
10
|
+
const fieldValuesSchema = z.record(z.string(), z.array(z.string()));
|
|
11
|
+
|
|
12
|
+
export const attemptRowSchema: z.ZodType<AttemptRow> = z.object({
|
|
13
|
+
id: brandSchemas.attemptId,
|
|
14
|
+
item: brandSchemas.itemId,
|
|
15
|
+
phase: brandSchemas.phaseName,
|
|
16
|
+
number: z.number().int().positive(),
|
|
17
|
+
runner: brandSchemas.runnerId,
|
|
18
|
+
recipeHash: brandSchemas.recipeHash,
|
|
19
|
+
rulesVersion: brandSchemas.rulesVersion,
|
|
20
|
+
pool: brandSchemas.poolName,
|
|
21
|
+
leaseGen: countSchema,
|
|
22
|
+
spawnRequest: z.json(),
|
|
23
|
+
spawnRequestHash: contentHashSchema,
|
|
24
|
+
state: z.enum([
|
|
25
|
+
'starting',
|
|
26
|
+
'running',
|
|
27
|
+
'reconciling',
|
|
28
|
+
'sleeping',
|
|
29
|
+
'waking',
|
|
30
|
+
'closing',
|
|
31
|
+
'succeeded',
|
|
32
|
+
'failed',
|
|
33
|
+
]),
|
|
34
|
+
failure: z.string().nullable(),
|
|
35
|
+
atcSession: brandSchemas.atcSessionId.nullable(),
|
|
36
|
+
caller: callerIdentitySchema.nullable(),
|
|
37
|
+
claimedAt: brandSchemas.instant,
|
|
38
|
+
startedAt: instantOrNullSchema,
|
|
39
|
+
endedAt: instantOrNullSchema,
|
|
40
|
+
fields: fieldValuesSchema,
|
|
41
|
+
mode: z.enum(['agent', 'attended']),
|
|
42
|
+
round: z.number().int().positive(),
|
|
43
|
+
input: z.json(),
|
|
44
|
+
holdGen: z.number().int().positive(),
|
|
45
|
+
closeOut: closeOutSchema.nullable(),
|
|
46
|
+
closeOutHash: contentHashSchema.nullable(),
|
|
47
|
+
closeOutFact: brandSchemas.factId.nullable(),
|
|
48
|
+
closing: z
|
|
49
|
+
.enum([
|
|
50
|
+
'closed-out',
|
|
51
|
+
'timed-out',
|
|
52
|
+
'item-closed',
|
|
53
|
+
'stalled',
|
|
54
|
+
'skill-missing',
|
|
55
|
+
'turn-ended',
|
|
56
|
+
'usage-limit',
|
|
57
|
+
])
|
|
58
|
+
.nullable(),
|
|
59
|
+
closingAt: instantOrNullSchema,
|
|
60
|
+
suspendRequestedAt: instantOrNullSchema,
|
|
61
|
+
sleptAt: instantOrNullSchema,
|
|
62
|
+
wakeRequestedAt: instantOrNullSchema,
|
|
63
|
+
wokenAt: instantOrNullSchema,
|
|
64
|
+
outcome: z.union([brandSchemas.outcomeName, z.enum(['lost', 'unsettleable'])]).nullable(),
|
|
65
|
+
returnedOutcome: z.string().nullable(),
|
|
66
|
+
strayedAt: instantOrNullSchema,
|
|
67
|
+
sessionStoppedAt: instantOrNullSchema,
|
|
68
|
+
sessionForgottenAt: instantOrNullSchema,
|
|
69
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AttemptSessionRow } from '../types.ts';
|
|
3
|
+
import { brandSchemas } from './brand-schemas.ts';
|
|
4
|
+
|
|
5
|
+
export const attemptSessionRowSchema: z.ZodType<AttemptSessionRow> = z.object({
|
|
6
|
+
attempt: brandSchemas.attemptId,
|
|
7
|
+
agentSession: brandSchemas.agentSessionId,
|
|
8
|
+
atcSession: brandSchemas.atcSessionId,
|
|
9
|
+
product: z.enum(['claude_code', 'codex']),
|
|
10
|
+
source: z.enum(['worker', 'hook']),
|
|
11
|
+
seenAt: brandSchemas.instant,
|
|
12
|
+
});
|
|
@@ -12,7 +12,7 @@ export const brandSchemas = {
|
|
|
12
12
|
itemId: uuidV7Schema.brand<'ItemId'>(),
|
|
13
13
|
attemptId: uuidV7Schema.brand<'AttemptId'>(),
|
|
14
14
|
decisionId: uuidV7Schema.brand<'DecisionId'>(),
|
|
15
|
-
factId:
|
|
15
|
+
factId: uuidV7Schema.brand<'FactId'>(),
|
|
16
16
|
runnerId: z
|
|
17
17
|
.string()
|
|
18
18
|
.regex(/^[a-z0-9-]+$/u)
|
|
@@ -32,10 +32,13 @@ export const brandSchemas = {
|
|
|
32
32
|
.regex(/^a[1-9]\d*$/u)
|
|
33
33
|
.brand<'CriterionId'>(),
|
|
34
34
|
itemKindName: nameSchema.brand<'ItemKindName'>(),
|
|
35
|
-
|
|
35
|
+
relationTypeName: nameSchema.brand<'RelationTypeName'>(),
|
|
36
36
|
phaseName: nameSchema.brand<'PhaseName'>(),
|
|
37
37
|
pipelineName: nameSchema.brand<'PipelineName'>(),
|
|
38
38
|
poolName: nameSchema.brand<'PoolName'>(),
|
|
39
|
+
limitName: nameSchema.brand<'LimitName'>(),
|
|
40
|
+
outcomeName: nameSchema.brand<'OutcomeName'>(),
|
|
41
|
+
rulesHoldReason: nameSchema.brand<'RulesHoldReason'>(),
|
|
39
42
|
recipeName: nameSchema.brand<'RecipeName'>(),
|
|
40
43
|
workerKindName: nameSchema.brand<'WorkerKindName'>(),
|
|
41
44
|
sourceName: nameSchema.brand<'SourceName'>(),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Criterion } from '../types.ts';
|
|
3
|
+
import { brandSchemas } from './brand-schemas.ts';
|
|
4
|
+
|
|
5
|
+
export const criterionSchema: z.ZodType<Criterion> = z.object({
|
|
6
|
+
id: brandSchemas.criterionId,
|
|
7
|
+
text: z.string().min(1),
|
|
8
|
+
evidence: z.array(brandSchemas.factId),
|
|
9
|
+
});
|