@boxxy/model 0.1.1 → 0.1.2
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/actor-ref-schema.d.ts +2 -0
- package/dist/build-applied-change.d.ts +13 -0
- package/dist/build-item-views.d.ts +2 -0
- package/dist/find-slot-take.d.ts +5 -0
- package/dist/format-key-path.d.ts +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/outbox-fact-schema.d.ts +3 -0
- package/dist/row-schemas.d.ts +5 -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-item-view.d.ts +2 -0
- package/dist/test-utils/build-mock-snapshot.d.ts +2 -0
- package/dist/to-json-value.d.ts +2 -0
- package/dist/types/core.d.ts +9 -1
- package/package.json +2 -1
- package/src/actor-ref-schema.ts +9 -0
- package/src/build-applied-change.ts +71 -0
- package/src/build-item-views.ts +128 -0
- package/src/fact-schemas.ts +2 -8
- package/src/find-slot-take.ts +12 -0
- package/src/format-key-path.ts +19 -0
- package/src/index.ts +14 -0
- package/src/outbox-fact-schema.ts +25 -0
- package/src/row-schemas.ts +89 -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 +9 -0
- package/src/test-utils/build-mock-item-view.ts +8 -0
- package/src/test-utils/build-mock-snapshot.ts +5 -0
- package/src/to-json-value.ts +25 -0
- package/src/types/core.ts +14 -2
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const actorRefSchema: z.ZodUnion<readonly [z.ZodLiteral<"boxxy:owner:local">, z.ZodTemplateLiteral<`boxxy:runner:${string & z.$brand<"RunnerId">}`>, z.ZodTemplateLiteral<`boxxy:attempt:${string & z.$brand<"AttemptId">}`>, z.ZodTemplateLiteral<`boxxy:hook:${string & z.$brand<"AttemptId">}`>]>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AttemptId, PoolName } from './types/core.ts';
|
|
2
|
+
import type { StoreChange } from './types/ports.ts';
|
|
3
|
+
export interface TakenSlot {
|
|
4
|
+
readonly holder: AttemptId;
|
|
5
|
+
readonly pool: PoolName;
|
|
6
|
+
readonly slot: number;
|
|
7
|
+
readonly gen: number;
|
|
8
|
+
}
|
|
9
|
+
export interface ChangeAssignments {
|
|
10
|
+
readonly taken: TakenSlot | null;
|
|
11
|
+
readonly lastLeverSeq: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function buildAppliedChange(change: StoreChange, assignments: ChangeAssignments): Pick<StoreChange, 'writes' | 'facts'>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatKeyPath(segments: readonly PropertyKey[]): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
export type { ContentHash, JsonValue } from '@boxxy/content-hash';
|
|
2
2
|
export { brandSchemas } from './brand-schemas.ts';
|
|
3
|
+
export { buildAppliedChange, type ChangeAssignments, type TakenSlot, } from './build-applied-change.ts';
|
|
3
4
|
export { buildCloseOutSchema } from './build-close-out-schema.ts';
|
|
5
|
+
export { buildItemViews } from './build-item-views.ts';
|
|
4
6
|
export { defineAdmission } from './define-admission.ts';
|
|
5
7
|
export { defineGate } from './define-gate.ts';
|
|
6
8
|
export { defineSignal } from './define-signal.ts';
|
|
7
9
|
export { deriveItemState } from './derive-item-state.ts';
|
|
8
10
|
export { factSchemas } from './fact-schemas.ts';
|
|
11
|
+
export { findSlotTake, type SlotTake } from './find-slot-take.ts';
|
|
12
|
+
export { formatKeyPath } from './format-key-path.ts';
|
|
9
13
|
export { frictionSchema } from './friction-schema.ts';
|
|
10
14
|
export { liveLevers } from './live-levers.ts';
|
|
15
|
+
export { outboxFactSchema } from './outbox-fact-schema.ts';
|
|
16
|
+
export { rowSchemas } from './row-schemas.ts';
|
|
11
17
|
export { signalKey } from './signal-key.ts';
|
|
12
|
-
export
|
|
18
|
+
export { toJSONValue } from './to-json-value.ts';
|
|
19
|
+
export type { Actor, ActorRef, AgentSessionId, AtcSessionId, AttemptId, AttemptRow, AttemptSessionRow, AttemptState, CommitSha, ConceptPin, Criterion, CriterionId, DecisionId, DecisionRow, DerivedState, Effort, EvidenceKind, EvidenceRow, EvidenceSource, FactId, FaultRecord, ForcedFailure, Instant, ItemId, ItemKindName, ItemRow, ItemRows, ItemSetRows, ItemState, ItemView, ItemWithLevers, KnownFailure, LeaseRow, LeverKind, LeverRow, LinkRow, LinkTypeName, LiveAttemptState, LoadedRecipe, MaxAttemptsByKind, Minutes, ObservationBoundary, PhaseName, PipelineName, PoolName, PoolSlotRow, PullRequestRef, PullRequestRow, Recipe, RecipeHash, RecipeMaterial, RecipeName, RepoName, ResolvedRecipe, RulesVersion, RunnerId, RunnerRow, SnapshotHash, SourceName, StreamName, WaitingReason, WorkerKindName, } from './types/core.ts';
|
|
13
20
|
export type { Claim, CloseOutContext, CloseOutParse, CloseOutParser, CloseOutProblem, ReconcileAction, ReconcileTable, SettleInput, Settlement, SettleRule, SpawnContext, } from './types/dispatch.ts';
|
|
14
21
|
export type { BuildCloseOut, EntityRef, FactEnvelope, FactType, Friction, FrictionTargetKind, LinkPayload, NewFact, OpenItem, OutboxFact, SliceOneFacts, } from './types/facts.ts';
|
|
15
22
|
export type { Admission, AdmissionGrant, AdmissionName, Decision, Fault, Frame, FrameItem, FrameValues, Gate, GateName, Hold, Lift, Pick, PlanContext, Pool, PropagationLine, Signal, SignalKey, SignalName, Snapshot, View, ViewName, Waiver, WaiverName, } from './types/planning.ts';
|
package/dist/types/core.d.ts
CHANGED
|
@@ -88,6 +88,14 @@ export interface ItemRows {
|
|
|
88
88
|
readonly pullRequests: readonly PullRequestRow[];
|
|
89
89
|
readonly maxAttempts: number;
|
|
90
90
|
}
|
|
91
|
+
export interface ItemSetRows {
|
|
92
|
+
readonly items: readonly ItemRow[];
|
|
93
|
+
readonly links: readonly LinkRow[];
|
|
94
|
+
readonly levers: readonly LeverRow[];
|
|
95
|
+
readonly attempts: readonly AttemptRow[];
|
|
96
|
+
readonly liveLeases: readonly LeaseRow[];
|
|
97
|
+
readonly pullRequests: readonly PullRequestRow[];
|
|
98
|
+
}
|
|
91
99
|
export type MaxAttemptsByKind = ReadonlyMap<ItemKindName, ReadonlyMap<PhaseName, number>>;
|
|
92
100
|
export interface ItemWithLevers extends ItemView {
|
|
93
101
|
readonly levers: readonly LeverRow[];
|
|
@@ -175,7 +183,7 @@ export interface DecisionRow {
|
|
|
175
183
|
}
|
|
176
184
|
export interface FaultRecord {
|
|
177
185
|
readonly plugin: string;
|
|
178
|
-
readonly kind: 'signal' | 'gate' | 'waiver' | 'admission' | 'propagation';
|
|
186
|
+
readonly kind: 'signal' | 'gate' | 'waiver' | 'admission' | 'propagation' | 'filter-field';
|
|
179
187
|
readonly item: ItemId | null;
|
|
180
188
|
readonly error: string;
|
|
181
189
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boxxy/model",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "The types that boxxy's packages share",
|
|
5
5
|
"homepage": "https://github.com/zgeoff/boxxy/tree/main/libs/model#readme",
|
|
6
6
|
"bugs": "https://github.com/zgeoff/boxxy/issues",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@boxxy/content-hash": "0.1.0",
|
|
35
35
|
"@faker-js/faker": "10.6.0",
|
|
36
|
+
"tiny-invariant": "1.3.3",
|
|
36
37
|
"zod": "4.6.7"
|
|
37
38
|
},
|
|
38
39
|
"engines": {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { brandSchemas } from './brand-schemas.ts';
|
|
3
|
+
|
|
4
|
+
export const actorRefSchema = z.union([
|
|
5
|
+
z.literal('boxxy:owner:local'),
|
|
6
|
+
z.templateLiteral(['boxxy:runner:', brandSchemas.runnerId]),
|
|
7
|
+
z.templateLiteral(['boxxy:attempt:', brandSchemas.attemptId]),
|
|
8
|
+
z.templateLiteral(['boxxy:hook:', brandSchemas.attemptId]),
|
|
9
|
+
]);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { AttemptId, PoolName } from './types/core.ts';
|
|
2
|
+
import type { AnyNewFact, RowWrite, StoreChange } from './types/ports.ts';
|
|
3
|
+
|
|
4
|
+
export interface TakenSlot {
|
|
5
|
+
readonly holder: AttemptId;
|
|
6
|
+
readonly pool: PoolName;
|
|
7
|
+
readonly slot: number;
|
|
8
|
+
readonly gen: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ChangeAssignments {
|
|
12
|
+
readonly taken: TakenSlot | null;
|
|
13
|
+
|
|
14
|
+
// 0 when the store holds no lever
|
|
15
|
+
readonly lastLeverSeq: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildAppliedChange(
|
|
19
|
+
change: StoreChange,
|
|
20
|
+
assignments: ChangeAssignments,
|
|
21
|
+
): Pick<StoreChange, 'writes' | 'facts'> {
|
|
22
|
+
const leverSeqs: number[] = [];
|
|
23
|
+
|
|
24
|
+
const writes = change.writes.map((write): RowWrite => {
|
|
25
|
+
if (write.op !== 'insert') {
|
|
26
|
+
return write;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (write.table === 'levers') {
|
|
30
|
+
const seq = assignments.lastLeverSeq + leverSeqs.length + 1;
|
|
31
|
+
|
|
32
|
+
leverSeqs.push(seq);
|
|
33
|
+
|
|
34
|
+
return { ...write, row: { ...write.row, seq } };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (write.table === 'attempts' && write.row.id === assignments.taken?.holder) {
|
|
38
|
+
const taken = assignments.taken;
|
|
39
|
+
|
|
40
|
+
return { ...write, row: { ...write.row, slot: taken.slot, slotGen: taken.gen } };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return write;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
let leverFacts = 0;
|
|
47
|
+
|
|
48
|
+
const facts = change.facts.map((fact): AnyNewFact => {
|
|
49
|
+
if (fact.type === 'boxxy.lever.recorded') {
|
|
50
|
+
const seq = leverSeqs[leverFacts];
|
|
51
|
+
|
|
52
|
+
leverFacts += 1;
|
|
53
|
+
|
|
54
|
+
return seq === undefined ? fact : { ...fact, payload: { ...fact.payload, seq } };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
fact.type === 'boxxy.attempt.claimed' &&
|
|
59
|
+
assignments.taken !== null &&
|
|
60
|
+
fact.entity === `boxxy:attempt:${assignments.taken.holder}`
|
|
61
|
+
) {
|
|
62
|
+
const taken = assignments.taken;
|
|
63
|
+
|
|
64
|
+
return { ...fact, payload: { ...fact.payload, slot: taken.slot, gen: taken.gen } };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return fact;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return { writes, facts };
|
|
71
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { deriveItemState } from './derive-item-state.ts';
|
|
2
|
+
import { liveLevers } from './live-levers.ts';
|
|
3
|
+
import type {
|
|
4
|
+
AttemptId,
|
|
5
|
+
AttemptRow,
|
|
6
|
+
Instant,
|
|
7
|
+
ItemId,
|
|
8
|
+
ItemKindName,
|
|
9
|
+
ItemRow,
|
|
10
|
+
ItemRows,
|
|
11
|
+
ItemSetRows,
|
|
12
|
+
ItemState,
|
|
13
|
+
ItemWithLevers,
|
|
14
|
+
LeaseRow,
|
|
15
|
+
LeverRow,
|
|
16
|
+
LinkRow,
|
|
17
|
+
MaxAttemptsByKind,
|
|
18
|
+
PullRequestRow,
|
|
19
|
+
} from './types/core.ts';
|
|
20
|
+
|
|
21
|
+
export function buildItemViews(
|
|
22
|
+
rows: ItemSetRows,
|
|
23
|
+
now: Instant,
|
|
24
|
+
maxAttemptsByKind: MaxAttemptsByKind,
|
|
25
|
+
): ItemWithLevers[] {
|
|
26
|
+
const groups = collectItemGroups(rows, now);
|
|
27
|
+
|
|
28
|
+
// A blocking source counts only when it is done or archived, and no link makes an item either,
|
|
29
|
+
// so each source's state derived without its links settles its targets.
|
|
30
|
+
const ownStates = new Map<ItemId, ItemState>(
|
|
31
|
+
rows.items.map((item) => [
|
|
32
|
+
item.id,
|
|
33
|
+
deriveItemState(
|
|
34
|
+
item,
|
|
35
|
+
buildItemRows(item, groups.get(item.id), maxAttemptsByKind, {
|
|
36
|
+
links: [],
|
|
37
|
+
blockerStates: new Map(),
|
|
38
|
+
}),
|
|
39
|
+
).state,
|
|
40
|
+
]),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return rows.items.map((item) => {
|
|
44
|
+
const group = groups.get(item.id);
|
|
45
|
+
|
|
46
|
+
const itemRows = buildItemRows(item, group, maxAttemptsByKind, {
|
|
47
|
+
links: group?.links ?? [],
|
|
48
|
+
blockerStates: ownStates,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return { ...item, derived: deriveItemState(item, itemRows), levers: group?.levers ?? [] };
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface ItemGroup {
|
|
56
|
+
readonly links: LinkRow[];
|
|
57
|
+
readonly attempts: AttemptRow[];
|
|
58
|
+
readonly pullRequests: PullRequestRow[];
|
|
59
|
+
levers: LeverRow[];
|
|
60
|
+
liveLease: LeaseRow | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function collectItemGroups(rows: ItemSetRows, now: Instant): Map<ItemId, ItemGroup> {
|
|
64
|
+
const groups = new Map<ItemId, ItemGroup>(
|
|
65
|
+
rows.items.map((item) => [
|
|
66
|
+
item.id,
|
|
67
|
+
{ links: [], attempts: [], pullRequests: [], levers: [], liveLease: null },
|
|
68
|
+
]),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const attemptItems = new Map<AttemptId, ItemId>();
|
|
72
|
+
|
|
73
|
+
for (const link of rows.links) {
|
|
74
|
+
groups.get(link.target)?.links.push(link);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const attempt of rows.attempts) {
|
|
78
|
+
attemptItems.set(attempt.id, attempt.item);
|
|
79
|
+
groups.get(attempt.item)?.attempts.push(attempt);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const pullRequest of rows.pullRequests) {
|
|
83
|
+
const item = attemptItems.get(pullRequest.attempt);
|
|
84
|
+
|
|
85
|
+
if (item !== undefined) {
|
|
86
|
+
groups.get(item)?.pullRequests.push(pullRequest);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const lease of rows.liveLeases) {
|
|
91
|
+
const group = groups.get(lease.item);
|
|
92
|
+
|
|
93
|
+
if (group !== undefined) {
|
|
94
|
+
group.liveLease = lease;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const [item, group] of groups) {
|
|
99
|
+
group.levers = liveLevers(
|
|
100
|
+
rows.levers.filter((lever) => lever.item === item),
|
|
101
|
+
now,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return groups;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildItemRows(
|
|
109
|
+
item: ItemRow,
|
|
110
|
+
group: ItemGroup | undefined,
|
|
111
|
+
maxAttemptsByKind: MaxAttemptsByKind,
|
|
112
|
+
blocking: Pick<ItemRows, 'links' | 'blockerStates'>,
|
|
113
|
+
): ItemRows {
|
|
114
|
+
return {
|
|
115
|
+
...blocking,
|
|
116
|
+
levers: group?.levers ?? [],
|
|
117
|
+
attempts: group?.attempts ?? [],
|
|
118
|
+
liveLease: group?.liveLease ?? null,
|
|
119
|
+
pullRequests: group?.pullRequests ?? [],
|
|
120
|
+
maxAttempts: pickMaxAttempts(item.kind, maxAttemptsByKind),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function pickMaxAttempts(kind: ItemKindName, maxAttemptsByKind: MaxAttemptsByKind): number {
|
|
125
|
+
const [first] = maxAttemptsByKind.get(kind)?.values() ?? [];
|
|
126
|
+
|
|
127
|
+
return first ?? Number.POSITIVE_INFINITY;
|
|
128
|
+
}
|
package/src/fact-schemas.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { actorRefSchema } from './actor-ref-schema.ts';
|
|
2
3
|
import { brandSchemas } from './brand-schemas.ts';
|
|
3
4
|
import { buildCloseOutSchema } from './build-close-out-schema.ts';
|
|
4
5
|
import { frictionSchema } from './friction-schema.ts';
|
|
5
6
|
import type { FactType, SliceOneFacts } from './types/facts.ts';
|
|
6
7
|
|
|
7
|
-
const actorRefSchema = z.union([
|
|
8
|
-
z.literal('boxxy:owner:local'),
|
|
9
|
-
z.templateLiteral(['boxxy:runner:', brandSchemas.runnerId]),
|
|
10
|
-
z.templateLiteral(['boxxy:attempt:', brandSchemas.attemptId]),
|
|
11
|
-
z.templateLiteral(['boxxy:hook:', brandSchemas.attemptId]),
|
|
12
|
-
]);
|
|
13
|
-
|
|
14
8
|
const criterionSchema = z.object({
|
|
15
9
|
id: brandSchemas.criterionId,
|
|
16
10
|
text: z.string().min(1),
|
|
@@ -25,7 +19,7 @@ const linkPayloadSchema = z.object({
|
|
|
25
19
|
|
|
26
20
|
const faultSchema = z.object({
|
|
27
21
|
plugin: z.string().min(1),
|
|
28
|
-
kind: z.enum(['signal', 'gate', 'waiver', 'admission', 'propagation']),
|
|
22
|
+
kind: z.enum(['signal', 'gate', 'waiver', 'admission', 'propagation', 'filter-field']),
|
|
29
23
|
item: brandSchemas.itemId.nullable(),
|
|
30
24
|
error: z.string(),
|
|
31
25
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import invariant from 'tiny-invariant';
|
|
2
|
+
import type { RowWrite } from './types/ports.ts';
|
|
3
|
+
|
|
4
|
+
export type SlotTake = Extract<RowWrite, { op: 'take-slot' }>;
|
|
5
|
+
|
|
6
|
+
export function findSlotTake(writes: readonly RowWrite[]): SlotTake | null {
|
|
7
|
+
const takes = writes.filter((write): write is SlotTake => write.op === 'take-slot');
|
|
8
|
+
|
|
9
|
+
invariant(takes.length <= 1, 'a change takes one slot at most');
|
|
10
|
+
|
|
11
|
+
return takes.at(0) ?? null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function formatKeyPath(segments: readonly PropertyKey[]): string {
|
|
2
|
+
return segments.map((segment, index) => formatSegment(segment, index === 0)).join('');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const plainKeyPattern = /^[A-Za-z_][\w-]*$/u;
|
|
6
|
+
|
|
7
|
+
function formatSegment(segment: PropertyKey, isFirst: boolean): string {
|
|
8
|
+
if (typeof segment === 'number') {
|
|
9
|
+
return `[${segment}]`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const key = String(segment);
|
|
13
|
+
|
|
14
|
+
if (!plainKeyPattern.test(key)) {
|
|
15
|
+
return `[${JSON.stringify(key)}]`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return isFirst ? key : `.${key}`;
|
|
19
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
export type { ContentHash, JsonValue } from '@boxxy/content-hash';
|
|
2
2
|
export { brandSchemas } from './brand-schemas.ts';
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
buildAppliedChange,
|
|
6
|
+
type ChangeAssignments,
|
|
7
|
+
type TakenSlot,
|
|
8
|
+
} from './build-applied-change.ts';
|
|
9
|
+
|
|
3
10
|
export { buildCloseOutSchema } from './build-close-out-schema.ts';
|
|
11
|
+
export { buildItemViews } from './build-item-views.ts';
|
|
4
12
|
export { defineAdmission } from './define-admission.ts';
|
|
5
13
|
export { defineGate } from './define-gate.ts';
|
|
6
14
|
export { defineSignal } from './define-signal.ts';
|
|
7
15
|
export { deriveItemState } from './derive-item-state.ts';
|
|
8
16
|
export { factSchemas } from './fact-schemas.ts';
|
|
17
|
+
export { findSlotTake, type SlotTake } from './find-slot-take.ts';
|
|
18
|
+
export { formatKeyPath } from './format-key-path.ts';
|
|
9
19
|
export { frictionSchema } from './friction-schema.ts';
|
|
10
20
|
export { liveLevers } from './live-levers.ts';
|
|
21
|
+
export { outboxFactSchema } from './outbox-fact-schema.ts';
|
|
22
|
+
export { rowSchemas } from './row-schemas.ts';
|
|
11
23
|
export { signalKey } from './signal-key.ts';
|
|
24
|
+
export { toJSONValue } from './to-json-value.ts';
|
|
12
25
|
|
|
13
26
|
export type {
|
|
14
27
|
Actor,
|
|
@@ -38,6 +51,7 @@ export type {
|
|
|
38
51
|
ItemKindName,
|
|
39
52
|
ItemRow,
|
|
40
53
|
ItemRows,
|
|
54
|
+
ItemSetRows,
|
|
41
55
|
ItemState,
|
|
42
56
|
ItemView,
|
|
43
57
|
ItemWithLevers,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { actorRefSchema } from './actor-ref-schema.ts';
|
|
3
|
+
import { brandSchemas } from './brand-schemas.ts';
|
|
4
|
+
import type { OutboxFact } from './types/facts.ts';
|
|
5
|
+
|
|
6
|
+
const idSchema = z.string();
|
|
7
|
+
|
|
8
|
+
const entityRefSchema = z.union([
|
|
9
|
+
z.templateLiteral(['boxxy:item:', idSchema]),
|
|
10
|
+
z.templateLiteral(['boxxy:link:', idSchema, ':', idSchema, ':', idSchema]),
|
|
11
|
+
z.templateLiteral(['boxxy:attempt:', idSchema]),
|
|
12
|
+
z.templateLiteral(['boxxy:decision:', idSchema]),
|
|
13
|
+
z.templateLiteral(['boxxy:runner:', idSchema]),
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
export const outboxFactSchema: z.ZodType<OutboxFact> = z.object({
|
|
17
|
+
seq: z.number().int().positive(),
|
|
18
|
+
factId: brandSchemas.factId,
|
|
19
|
+
type: z.string().min(1),
|
|
20
|
+
entity: entityRefSchema,
|
|
21
|
+
actor: actorRefSchema,
|
|
22
|
+
at: brandSchemas.instant,
|
|
23
|
+
payload: z.json(),
|
|
24
|
+
sentAt: brandSchemas.instant.nullable(),
|
|
25
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { contentHashSchema } from '@boxxy/content-hash';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { actorRefSchema } from './actor-ref-schema.ts';
|
|
4
|
+
import { brandSchemas } from './brand-schemas.ts';
|
|
5
|
+
import { buildCloseOutSchema } from './build-close-out-schema.ts';
|
|
6
|
+
import { factSchemas } from './fact-schemas.ts';
|
|
7
|
+
import type { TableName, Tables } from './types/ports.ts';
|
|
8
|
+
|
|
9
|
+
const countSchema = z.number().int().nonnegative();
|
|
10
|
+
|
|
11
|
+
export const rowSchemas: { readonly [T in TableName]: z.ZodType<Tables[T]> } = {
|
|
12
|
+
items: factSchemas['boxxy.item.filed'],
|
|
13
|
+
links: z.object({
|
|
14
|
+
source: brandSchemas.itemId,
|
|
15
|
+
target: brandSchemas.itemId,
|
|
16
|
+
type: brandSchemas.linkTypeName,
|
|
17
|
+
addedBy: actorRefSchema,
|
|
18
|
+
addedAt: brandSchemas.instant,
|
|
19
|
+
removedAt: brandSchemas.instant.nullable(),
|
|
20
|
+
}),
|
|
21
|
+
levers: factSchemas['boxxy.lever.recorded'],
|
|
22
|
+
attempts: z.object({
|
|
23
|
+
id: brandSchemas.attemptId,
|
|
24
|
+
item: brandSchemas.itemId,
|
|
25
|
+
phase: brandSchemas.phaseName,
|
|
26
|
+
number: z.number().int().positive(),
|
|
27
|
+
runner: brandSchemas.runnerId,
|
|
28
|
+
recipeHash: brandSchemas.recipeHash,
|
|
29
|
+
rulesVersion: brandSchemas.rulesVersion,
|
|
30
|
+
pool: brandSchemas.poolName,
|
|
31
|
+
slot: countSchema,
|
|
32
|
+
slotGen: countSchema,
|
|
33
|
+
leaseGen: countSchema,
|
|
34
|
+
spawnRequest: z.json(),
|
|
35
|
+
spawnRequestHash: contentHashSchema,
|
|
36
|
+
state: z.enum(['starting', 'running', 'reconciling', 'succeeded', 'failed']),
|
|
37
|
+
failure: z.string().nullable(),
|
|
38
|
+
atcSession: brandSchemas.atcSessionId.nullable(),
|
|
39
|
+
closeOut: buildCloseOutSchema.nullable(),
|
|
40
|
+
claimedAt: brandSchemas.instant,
|
|
41
|
+
startedAt: brandSchemas.instant.nullable(),
|
|
42
|
+
endedAt: brandSchemas.instant.nullable(),
|
|
43
|
+
}),
|
|
44
|
+
attempt_sessions: z.object({
|
|
45
|
+
attempt: brandSchemas.attemptId,
|
|
46
|
+
agentSession: brandSchemas.agentSessionId,
|
|
47
|
+
atcSession: brandSchemas.atcSessionId,
|
|
48
|
+
seenAt: brandSchemas.instant,
|
|
49
|
+
}),
|
|
50
|
+
leases: z.object({
|
|
51
|
+
item: brandSchemas.itemId,
|
|
52
|
+
phase: brandSchemas.phaseName,
|
|
53
|
+
attempt: brandSchemas.attemptId,
|
|
54
|
+
gen: countSchema,
|
|
55
|
+
takenAt: brandSchemas.instant,
|
|
56
|
+
releasedAt: brandSchemas.instant.nullable(),
|
|
57
|
+
}),
|
|
58
|
+
pool_slots: z.object({
|
|
59
|
+
pool: brandSchemas.poolName,
|
|
60
|
+
slot: countSchema,
|
|
61
|
+
holder: brandSchemas.attemptId.nullable(),
|
|
62
|
+
gen: countSchema,
|
|
63
|
+
retired: z.boolean(),
|
|
64
|
+
}),
|
|
65
|
+
decisions: factSchemas['boxxy.decision.recorded'],
|
|
66
|
+
evidence: z.object({
|
|
67
|
+
factId: brandSchemas.factId,
|
|
68
|
+
attempt: brandSchemas.attemptId,
|
|
69
|
+
kind: z.enum(['pr', 'commit', 'test', 'ci-run', 'live-check']),
|
|
70
|
+
ref: z.string().min(1),
|
|
71
|
+
observed: z.string().nullable(),
|
|
72
|
+
source: z.enum(['hook', 'trailer', 'agent', 'watch']),
|
|
73
|
+
at: brandSchemas.instant,
|
|
74
|
+
}),
|
|
75
|
+
pull_requests: z.object({
|
|
76
|
+
repo: brandSchemas.repoName,
|
|
77
|
+
number: z.number().int().positive(),
|
|
78
|
+
attempt: brandSchemas.attemptId,
|
|
79
|
+
state: z.enum(['open', 'closed', 'merged']),
|
|
80
|
+
mergedAt: brandSchemas.instant.nullable(),
|
|
81
|
+
mergeCommit: brandSchemas.commitSha.nullable(),
|
|
82
|
+
checkedAt: brandSchemas.instant,
|
|
83
|
+
}),
|
|
84
|
+
runners: z.object({
|
|
85
|
+
id: brandSchemas.runnerId,
|
|
86
|
+
heartbeatAt: brandSchemas.instant,
|
|
87
|
+
paused: z.boolean(),
|
|
88
|
+
}),
|
|
89
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
import { brandSchemas } from '../brand-schemas.ts';
|
|
3
|
+
import type { EvidenceRow } from '../types/core.ts';
|
|
4
|
+
import { buildMockFactId } from './build-mock-fact-id.ts';
|
|
5
|
+
|
|
6
|
+
export function buildMockEvidenceRow(overrides: Partial<EvidenceRow> = {}): EvidenceRow {
|
|
7
|
+
return {
|
|
8
|
+
factId: buildMockFactId(),
|
|
9
|
+
attempt: brandSchemas.attemptId.parse(faker.string.uuid({ version: 7 })),
|
|
10
|
+
kind: 'test',
|
|
11
|
+
ref: faker.system.filePath(),
|
|
12
|
+
observed: null,
|
|
13
|
+
source: 'agent',
|
|
14
|
+
at: brandSchemas.instant.parse(faker.number.int({ max: 1_800_000_000_000 })),
|
|
15
|
+
...overrides,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { faker } from '@faker-js/faker';
|
|
2
|
+
import { brandSchemas } from '../brand-schemas.ts';
|
|
3
|
+
import type { FactId } from '../types/core.ts';
|
|
4
|
+
|
|
5
|
+
export function buildMockFactId(): FactId {
|
|
6
|
+
return brandSchemas.factId.parse(`manual:${faker.string.uuid({ version: 7 })}`);
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { brandSchemas } from '../brand-schemas.ts';
|
|
2
|
+
import type { FrameItem } from '../types/planning.ts';
|
|
3
|
+
import { buildMockItemView } from './build-mock-item-view.ts';
|
|
4
|
+
|
|
5
|
+
export function buildMockFrameItem(overrides: Partial<FrameItem> = {}): FrameItem {
|
|
6
|
+
const { phase, ...view } = overrides;
|
|
7
|
+
|
|
8
|
+
return { ...buildMockItemView(view), phase: phase ?? brandSchemas.phaseName.parse('build') };
|
|
9
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ItemView } from '../types/core.ts';
|
|
2
|
+
import { buildMockItemRow } from './build-mock-item-row.ts';
|
|
3
|
+
|
|
4
|
+
export function buildMockItemView(overrides: Partial<ItemView> = {}): ItemView {
|
|
5
|
+
const { derived, ...row } = overrides;
|
|
6
|
+
|
|
7
|
+
return { ...buildMockItemRow(row), derived: derived ?? { state: 'ready' } };
|
|
8
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { JsonValue } from '@boxxy/content-hash';
|
|
2
|
+
import invariant from 'tiny-invariant';
|
|
3
|
+
|
|
4
|
+
export function toJSONValue(value: unknown): JsonValue {
|
|
5
|
+
if (
|
|
6
|
+
value === null ||
|
|
7
|
+
typeof value === 'boolean' ||
|
|
8
|
+
typeof value === 'number' ||
|
|
9
|
+
typeof value === 'string'
|
|
10
|
+
) {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return value.map((entry: unknown) => toJSONValue(entry));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
invariant(typeof value === 'object', `a ${typeof value} has no JSON form`);
|
|
19
|
+
|
|
20
|
+
return Object.fromEntries(
|
|
21
|
+
Object.entries(value)
|
|
22
|
+
.filter(([, entry]: [string, unknown]) => entry !== undefined)
|
|
23
|
+
.map(([key, entry]: [string, unknown]) => [key, toJSONValue(entry)]),
|
|
24
|
+
);
|
|
25
|
+
}
|
package/src/types/core.ts
CHANGED
|
@@ -124,6 +124,18 @@ export interface ItemRows {
|
|
|
124
124
|
readonly maxAttempts: number;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
// the rows a store reads to derive the state of a set of items, with each blocking source item
|
|
128
|
+
export interface ItemSetRows {
|
|
129
|
+
readonly items: readonly ItemRow[];
|
|
130
|
+
|
|
131
|
+
// live links only
|
|
132
|
+
readonly links: readonly LinkRow[];
|
|
133
|
+
readonly levers: readonly LeverRow[];
|
|
134
|
+
readonly attempts: readonly AttemptRow[];
|
|
135
|
+
readonly liveLeases: readonly LeaseRow[];
|
|
136
|
+
readonly pullRequests: readonly PullRequestRow[];
|
|
137
|
+
}
|
|
138
|
+
|
|
127
139
|
export type MaxAttemptsByKind = ReadonlyMap<ItemKindName, ReadonlyMap<PhaseName, number>>;
|
|
128
140
|
|
|
129
141
|
export interface ItemWithLevers extends ItemView {
|
|
@@ -224,9 +236,9 @@ export interface DecisionRow {
|
|
|
224
236
|
}
|
|
225
237
|
|
|
226
238
|
export interface FaultRecord {
|
|
227
|
-
// the signal, gate or
|
|
239
|
+
// the signal, gate, waiver or filter field name, or `propagation:<signal>:<link>`
|
|
228
240
|
readonly plugin: string;
|
|
229
|
-
readonly kind: 'signal' | 'gate' | 'waiver' | 'admission' | 'propagation';
|
|
241
|
+
readonly kind: 'signal' | 'gate' | 'waiver' | 'admission' | 'propagation' | 'filter-field';
|
|
230
242
|
readonly item: ItemId | null;
|
|
231
243
|
readonly error: string;
|
|
232
244
|
}
|