@boxxy/model 0.1.1 → 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/facts/outbox-fact-schema.d.ts +3 -0
- 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 -16
- 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/rows/row-schemas.d.ts +5 -0
- 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 +2 -0
- package/dist/test-utils/build-mock-fact-id.d.ts +2 -0
- package/dist/test-utils/build-mock-frame-item.d.ts +2 -0
- 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 +2 -0
- 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} +94 -36
- package/dist/utils/format-key-path.d.ts +1 -0
- package/dist/utils/to-json-value.d.ts +2 -0
- package/package.json +14 -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 -41
- 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 +17 -0
- package/src/test-utils/build-mock-fact-id.ts +7 -0
- package/src/test-utils/build-mock-frame-item.ts +14 -0
- 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 +13 -0
- 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} +184 -48
- package/src/utils/format-key-path.ts +19 -0
- package/src/utils/to-json-value.ts +25 -0
- package/dist/build-close-out-schema.d.ts +0 -3
- 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/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-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/derive-item-state.ts +0 -76
- package/src/fact-schemas.ts +0 -138
- package/src/friction-schema.ts +0 -10
- package/src/test-utils/build-mock-item-rows.ts +0 -14
- 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/src/test-utils/{seed-faker.ts → pin-faker-seed.ts} +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { PepysAnswer, PepysReader } from '../ports/types.ts';
|
|
2
|
+
import { stageSteps } from '../reports/constants.ts';
|
|
3
|
+
import type { CostToLand, StageTime } from '../reports/types.ts';
|
|
4
|
+
import type { ItemId } from '../types.ts';
|
|
5
|
+
|
|
6
|
+
interface FakeValues {
|
|
7
|
+
readonly cost?: CostToLand;
|
|
8
|
+
readonly stageTime?: StageTime;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FakePepysReader extends PepysReader {
|
|
12
|
+
// an item with no value set answers as pepys does for an item it holds no fact of
|
|
13
|
+
set: (item: ItemId, values: FakeValues) => void;
|
|
14
|
+
|
|
15
|
+
// the next call, of either method, answers `unavailable` with this reason; each call of
|
|
16
|
+
// `failNext` fails one more call
|
|
17
|
+
failNext: (reason: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createFakePepysReader(environment = 'production'): FakePepysReader {
|
|
21
|
+
const costs = new Map<ItemId, CostToLand>();
|
|
22
|
+
const stageTimes = new Map<ItemId, StageTime>();
|
|
23
|
+
|
|
24
|
+
const failures: string[] = [];
|
|
25
|
+
|
|
26
|
+
const answer = <Value>(value: Value): Promise<PepysAnswer<Value>> => {
|
|
27
|
+
const reason = failures.shift();
|
|
28
|
+
|
|
29
|
+
const answered: PepysAnswer<Value> =
|
|
30
|
+
reason === undefined ? { kind: 'value', value } : { kind: 'unavailable', reason };
|
|
31
|
+
|
|
32
|
+
return Promise.resolve(answered);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
set: (item, values) => {
|
|
37
|
+
if (values.cost !== undefined) {
|
|
38
|
+
costs.set(item, values.cost);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (values.stageTime !== undefined) {
|
|
42
|
+
stageTimes.set(item, values.stageTime);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
failNext: (reason) => {
|
|
46
|
+
failures.push(reason);
|
|
47
|
+
},
|
|
48
|
+
readCostToLand: (item) => answer(costs.get(item) ?? buildEmptyCost(item)),
|
|
49
|
+
readStageTime: (item) => answer(stageTimes.get(item) ?? buildEmptyStageTime(item, environment)),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function buildEmptyCost(item: ItemId): CostToLand {
|
|
54
|
+
return {
|
|
55
|
+
item: `boxxy:item:${item}`,
|
|
56
|
+
landedAt: null,
|
|
57
|
+
attempts: 0,
|
|
58
|
+
lines: [],
|
|
59
|
+
totals: { requests: 0, costUSD: 0, reworkCostUSD: 0, requestsWithoutCost: 0 },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildEmptyStageTime(item: ItemId, environment: string): StageTime {
|
|
64
|
+
return {
|
|
65
|
+
item: `boxxy:item:${item}`,
|
|
66
|
+
environment,
|
|
67
|
+
steps: stageSteps.map((step) => ({ step, minutes: null, endedAt: null })),
|
|
68
|
+
rework: { count: 0, minutes: 0 },
|
|
69
|
+
reviewWaitMinutes: null,
|
|
70
|
+
sleepMinutes: 0,
|
|
71
|
+
pullRequests: [],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { FactId, FactSink, OutboxFact } from '@boxxy/model';
|
|
2
|
+
import { buildMockOutboxFact } from '@boxxy/model/test-utils/build-mock-outbox-fact';
|
|
3
|
+
|
|
4
|
+
export interface FactSinkHarness {
|
|
5
|
+
readonly sink: FactSink;
|
|
6
|
+
|
|
7
|
+
// the fact IDs the log holds, in the order they arrived, a copy of an ID counted once
|
|
8
|
+
readStored: () => Promise<readonly FactId[]>;
|
|
9
|
+
|
|
10
|
+
// the same fact, changed so that the sink refuses a batch that holds it
|
|
11
|
+
corrupt: (fact: OutboxFact) => OutboxFact;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type FactSinkCase = readonly [
|
|
15
|
+
title: string,
|
|
16
|
+
check: (harness: FactSinkHarness) => Promise<{ received: unknown; expected: unknown }>,
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
// The cases that every adapter of the sink port must pass: the fake, and the pepys adapter behind
|
|
20
|
+
// ingest's route handler. Each case gets a harness with an empty log.
|
|
21
|
+
export const factSinkContract: readonly FactSinkCase[] = [
|
|
22
|
+
[
|
|
23
|
+
'a batch is stored whole and in order',
|
|
24
|
+
async (harness) => {
|
|
25
|
+
const batch = buildFacts(3, 1);
|
|
26
|
+
|
|
27
|
+
const answer = await harness.sink.send(batch);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
received: { answer, stored: await harness.readStored() },
|
|
31
|
+
expected: { answer: { kind: 'stored' }, stored: batch.map((fact) => fact.factId) },
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
'the same batch sent twice is stored as one copy of each fact',
|
|
37
|
+
async (harness) => {
|
|
38
|
+
const batch = buildFacts(2, 1);
|
|
39
|
+
|
|
40
|
+
await harness.sink.send(batch);
|
|
41
|
+
|
|
42
|
+
const answer = await harness.sink.send(batch);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
received: { answer, stored: await harness.readStored() },
|
|
46
|
+
expected: { answer: { kind: 'stored' }, stored: batch.map((fact) => fact.factId) },
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
'two batches sent one after the other keep their order',
|
|
52
|
+
async (harness) => {
|
|
53
|
+
const first = buildFacts(2, 1);
|
|
54
|
+
const second = buildFacts(2, 3);
|
|
55
|
+
|
|
56
|
+
await harness.sink.send(first);
|
|
57
|
+
await harness.sink.send(second);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
received: await harness.readStored(),
|
|
61
|
+
expected: [...first, ...second].map((fact) => fact.factId),
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
'a batch with a bad fact is refused whole and stores none of it',
|
|
67
|
+
async (harness) => {
|
|
68
|
+
const [good, bad] = buildPair();
|
|
69
|
+
|
|
70
|
+
const answer = await harness.sink.send([good, harness.corrupt(bad)]);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
received: { kind: answer.kind, stored: await harness.readStored() },
|
|
74
|
+
expected: { kind: 'refused-batch', stored: [] },
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
'a good batch is stored after a bad one was refused',
|
|
80
|
+
async (harness) => {
|
|
81
|
+
const [good, bad] = buildPair();
|
|
82
|
+
|
|
83
|
+
await harness.sink.send([good, harness.corrupt(bad)]);
|
|
84
|
+
|
|
85
|
+
const answer = await harness.sink.send([good]);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
received: { answer, stored: await harness.readStored() },
|
|
89
|
+
expected: { answer: { kind: 'stored' }, stored: [good.factId] },
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
function buildFacts(count: number, firstSeq: number): OutboxFact[] {
|
|
96
|
+
return Array.from({ length: count }, (_unused, index) =>
|
|
97
|
+
buildMockOutboxFact({ seq: firstSeq + index }),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildPair(): [OutboxFact, OutboxFact] {
|
|
102
|
+
return [buildMockOutboxFact({ seq: 1 }), buildMockOutboxFact({ seq: 2 })];
|
|
103
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
import type { PepysReader } from '../ports/types.ts';
|
|
3
|
+
import type { CostToLand } from '../reports/types.ts';
|
|
4
|
+
import { brandSchemas } from '../rows/brand-schemas.ts';
|
|
5
|
+
import type { ItemId } from '../types.ts';
|
|
6
|
+
import { buildMockCostToLand } from './build-mock-cost-to-land.ts';
|
|
7
|
+
|
|
8
|
+
export interface PepysReaderHarness {
|
|
9
|
+
readonly reader: PepysReader;
|
|
10
|
+
|
|
11
|
+
// the environment the reader passes to the stage times
|
|
12
|
+
readonly environment: string;
|
|
13
|
+
|
|
14
|
+
// pepys holds this cost for the item
|
|
15
|
+
writeCost: (item: ItemId, cost: CostToLand) => Promise<void>;
|
|
16
|
+
|
|
17
|
+
// every later call finds pepys down
|
|
18
|
+
takeDown: () => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type PepysReaderCase = readonly [
|
|
22
|
+
title: string,
|
|
23
|
+
check: (harness: PepysReaderHarness) => Promise<{ received: unknown; expected: unknown }>,
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// The cases that every adapter of the reader port must pass: the fake, and the pepys adapter
|
|
27
|
+
// behind the plug-in's queries. Each case gets a harness with an empty pepys.
|
|
28
|
+
export const pepysReaderContract: readonly PepysReaderCase[] = [
|
|
29
|
+
[
|
|
30
|
+
'an item that pepys holds no fact of gives the empty cost, not unavailable',
|
|
31
|
+
async (harness) => {
|
|
32
|
+
const item = buildItemId();
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
received: await harness.reader.readCostToLand(item),
|
|
36
|
+
expected: {
|
|
37
|
+
kind: 'value',
|
|
38
|
+
value: {
|
|
39
|
+
item: `boxxy:item:${item}`,
|
|
40
|
+
landedAt: null,
|
|
41
|
+
attempts: 0,
|
|
42
|
+
lines: [],
|
|
43
|
+
totals: { requests: 0, costUSD: 0, reworkCostUSD: 0, requestsWithoutCost: 0 },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
'an item that pepys holds no fact of gives seven steps with null minutes',
|
|
51
|
+
async (harness) => {
|
|
52
|
+
const item = buildItemId();
|
|
53
|
+
const empty = { minutes: null, endedAt: null };
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
received: await harness.reader.readStageTime(item),
|
|
57
|
+
expected: {
|
|
58
|
+
kind: 'value',
|
|
59
|
+
value: {
|
|
60
|
+
item: `boxxy:item:${item}`,
|
|
61
|
+
environment: harness.environment,
|
|
62
|
+
steps: [
|
|
63
|
+
{ step: 'triage', ...empty },
|
|
64
|
+
{ step: 'design', ...empty },
|
|
65
|
+
{ step: 'build', ...empty },
|
|
66
|
+
{ step: 'review', ...empty },
|
|
67
|
+
{ step: 'merge', ...empty },
|
|
68
|
+
{ step: 'release', ...empty },
|
|
69
|
+
{ step: 'deploy', ...empty },
|
|
70
|
+
],
|
|
71
|
+
rework: { count: 0, minutes: 0 },
|
|
72
|
+
reviewWaitMinutes: null,
|
|
73
|
+
sleepMinutes: 0,
|
|
74
|
+
pullRequests: [],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
[
|
|
81
|
+
'a cost that pepys holds comes back whole',
|
|
82
|
+
async (harness) => {
|
|
83
|
+
const item = buildItemId();
|
|
84
|
+
const cost = buildMockCostToLand({ item: `boxxy:item:${item}` });
|
|
85
|
+
|
|
86
|
+
await harness.writeCost(item, cost);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
received: await harness.reader.readCostToLand(item),
|
|
90
|
+
expected: { kind: 'value', value: cost },
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
'a pepys that is down gives unavailable with a reason for both reads',
|
|
96
|
+
async (harness) => {
|
|
97
|
+
const item = buildItemId();
|
|
98
|
+
|
|
99
|
+
await harness.takeDown();
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
received: [
|
|
103
|
+
await harness.reader.readCostToLand(item),
|
|
104
|
+
await harness.reader.readStageTime(item),
|
|
105
|
+
].map((answer) => ({ kind: answer.kind, hasReason: 'reason' in answer })),
|
|
106
|
+
expected: [
|
|
107
|
+
{ kind: 'unavailable', hasReason: true },
|
|
108
|
+
{ kind: 'unavailable', hasReason: true },
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
function buildItemId(): ItemId {
|
|
116
|
+
return brandSchemas.itemId.parse(faker.string.uuid({ version: 7 }));
|
|
117
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ContentHash, JsonValue } from '@boxxy/content-hash';
|
|
2
2
|
import type { z } from 'zod';
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
3
|
+
import type { CloseOut } from './closeouts/types.ts';
|
|
4
|
+
import type { HoldReason, Position } from './pipeline/types.ts';
|
|
5
|
+
import type { brandSchemas } from './rows/brand-schemas.ts';
|
|
5
6
|
|
|
6
7
|
type BrandOf<K extends keyof typeof brandSchemas> = z.output<(typeof brandSchemas)[K]>;
|
|
7
8
|
|
|
@@ -15,6 +16,8 @@ export type AttemptId = BrandOf<'attemptId'>;
|
|
|
15
16
|
|
|
16
17
|
export type DecisionId = BrandOf<'decisionId'>;
|
|
17
18
|
|
|
19
|
+
// A bare UUID version 7. The outbox row's fact ID, an evidence ID that a close-out cites, and
|
|
20
|
+
// each ID in `settled.criteria[].evidence` all have this one brand.
|
|
18
21
|
export type FactId = BrandOf<'factId'>;
|
|
19
22
|
|
|
20
23
|
export type RunnerId = BrandOf<'runnerId'>;
|
|
@@ -36,7 +39,7 @@ export interface PullRequestRef {
|
|
|
36
39
|
|
|
37
40
|
export type ItemKindName = BrandOf<'itemKindName'>;
|
|
38
41
|
|
|
39
|
-
export type
|
|
42
|
+
export type RelationTypeName = BrandOf<'relationTypeName'>;
|
|
40
43
|
|
|
41
44
|
export type PhaseName = BrandOf<'phaseName'>;
|
|
42
45
|
|
|
@@ -44,6 +47,8 @@ export type PipelineName = BrandOf<'pipelineName'>;
|
|
|
44
47
|
|
|
45
48
|
export type PoolName = BrandOf<'poolName'>;
|
|
46
49
|
|
|
50
|
+
export type LimitName = BrandOf<'limitName'>;
|
|
51
|
+
|
|
47
52
|
export type RecipeName = BrandOf<'recipeName'>;
|
|
48
53
|
|
|
49
54
|
export type WorkerKindName = BrandOf<'workerKindName'>;
|
|
@@ -60,6 +65,9 @@ export type SnapshotHash = BrandOf<'snapshotHash'>;
|
|
|
60
65
|
|
|
61
66
|
export type RulesVersion = BrandOf<'rulesVersion'>;
|
|
62
67
|
|
|
68
|
+
// A name from a phase kind's `outcomes` list.
|
|
69
|
+
export type OutcomeName = BrandOf<'outcomeName'>;
|
|
70
|
+
|
|
63
71
|
export interface ConceptPin {
|
|
64
72
|
readonly ref: `${string}@${number}`;
|
|
65
73
|
readonly sourceHash: ContentHash;
|
|
@@ -77,7 +85,14 @@ export type ActorRef =
|
|
|
77
85
|
| `boxxy:attempt:${string}`
|
|
78
86
|
| `boxxy:hook:${string}`;
|
|
79
87
|
|
|
80
|
-
|
|
88
|
+
// A proven caller: who runs an attempt's session, or who sent a request. The core compares two
|
|
89
|
+
// of these for equality, on both fields, and reads nothing else from them.
|
|
90
|
+
export interface CallerIdentity {
|
|
91
|
+
readonly issuer: string;
|
|
92
|
+
readonly subject: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type EvidenceKind = 'pull_request' | 'commit' | 'test' | 'ci-run' | 'live-check';
|
|
81
96
|
|
|
82
97
|
export interface Criterion {
|
|
83
98
|
readonly id: CriterionId;
|
|
@@ -85,6 +100,20 @@ export interface Criterion {
|
|
|
85
100
|
readonly evidence: readonly FactId[];
|
|
86
101
|
}
|
|
87
102
|
|
|
103
|
+
export type DesignMode = 'agent' | 'attended';
|
|
104
|
+
|
|
105
|
+
// How dispatch reads an attempt's session. Only design gives 'attended' in v1.
|
|
106
|
+
export type SessionMode = 'agent' | 'attended';
|
|
107
|
+
|
|
108
|
+
// v1 writes `claude_code` only. The second value keeps the telemetry tables' other product.
|
|
109
|
+
export type Product = 'claude_code' | 'codex';
|
|
110
|
+
|
|
111
|
+
// The fields of an item that a filter reads. The one v1 field is the design mode, and an item
|
|
112
|
+
// that sets none runs in agent mode.
|
|
113
|
+
export interface ItemFields {
|
|
114
|
+
readonly designMode?: DesignMode;
|
|
115
|
+
}
|
|
116
|
+
|
|
88
117
|
export interface ItemRow {
|
|
89
118
|
readonly id: ItemId;
|
|
90
119
|
readonly kind: ItemKindName;
|
|
@@ -92,16 +121,48 @@ export interface ItemRow {
|
|
|
92
121
|
readonly title: string;
|
|
93
122
|
readonly outcome: string;
|
|
94
123
|
readonly acceptance: readonly Criterion[];
|
|
124
|
+
readonly fields: ItemFields;
|
|
125
|
+
|
|
126
|
+
// free short strings: `item.file` sets them, and `item.edit` replaces the whole list
|
|
127
|
+
readonly labels: readonly string[];
|
|
95
128
|
readonly foundInAttempt: AttemptId | null;
|
|
96
129
|
readonly filedBy: ActorRef;
|
|
97
130
|
readonly filedAt: Instant;
|
|
98
131
|
readonly closedAt: Instant | null;
|
|
99
132
|
readonly closeReason: string | null;
|
|
133
|
+
|
|
134
|
+
// state of the item between two attempts, which no fact carries
|
|
135
|
+
readonly keptSession: KeptSession | null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface KeptSession {
|
|
139
|
+
readonly atcSession: AtcSessionId;
|
|
140
|
+
|
|
141
|
+
// the worker target the session runs on, as `SpawnRequest.target` has it
|
|
142
|
+
readonly target: string;
|
|
143
|
+
readonly attempt: AttemptId;
|
|
144
|
+
readonly keptAt: Instant;
|
|
145
|
+
readonly caller: CallerIdentity | null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Append-only: no procedure edits or removes a note.
|
|
149
|
+
export interface NoteRow {
|
|
150
|
+
readonly item: ItemId;
|
|
151
|
+
|
|
152
|
+
// 1, 2, 3… per item
|
|
153
|
+
readonly seq: number;
|
|
154
|
+
readonly text: string;
|
|
155
|
+
readonly by: ActorRef;
|
|
156
|
+
readonly at: Instant;
|
|
100
157
|
}
|
|
101
158
|
|
|
102
159
|
export type ItemState = 'idea' | 'ready' | 'running' | 'blocked' | 'waiting' | 'done' | 'archived';
|
|
103
160
|
|
|
104
|
-
export type WaitingReason =
|
|
161
|
+
export type WaitingReason =
|
|
162
|
+
| 'pull request open'
|
|
163
|
+
| 'no pull request linked'
|
|
164
|
+
| 'pull request closed unmerged'
|
|
165
|
+
| HoldReason;
|
|
105
166
|
|
|
106
167
|
export type DerivedState =
|
|
107
168
|
| { readonly state: Exclude<ItemState, 'waiting'> }
|
|
@@ -109,31 +170,13 @@ export type DerivedState =
|
|
|
109
170
|
|
|
110
171
|
export interface ItemView extends ItemRow {
|
|
111
172
|
readonly derived: DerivedState;
|
|
173
|
+
readonly position: Position;
|
|
112
174
|
}
|
|
113
175
|
|
|
114
|
-
export interface
|
|
115
|
-
// live links whose target is the item
|
|
116
|
-
readonly links: readonly LinkRow[];
|
|
117
|
-
readonly blockerStates: ReadonlyMap<ItemId, ItemState>;
|
|
118
|
-
readonly levers: readonly LeverRow[];
|
|
119
|
-
readonly attempts: readonly AttemptRow[];
|
|
120
|
-
readonly liveLease: LeaseRow | null;
|
|
121
|
-
readonly pullRequests: readonly PullRequestRow[];
|
|
122
|
-
|
|
123
|
-
// the kind's `maxAttempts` for the item's current phase
|
|
124
|
-
readonly maxAttempts: number;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export type MaxAttemptsByKind = ReadonlyMap<ItemKindName, ReadonlyMap<PhaseName, number>>;
|
|
128
|
-
|
|
129
|
-
export interface ItemWithLevers extends ItemView {
|
|
130
|
-
readonly levers: readonly LeverRow[];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export interface LinkRow {
|
|
176
|
+
export interface RelationRow {
|
|
134
177
|
readonly source: ItemId;
|
|
135
178
|
readonly target: ItemId;
|
|
136
|
-
readonly type:
|
|
179
|
+
readonly type: RelationTypeName;
|
|
137
180
|
readonly addedBy: ActorRef;
|
|
138
181
|
readonly addedAt: Instant;
|
|
139
182
|
readonly removedAt: Instant | null;
|
|
@@ -146,17 +189,53 @@ export interface LeverRow {
|
|
|
146
189
|
readonly until: Instant | null;
|
|
147
190
|
readonly expiresAt: Instant | null;
|
|
148
191
|
readonly reason: string | null;
|
|
192
|
+
|
|
193
|
+
// only `item.retry --phase` sets it
|
|
194
|
+
readonly phase: PhaseName | null;
|
|
149
195
|
readonly actor: ActorRef;
|
|
150
196
|
readonly at: Instant;
|
|
151
197
|
}
|
|
152
198
|
|
|
153
|
-
export type AttemptState =
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
199
|
+
export type AttemptState =
|
|
200
|
+
| 'starting'
|
|
201
|
+
| 'running'
|
|
202
|
+
| 'reconciling'
|
|
203
|
+
| 'sleeping'
|
|
204
|
+
| 'waking'
|
|
205
|
+
| 'closing'
|
|
206
|
+
| 'succeeded'
|
|
207
|
+
| 'failed';
|
|
208
|
+
|
|
209
|
+
export type LiveAttemptState = Exclude<AttemptState, 'succeeded' | 'failed'>;
|
|
210
|
+
|
|
211
|
+
export type ClosingCause =
|
|
212
|
+
| 'closed-out'
|
|
213
|
+
| 'timed-out'
|
|
214
|
+
| 'item-closed'
|
|
215
|
+
| 'stalled'
|
|
216
|
+
| 'skill-missing'
|
|
217
|
+
| 'turn-ended'
|
|
218
|
+
| 'usage-limit';
|
|
219
|
+
|
|
220
|
+
export type ForcedFailure =
|
|
221
|
+
| 'spawn-refused'
|
|
222
|
+
| 'no-capacity'
|
|
223
|
+
| 'spawn-lost'
|
|
224
|
+
| 'session-lost'
|
|
225
|
+
| 'resume-lost'
|
|
226
|
+
| 'timed-out'
|
|
227
|
+
| 'item-closed'
|
|
228
|
+
| 'skill-missing'
|
|
229
|
+
| 'stalled'
|
|
230
|
+
| 'usage-limit';
|
|
231
|
+
|
|
232
|
+
export type KnownFailure = ForcedFailure | 'closeout-missing' | 'closeout-unsettleable';
|
|
233
|
+
|
|
234
|
+
// The engine's own outcomes. No phase kind may list either name.
|
|
235
|
+
export type EngineOutcome = 'lost' | 'unsettleable';
|
|
236
|
+
|
|
237
|
+
// each field holds a list of values, never one string
|
|
238
|
+
export type FieldValues = Readonly<Record<string, readonly string[]>>;
|
|
160
239
|
|
|
161
240
|
export interface AttemptRow {
|
|
162
241
|
readonly id: AttemptId;
|
|
@@ -167,24 +246,53 @@ export interface AttemptRow {
|
|
|
167
246
|
readonly recipeHash: RecipeHash;
|
|
168
247
|
readonly rulesVersion: RulesVersion;
|
|
169
248
|
readonly pool: PoolName;
|
|
170
|
-
readonly slot: number;
|
|
171
|
-
readonly slotGen: number;
|
|
172
249
|
readonly leaseGen: number;
|
|
173
250
|
readonly spawnRequest: JsonValue;
|
|
174
251
|
readonly spawnRequestHash: ContentHash;
|
|
175
252
|
readonly state: AttemptState;
|
|
176
253
|
readonly failure: string | null;
|
|
177
254
|
readonly atcSession: AtcSessionId | null;
|
|
178
|
-
|
|
255
|
+
|
|
256
|
+
// null: the worker gave no caller, and every session and hook call for the attempt is refused
|
|
257
|
+
readonly caller: CallerIdentity | null;
|
|
179
258
|
readonly claimedAt: Instant;
|
|
180
259
|
readonly startedAt: Instant | null;
|
|
181
260
|
readonly endedAt: Instant | null;
|
|
261
|
+
|
|
262
|
+
// what `decide` saw; a wake and a rules reload match limits against these
|
|
263
|
+
readonly fields: FieldValues;
|
|
264
|
+
readonly mode: SessionMode;
|
|
265
|
+
readonly round: number;
|
|
266
|
+
readonly input: JsonValue;
|
|
267
|
+
|
|
268
|
+
// 1 at the claim, one higher on each wake; every held seat carries it
|
|
269
|
+
readonly holdGen: number;
|
|
270
|
+
|
|
271
|
+
// the first valid close-out, never replaced
|
|
272
|
+
readonly closeOut: CloseOut | null;
|
|
273
|
+
readonly closeOutHash: ContentHash | null;
|
|
274
|
+
readonly closeOutFact: FactId | null;
|
|
275
|
+
readonly closing: ClosingCause | null;
|
|
276
|
+
readonly closingAt: Instant | null;
|
|
277
|
+
readonly suspendRequestedAt: Instant | null;
|
|
278
|
+
readonly sleptAt: Instant | null;
|
|
279
|
+
readonly wakeRequestedAt: Instant | null;
|
|
280
|
+
readonly wokenAt: Instant | null;
|
|
281
|
+
readonly outcome: OutcomeName | EngineOutcome | null;
|
|
282
|
+
|
|
283
|
+
// what the phase code returned, for an attempt settled as `closeout-unsettleable`
|
|
284
|
+
readonly returnedOutcome: string | null;
|
|
285
|
+
readonly strayedAt: Instant | null;
|
|
286
|
+
readonly sessionStoppedAt: Instant | null;
|
|
287
|
+
readonly sessionForgottenAt: Instant | null;
|
|
182
288
|
}
|
|
183
289
|
|
|
184
290
|
export interface AttemptSessionRow {
|
|
185
291
|
readonly attempt: AttemptId;
|
|
186
292
|
readonly agentSession: AgentSessionId;
|
|
187
293
|
readonly atcSession: AtcSessionId;
|
|
294
|
+
readonly product: Product;
|
|
295
|
+
readonly source: 'worker' | 'hook';
|
|
188
296
|
readonly seenAt: Instant;
|
|
189
297
|
}
|
|
190
298
|
|
|
@@ -197,12 +305,16 @@ export interface LeaseRow {
|
|
|
197
305
|
readonly releasedAt: Instant | null;
|
|
198
306
|
}
|
|
199
307
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
readonly
|
|
203
|
-
readonly
|
|
204
|
-
readonly
|
|
205
|
-
|
|
308
|
+
// A limit's count is its rows with no `releasedAt`.
|
|
309
|
+
export interface LimitSeatRow {
|
|
310
|
+
readonly limit: LimitName;
|
|
311
|
+
readonly attempt: AttemptId;
|
|
312
|
+
readonly holdGen: number;
|
|
313
|
+
|
|
314
|
+
// taken past the limit's `max`, by a wake over the cap
|
|
315
|
+
readonly overCap: boolean;
|
|
316
|
+
readonly takenAt: Instant;
|
|
317
|
+
readonly releasedAt: Instant | null;
|
|
206
318
|
}
|
|
207
319
|
|
|
208
320
|
export interface ObservationBoundary {
|
|
@@ -210,23 +322,34 @@ export interface ObservationBoundary {
|
|
|
210
322
|
readonly pepysOffsets: Readonly<Record<StreamName, number>>;
|
|
211
323
|
}
|
|
212
324
|
|
|
325
|
+
// A limit's count and `max` at the moment of a pick.
|
|
326
|
+
export interface LimitCount {
|
|
327
|
+
readonly limit: LimitName;
|
|
328
|
+
readonly held: number;
|
|
329
|
+
readonly max: number;
|
|
330
|
+
}
|
|
331
|
+
|
|
213
332
|
export interface DecisionRow {
|
|
214
333
|
readonly id: DecisionId;
|
|
215
334
|
readonly attempt: AttemptId;
|
|
216
335
|
readonly item: ItemId;
|
|
336
|
+
readonly kind: 'start' | 'wake';
|
|
217
337
|
readonly pool: PoolName;
|
|
218
338
|
readonly rulesVersion: RulesVersion;
|
|
219
339
|
readonly snapshotHash: SnapshotHash;
|
|
220
340
|
readonly storeVersion: number;
|
|
221
341
|
readonly pepysOffsets: ObservationBoundary['pepysOffsets'];
|
|
222
342
|
readonly faults: readonly FaultRecord[];
|
|
343
|
+
|
|
344
|
+
// each limit that counts the pick, with its count before the seat was taken
|
|
345
|
+
readonly limits: readonly LimitCount[];
|
|
223
346
|
readonly at: Instant;
|
|
224
347
|
}
|
|
225
348
|
|
|
226
349
|
export interface FaultRecord {
|
|
227
|
-
// the signal, gate or
|
|
350
|
+
// the signal, gate, waiver or filter field name, or `propagation:<signal>:<relation>`
|
|
228
351
|
readonly plugin: string;
|
|
229
|
-
readonly kind: 'signal' | 'gate' | 'waiver' | 'admission' | 'propagation';
|
|
352
|
+
readonly kind: 'signal' | 'gate' | 'waiver' | 'admission' | 'propagation' | 'filter-field';
|
|
230
353
|
readonly item: ItemId | null;
|
|
231
354
|
readonly error: string;
|
|
232
355
|
}
|
|
@@ -247,6 +370,10 @@ export interface PullRequestRow {
|
|
|
247
370
|
readonly repo: RepoName;
|
|
248
371
|
readonly number: number;
|
|
249
372
|
readonly attempt: AttemptId;
|
|
373
|
+
|
|
374
|
+
// GitHub's own `id` of the pull request, which pepys builds its entity ID from
|
|
375
|
+
readonly githubId: number;
|
|
376
|
+
readonly url: string;
|
|
250
377
|
readonly state: 'open' | 'closed' | 'merged';
|
|
251
378
|
readonly mergedAt: Instant | null;
|
|
252
379
|
readonly mergeCommit: CommitSha | null;
|
|
@@ -259,6 +386,16 @@ export interface RunnerRow {
|
|
|
259
386
|
readonly paused: boolean;
|
|
260
387
|
}
|
|
261
388
|
|
|
389
|
+
// A hold notice for the `Notifier` port. The tick tries it again until `tries` reaches 5.
|
|
390
|
+
export interface NoticeRow {
|
|
391
|
+
readonly id: number;
|
|
392
|
+
readonly item: ItemId;
|
|
393
|
+
readonly reason: HoldReason;
|
|
394
|
+
readonly at: Instant;
|
|
395
|
+
readonly tries: number;
|
|
396
|
+
readonly sentAt: Instant | null;
|
|
397
|
+
}
|
|
398
|
+
|
|
262
399
|
export type Effort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
|
|
263
400
|
|
|
264
401
|
export interface Recipe {
|
|
@@ -269,19 +406,18 @@ export interface Recipe {
|
|
|
269
406
|
// the prompt template's path in the rules checkout
|
|
270
407
|
readonly prompt: string;
|
|
271
408
|
readonly source: SourceName;
|
|
409
|
+
|
|
410
|
+
// the skills the phase needs; the recipe hash covers the list and the rules' pin
|
|
411
|
+
readonly skills: readonly string[];
|
|
272
412
|
}
|
|
273
413
|
|
|
274
414
|
export interface RecipeMaterial {
|
|
275
415
|
readonly name: RecipeName;
|
|
276
416
|
readonly recipe: Recipe;
|
|
277
417
|
readonly promptHash: ContentHash;
|
|
278
|
-
readonly pluginFolderHash: ContentHash;
|
|
279
418
|
readonly rulesVersion: RulesVersion;
|
|
280
419
|
}
|
|
281
420
|
|
|
282
|
-
// the loader can't see the plugin folder, so claimNext adds its hash
|
|
283
|
-
export type LoadedRecipe = Omit<RecipeMaterial, 'pluginFolderHash'>;
|
|
284
|
-
|
|
285
421
|
export interface ResolvedRecipe extends RecipeMaterial {
|
|
286
422
|
readonly hash: RecipeHash;
|
|
287
423
|
}
|