@boxxy/dispatch 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +4 -1
  2. package/dist/test-utils/build-mock-bind-change.d.ts +7 -0
  3. package/dist/test-utils/build-mock-claim-change.d.ts +2 -0
  4. package/dist/test-utils/build-mock-file-item-change.d.ts +2 -0
  5. package/dist/test-utils/build-mock-lever-change.d.ts +2 -0
  6. package/dist/test-utils/build-mock-link-change.d.ts +7 -0
  7. package/dist/test-utils/build-mock-pause-change.d.ts +2 -0
  8. package/dist/test-utils/build-mock-pull-request-change.d.ts +7 -0
  9. package/dist/test-utils/build-mock-settle-change.d.ts +7 -0
  10. package/dist/test-utils/build-mock-start-change.d.ts +2 -0
  11. package/dist/test-utils/claim-store-attempt-reader-cases.d.ts +2 -0
  12. package/dist/test-utils/claim-store-claim-cases.d.ts +2 -0
  13. package/dist/test-utils/claim-store-contract.d.ts +2 -0
  14. package/dist/test-utils/claim-store-exclusive-cases.d.ts +2 -0
  15. package/dist/test-utils/claim-store-guard-cases.d.ts +2 -0
  16. package/dist/test-utils/claim-store-item-reader-cases.d.ts +2 -0
  17. package/dist/test-utils/claim-store-pool-cases.d.ts +2 -0
  18. package/dist/test-utils/claim-store-rollback-cases.d.ts +2 -0
  19. package/dist/test-utils/create-memory-claim-store.d.ts +2 -0
  20. package/dist/test-utils/types.d.ts +9 -0
  21. package/package.json +8 -2
  22. package/src/test-utils/build-mock-bind-change.ts +36 -0
  23. package/src/test-utils/build-mock-claim-change.ts +42 -0
  24. package/src/test-utils/build-mock-file-item-change.ts +18 -0
  25. package/src/test-utils/build-mock-lever-change.ts +18 -0
  26. package/src/test-utils/build-mock-link-change.ts +40 -0
  27. package/src/test-utils/build-mock-pause-change.ts +27 -0
  28. package/src/test-utils/build-mock-pull-request-change.ts +53 -0
  29. package/src/test-utils/build-mock-settle-change.ts +58 -0
  30. package/src/test-utils/build-mock-start-change.ts +31 -0
  31. package/src/test-utils/claim-store-attempt-reader-cases.ts +327 -0
  32. package/src/test-utils/claim-store-claim-cases.ts +231 -0
  33. package/src/test-utils/claim-store-contract.ts +18 -0
  34. package/src/test-utils/claim-store-exclusive-cases.ts +197 -0
  35. package/src/test-utils/claim-store-guard-cases.ts +373 -0
  36. package/src/test-utils/claim-store-item-reader-cases.ts +257 -0
  37. package/src/test-utils/claim-store-pool-cases.ts +178 -0
  38. package/src/test-utils/claim-store-rollback-cases.ts +175 -0
  39. package/src/test-utils/create-memory-claim-store.ts +574 -0
  40. package/src/test-utils/types.ts +11 -0
package/README.md CHANGED
@@ -9,7 +9,10 @@
9
9
 
10
10
  boxxy's dispatch module, which claims the next task for an agent and settles each attempt.
11
11
 
12
- This version is a placeholder that exports only its package name, as `packageName`. The
12
+ This version exports only its package name, as `packageName`, from its main entry. Its
13
+ `./test-utils/*` entry holds `createMemoryClaimStore`, the memory fake of the `ClaimStore` port, and
14
+ `claimStoreContract`, the cases that every `ClaimStore` passes
15
+ ([ports and adapters](../../docs/design/boxxy/slice-1.md#ports-and-adapters)). The
13
16
  [boxxy README](https://github.com/zgeoff/boxxy#workspace) lists the workspace's packages.
14
17
 
15
18
  ## Install
@@ -0,0 +1,7 @@
1
+ import type { AgentSessionId, AttemptRow, StoreChange } from '@boxxy/model';
2
+ interface BindInput {
3
+ readonly attempt: AttemptRow;
4
+ readonly agentSession: AgentSessionId;
5
+ }
6
+ export declare function buildMockBindChange(input: BindInput): StoreChange;
7
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { AttemptRow, StoreChange } from '@boxxy/model';
2
+ export declare function buildMockClaimChange(attempt: AttemptRow): StoreChange;
@@ -0,0 +1,2 @@
1
+ import type { ItemRow, StoreChange } from '@boxxy/model';
2
+ export declare function buildMockFileItemChange(item: ItemRow): StoreChange;
@@ -0,0 +1,2 @@
1
+ import type { LeverRow, StoreChange } from '@boxxy/model';
2
+ export declare function buildMockLeverChange(lever: LeverRow): StoreChange;
@@ -0,0 +1,7 @@
1
+ import type { ItemId, StoreChange } from '@boxxy/model';
2
+ interface LinkInput {
3
+ readonly source: ItemId;
4
+ readonly target: ItemId;
5
+ }
6
+ export declare function buildMockLinkChange(input: LinkInput): StoreChange;
7
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { RunnerId, StoreChange } from '@boxxy/model';
2
+ export declare function buildMockPauseChange(runner: RunnerId): StoreChange;
@@ -0,0 +1,7 @@
1
+ import type { AttemptRow, PullRequestRow, StoreChange } from '@boxxy/model';
2
+ interface PullRequestInput {
3
+ readonly attempt: AttemptRow;
4
+ readonly state: PullRequestRow['state'];
5
+ }
6
+ export declare function buildMockPullRequestChange(input: PullRequestInput): StoreChange;
7
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { AttemptRow, StoreChange } from '@boxxy/model';
2
+ interface SettleInput {
3
+ readonly attempt: AttemptRow;
4
+ readonly state: 'succeeded' | 'failed';
5
+ }
6
+ export declare function buildMockSettleChange(input: SettleInput): StoreChange;
7
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { AttemptRow, StoreChange } from '@boxxy/model';
2
+ export declare function buildMockStartChange(attempt: AttemptRow): StoreChange;
@@ -0,0 +1,2 @@
1
+ import type { ClaimStoreCase } from './types.ts';
2
+ export declare const claimStoreAttemptReaderCases: readonly ClaimStoreCase[];
@@ -0,0 +1,2 @@
1
+ import type { ClaimStoreCase } from './types.ts';
2
+ export declare const claimStoreClaimCases: readonly ClaimStoreCase[];
@@ -0,0 +1,2 @@
1
+ import type { ClaimStoreCase } from './types.ts';
2
+ export declare const claimStoreContract: readonly ClaimStoreCase[];
@@ -0,0 +1,2 @@
1
+ import type { ClaimStoreCase } from './types.ts';
2
+ export declare const claimStoreExclusiveCases: readonly ClaimStoreCase[];
@@ -0,0 +1,2 @@
1
+ import type { ClaimStoreCase } from './types.ts';
2
+ export declare const claimStoreGuardCases: readonly ClaimStoreCase[];
@@ -0,0 +1,2 @@
1
+ import type { ClaimStoreCase } from './types.ts';
2
+ export declare const claimStoreItemReaderCases: readonly ClaimStoreCase[];
@@ -0,0 +1,2 @@
1
+ import type { ClaimStoreCase } from './types.ts';
2
+ export declare const claimStorePoolCases: readonly ClaimStoreCase[];
@@ -0,0 +1,2 @@
1
+ import type { ClaimStoreCase } from './types.ts';
2
+ export declare const claimStoreRollbackCases: readonly ClaimStoreCase[];
@@ -0,0 +1,2 @@
1
+ import type { ClaimStore } from '@boxxy/model';
2
+ export declare function createMemoryClaimStore(): ClaimStore;
@@ -0,0 +1,9 @@
1
+ import type { ClaimStore } from '@boxxy/model';
2
+ export interface ClaimStoreOutcome {
3
+ readonly received: unknown;
4
+ readonly expected: unknown;
5
+ }
6
+ export type ClaimStoreCase = readonly [
7
+ title: string,
8
+ check: (store: ClaimStore) => Promise<ClaimStoreOutcome>
9
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxxy/dispatch",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "boxxy's dispatch: claims the next task for an agent",
5
5
  "homepage": "https://github.com/zgeoff/boxxy/tree/main/modules/dispatch#readme",
6
6
  "bugs": "https://github.com/zgeoff/boxxy/issues",
@@ -20,7 +20,8 @@
20
20
  ".": {
21
21
  "types": "./dist/index.d.ts",
22
22
  "default": "./src/index.ts"
23
- }
23
+ },
24
+ "./test-utils/*": "./src/test-utils/*.ts"
24
25
  },
25
26
  "publishConfig": {
26
27
  "access": "public"
@@ -29,6 +30,11 @@
29
30
  "build": "rm -rf dist && tsrs -p tsconfig.build.json",
30
31
  "typecheck": "tsrs --noEmit"
31
32
  },
33
+ "dependencies": {
34
+ "@boxxy/model": "0.1.2",
35
+ "tiny-invariant": "1.3.3",
36
+ "ts-pattern": "5.9.2"
37
+ },
32
38
  "engines": {
33
39
  "bun": ">=1.3",
34
40
  "node": ">=24"
@@ -0,0 +1,36 @@
1
+ import { brandSchemas } from '@boxxy/model';
2
+ import type { AgentSessionId, AttemptRow, StoreChange } from '@boxxy/model';
3
+ import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
4
+
5
+ interface BindInput {
6
+ readonly attempt: AttemptRow;
7
+ readonly agentSession: AgentSessionId;
8
+ }
9
+
10
+ export function buildMockBindChange(input: BindInput): StoreChange {
11
+ const attempt = input.attempt;
12
+ const agentSession = input.agentSession;
13
+ const atcSession = brandSchemas.atcSessionId.parse('atc-session-1');
14
+ const seenAt = brandSchemas.instant.parse(2500);
15
+
16
+ return {
17
+ at: seenAt,
18
+ under: { item: attempt.item, phase: attempt.phase, gen: attempt.leaseGen },
19
+ writes: [
20
+ {
21
+ op: 'insert',
22
+ table: 'attempt_sessions',
23
+ row: { attempt: attempt.id, agentSession, atcSession, seenAt },
24
+ },
25
+ ],
26
+ facts: [
27
+ {
28
+ id: buildMockFactId(),
29
+ type: 'boxxy.attempt.session_bound',
30
+ entity: `boxxy:attempt:${attempt.id}`,
31
+ actor: `boxxy:runner:${attempt.runner}`,
32
+ payload: { atcSession, agentSession, seenAt },
33
+ },
34
+ ],
35
+ };
36
+ }
@@ -0,0 +1,42 @@
1
+ import type { AttemptRow, StoreChange } from '@boxxy/model';
2
+ import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
3
+
4
+ export function buildMockClaimChange(attempt: AttemptRow): StoreChange {
5
+ return {
6
+ at: attempt.claimedAt,
7
+ writes: [
8
+ { op: 'take-slot', pool: attempt.pool, holder: attempt.id },
9
+ {
10
+ op: 'insert',
11
+ table: 'leases',
12
+ row: {
13
+ item: attempt.item,
14
+ phase: attempt.phase,
15
+ attempt: attempt.id,
16
+ gen: attempt.leaseGen,
17
+ takenAt: attempt.claimedAt,
18
+ releasedAt: null,
19
+ },
20
+ },
21
+ { op: 'insert', table: 'attempts', row: { ...attempt, slot: 0, slotGen: 0 } },
22
+ ],
23
+ facts: [
24
+ {
25
+ id: buildMockFactId(),
26
+ type: 'boxxy.attempt.claimed',
27
+ entity: `boxxy:attempt:${attempt.id}`,
28
+ actor: `boxxy:runner:${attempt.runner}`,
29
+ payload: {
30
+ item: attempt.item,
31
+ phase: attempt.phase,
32
+ number: attempt.number,
33
+ pool: attempt.pool,
34
+ slot: 0,
35
+ gen: 0,
36
+ recipeHash: attempt.recipeHash,
37
+ rulesVersion: attempt.rulesVersion,
38
+ },
39
+ },
40
+ ],
41
+ };
42
+ }
@@ -0,0 +1,18 @@
1
+ import type { ItemRow, StoreChange } from '@boxxy/model';
2
+ import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
3
+
4
+ export function buildMockFileItemChange(item: ItemRow): StoreChange {
5
+ return {
6
+ at: item.filedAt,
7
+ writes: [{ op: 'insert', table: 'items', row: item }],
8
+ facts: [
9
+ {
10
+ id: buildMockFactId(),
11
+ type: 'boxxy.item.filed',
12
+ entity: `boxxy:item:${item.id}`,
13
+ actor: 'boxxy:owner:local',
14
+ payload: item,
15
+ },
16
+ ],
17
+ };
18
+ }
@@ -0,0 +1,18 @@
1
+ import type { LeverRow, StoreChange } from '@boxxy/model';
2
+ import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
3
+
4
+ export function buildMockLeverChange(lever: LeverRow): StoreChange {
5
+ return {
6
+ at: lever.at,
7
+ writes: [{ op: 'insert', table: 'levers', row: lever }],
8
+ facts: [
9
+ {
10
+ id: buildMockFactId(),
11
+ type: 'boxxy.lever.recorded',
12
+ entity: `boxxy:item:${lever.item}`,
13
+ actor: lever.actor,
14
+ payload: lever,
15
+ },
16
+ ],
17
+ };
18
+ }
@@ -0,0 +1,40 @@
1
+ import { brandSchemas } from '@boxxy/model';
2
+ import type { ItemId, StoreChange } from '@boxxy/model';
3
+ import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
4
+
5
+ interface LinkInput {
6
+ readonly source: ItemId;
7
+ readonly target: ItemId;
8
+ }
9
+
10
+ export function buildMockLinkChange(input: LinkInput): StoreChange {
11
+ const type = brandSchemas.linkTypeName.parse('blocks');
12
+ const addedAt = brandSchemas.instant.parse(3500);
13
+
14
+ return {
15
+ at: addedAt,
16
+ writes: [
17
+ {
18
+ op: 'insert',
19
+ table: 'links',
20
+ row: {
21
+ source: input.source,
22
+ target: input.target,
23
+ type,
24
+ addedBy: 'boxxy:owner:local',
25
+ addedAt,
26
+ removedAt: null,
27
+ },
28
+ },
29
+ ],
30
+ facts: [
31
+ {
32
+ id: buildMockFactId(),
33
+ type: 'boxxy.link.added',
34
+ entity: `boxxy:link:${input.source}:${type}:${input.target}`,
35
+ actor: 'boxxy:owner:local',
36
+ payload: { source: input.source, target: input.target, type },
37
+ },
38
+ ],
39
+ };
40
+ }
@@ -0,0 +1,27 @@
1
+ import { brandSchemas } from '@boxxy/model';
2
+ import type { RunnerId, StoreChange } from '@boxxy/model';
3
+ import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
4
+
5
+ export function buildMockPauseChange(runner: RunnerId): StoreChange {
6
+ return {
7
+ at: brandSchemas.instant.parse(1500),
8
+ writes: [
9
+ {
10
+ op: 'update',
11
+ table: 'runners',
12
+ key: { id: runner },
13
+ expect: { paused: false },
14
+ set: { paused: true },
15
+ },
16
+ ],
17
+ facts: [
18
+ {
19
+ id: buildMockFactId(),
20
+ type: 'boxxy.runner.paused',
21
+ entity: `boxxy:runner:${runner}`,
22
+ actor: 'boxxy:owner:local',
23
+ payload: { runner },
24
+ },
25
+ ],
26
+ };
27
+ }
@@ -0,0 +1,53 @@
1
+ import { brandSchemas } from '@boxxy/model';
2
+ import type { AttemptRow, PullRequestRow, StoreChange } from '@boxxy/model';
3
+ import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
4
+ import { buildMockPullRequestRow } from '@boxxy/model/test-utils/build-mock-pull-request-row';
5
+
6
+ interface PullRequestInput {
7
+ readonly attempt: AttemptRow;
8
+ readonly state: PullRequestRow['state'];
9
+ }
10
+
11
+ export function buildMockPullRequestChange(input: PullRequestInput): StoreChange {
12
+ const attempt = input.attempt;
13
+ const linkedAt = brandSchemas.instant.parse(4000);
14
+ const factId = buildMockFactId();
15
+ const url = 'https://github.com/acme/lamps/pull/7';
16
+
17
+ const pullRequest = buildMockPullRequestRow({
18
+ repo: brandSchemas.repoName.parse('acme/lamps'),
19
+ number: 7,
20
+ attempt: attempt.id,
21
+ state: input.state,
22
+ checkedAt: linkedAt,
23
+ });
24
+
25
+ return {
26
+ at: linkedAt,
27
+ writes: [
28
+ { op: 'insert', table: 'pull_requests', row: pullRequest },
29
+ {
30
+ op: 'insert',
31
+ table: 'evidence',
32
+ row: {
33
+ factId,
34
+ attempt: attempt.id,
35
+ kind: 'pr',
36
+ ref: url,
37
+ observed: null,
38
+ source: 'hook',
39
+ at: linkedAt,
40
+ },
41
+ },
42
+ ],
43
+ facts: [
44
+ {
45
+ id: factId,
46
+ type: 'boxxy.evidence.pr_linked',
47
+ entity: `boxxy:attempt:${attempt.id}`,
48
+ actor: `boxxy:hook:${attempt.id}`,
49
+ payload: { repo: pullRequest.repo, number: 7, url },
50
+ },
51
+ ],
52
+ };
53
+ }
@@ -0,0 +1,58 @@
1
+ import { brandSchemas } from '@boxxy/model';
2
+ import type { AttemptRow, StoreChange } from '@boxxy/model';
3
+ import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
4
+
5
+ interface SettleInput {
6
+ // the attempt as claimed, with the slot and slot gen its take got
7
+ readonly attempt: AttemptRow;
8
+ readonly state: 'succeeded' | 'failed';
9
+ }
10
+
11
+ export function buildMockSettleChange(input: SettleInput): StoreChange {
12
+ const attempt = input.attempt;
13
+ const endedAt = brandSchemas.instant.parse(3000);
14
+ const failure = input.state === 'failed' ? 'closeout-missing' : null;
15
+
16
+ return {
17
+ at: endedAt,
18
+ under: {
19
+ item: attempt.item,
20
+ phase: attempt.phase,
21
+ gen: attempt.leaseGen,
22
+ slot: { pool: attempt.pool, slot: attempt.slot, gen: attempt.slotGen },
23
+ },
24
+ writes: [
25
+ {
26
+ op: 'update',
27
+ table: 'attempts',
28
+ key: { id: attempt.id },
29
+ expect: { state: attempt.state },
30
+ set: { state: input.state, failure, endedAt },
31
+ },
32
+ {
33
+ op: 'update',
34
+ table: 'leases',
35
+ key: { item: attempt.item, phase: attempt.phase, gen: attempt.leaseGen },
36
+ expect: { releasedAt: null },
37
+ set: { releasedAt: endedAt },
38
+ },
39
+ { op: 'release-slot', pool: attempt.pool, slot: attempt.slot, holder: attempt.id },
40
+ ],
41
+ facts: [
42
+ {
43
+ id: buildMockFactId(),
44
+ type: 'boxxy.attempt.settled',
45
+ entity: `boxxy:attempt:${attempt.id}`,
46
+ actor: `boxxy:runner:${attempt.runner}`,
47
+ payload: {
48
+ state: input.state,
49
+ failure,
50
+ endedAt,
51
+ criteria: [],
52
+ matched: [],
53
+ frictionDropped: 0,
54
+ },
55
+ },
56
+ ],
57
+ };
58
+ }
@@ -0,0 +1,31 @@
1
+ import { brandSchemas } from '@boxxy/model';
2
+ import type { AttemptRow, StoreChange } from '@boxxy/model';
3
+ import { buildMockFactId } from '@boxxy/model/test-utils/build-mock-fact-id';
4
+
5
+ export function buildMockStartChange(attempt: AttemptRow): StoreChange {
6
+ const atcSession = brandSchemas.atcSessionId.parse('atc-session-1');
7
+ const startedAt = brandSchemas.instant.parse(2500);
8
+
9
+ return {
10
+ at: startedAt,
11
+ under: { item: attempt.item, phase: attempt.phase, gen: attempt.leaseGen },
12
+ writes: [
13
+ {
14
+ op: 'update',
15
+ table: 'attempts',
16
+ key: { id: attempt.id },
17
+ expect: { state: 'starting' },
18
+ set: { state: 'running', atcSession, startedAt },
19
+ },
20
+ ],
21
+ facts: [
22
+ {
23
+ id: buildMockFactId(),
24
+ type: 'boxxy.attempt.started',
25
+ entity: `boxxy:attempt:${attempt.id}`,
26
+ actor: `boxxy:runner:${attempt.runner}`,
27
+ payload: { atcSession },
28
+ },
29
+ ],
30
+ };
31
+ }