@usine/engine 0.1.0 → 0.3.0
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 +39 -12
- package/dist/cursor/{store.d.ts → cursor.d.ts} +9 -7
- package/dist/cursor/cursor.d.ts.map +1 -0
- package/dist/cursor/{store.js → cursor.js} +8 -7
- package/dist/cursor/cursor.js.map +1 -0
- package/dist/cycle-budget/cycle-budget.d.ts +25 -0
- package/dist/cycle-budget/cycle-budget.d.ts.map +1 -0
- package/dist/cycle-budget/cycle-budget.js +117 -0
- package/dist/cycle-budget/cycle-budget.js.map +1 -0
- package/dist/cycle-budget/model.d.ts +26 -0
- package/dist/cycle-budget/model.d.ts.map +1 -0
- package/dist/cycle-budget/model.js +20 -0
- package/dist/cycle-budget/model.js.map +1 -0
- package/dist/cycle-budget/queries.d.ts +27 -0
- package/dist/cycle-budget/queries.d.ts.map +1 -0
- package/dist/cycle-budget/queries.js +175 -0
- package/dist/cycle-budget/queries.js.map +1 -0
- package/dist/database/database.d.ts +1 -1
- package/dist/database/database.d.ts.map +1 -1
- package/dist/database/local-sqlite.d.ts +32 -12
- package/dist/database/local-sqlite.d.ts.map +1 -1
- package/dist/database/local-sqlite.js +121 -16
- package/dist/database/local-sqlite.js.map +1 -1
- package/dist/database/schema.d.ts +302 -9
- package/dist/database/schema.d.ts.map +1 -1
- package/dist/database/schema.js +69 -12
- package/dist/database/schema.js.map +1 -1
- package/dist/engine/engine.d.ts +59 -0
- package/dist/engine/engine.d.ts.map +1 -0
- package/dist/{engine.js → engine/engine.js} +80 -32
- package/dist/engine/engine.js.map +1 -0
- package/dist/engine/error.d.ts +45 -0
- package/dist/engine/error.d.ts.map +1 -0
- package/dist/engine/error.js +49 -0
- package/dist/engine/error.js.map +1 -0
- package/dist/engine/lifecycle.d.ts +112 -0
- package/dist/engine/lifecycle.d.ts.map +1 -0
- package/dist/engine/lifecycle.js +132 -0
- package/dist/engine/lifecycle.js.map +1 -0
- package/dist/engine/shutdown.d.ts +12 -0
- package/dist/engine/shutdown.d.ts.map +1 -0
- package/dist/engine/shutdown.js +10 -0
- package/dist/engine/shutdown.js.map +1 -0
- package/dist/engine/submission.d.ts +58 -0
- package/dist/engine/submission.d.ts.map +1 -0
- package/dist/engine/submission.js +54 -0
- package/dist/engine/submission.js.map +1 -0
- package/dist/item-projection/{service.d.ts → item-projection.d.ts} +6 -6
- package/dist/item-projection/item-projection.d.ts.map +1 -0
- package/dist/item-projection/{service.js → item-projection.js} +7 -5
- package/dist/item-projection/item-projection.js.map +1 -0
- package/dist/item-projection/model.d.ts +2 -5
- package/dist/item-projection/model.d.ts.map +1 -1
- package/dist/item-projection/model.js +5 -5
- package/dist/item-projection/model.js.map +1 -1
- package/dist/loop-control/loop-control.d.ts +21 -0
- package/dist/loop-control/loop-control.d.ts.map +1 -0
- package/dist/{loop-control.js → loop-control/loop-control.js} +7 -5
- package/dist/loop-control/loop-control.js.map +1 -0
- package/dist/loop-control/model.d.ts +10 -0
- package/dist/loop-control/model.d.ts.map +1 -0
- package/dist/loop-control/model.js +9 -0
- package/dist/loop-control/model.js.map +1 -0
- package/dist/repository-label-projection/model.d.ts +12 -3
- package/dist/repository-label-projection/model.d.ts.map +1 -1
- package/dist/repository-label-projection/model.js +14 -5
- package/dist/repository-label-projection/model.js.map +1 -1
- package/dist/repository-label-projection/{service.d.ts → repository-label-projection.d.ts} +5 -5
- package/dist/repository-label-projection/repository-label-projection.d.ts.map +1 -0
- package/dist/repository-label-projection/{service.js → repository-label-projection.js} +17 -7
- package/dist/repository-label-projection/repository-label-projection.js.map +1 -0
- package/dist/run-reader/model.d.ts +46 -0
- package/dist/run-reader/model.d.ts.map +1 -0
- package/dist/run-reader/model.js +44 -0
- package/dist/run-reader/model.js.map +1 -0
- package/dist/run-reader/run-reader.d.ts +29 -0
- package/dist/run-reader/run-reader.d.ts.map +1 -0
- package/dist/run-reader/run-reader.js +44 -0
- package/dist/run-reader/run-reader.js.map +1 -0
- package/dist/step-executor.d.ts +36 -12
- package/dist/step-executor.d.ts.map +1 -1
- package/dist/step-executor.js +22 -5
- package/dist/step-executor.js.map +1 -1
- package/dist/store/codec.d.ts +278 -40
- package/dist/store/codec.d.ts.map +1 -1
- package/dist/store/codec.js +72 -44
- package/dist/store/codec.js.map +1 -1
- package/dist/store/queries.d.ts +49 -16
- package/dist/store/queries.d.ts.map +1 -1
- package/dist/store/queries.js +104 -91
- package/dist/store/queries.js.map +1 -1
- package/dist/store/store.d.ts +44 -26
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +199 -128
- package/dist/store/store.js.map +1 -1
- package/dist/store/transcript.d.ts +9 -0
- package/dist/store/transcript.d.ts.map +1 -0
- package/dist/store/transcript.js +71 -0
- package/dist/store/transcript.js.map +1 -0
- package/dist/transcript.d.ts +86 -0
- package/dist/transcript.d.ts.map +1 -0
- package/dist/transcript.js +77 -0
- package/dist/transcript.js.map +1 -0
- package/dist/workflow/admission.d.ts +13 -0
- package/dist/workflow/admission.d.ts.map +1 -0
- package/dist/workflow/admission.js +55 -0
- package/dist/workflow/admission.js.map +1 -0
- package/dist/workflow/command.d.ts +13 -0
- package/dist/workflow/command.d.ts.map +1 -0
- package/dist/workflow/command.js +17 -0
- package/dist/workflow/command.js.map +1 -0
- package/dist/workflow/workflow.d.ts +18 -0
- package/dist/workflow/workflow.d.ts.map +1 -0
- package/dist/workflow/workflow.js +108 -0
- package/dist/workflow/workflow.js.map +1 -0
- package/drizzle/20260903091940_queued_approved_runs/migration.sql +3 -0
- package/drizzle/20260903091940_queued_approved_runs/snapshot.json +1147 -0
- package/drizzle/20260905150403_item_cycle_budgets/migration.sql +58 -0
- package/drizzle/20260905150403_item_cycle_budgets/snapshot.json +1417 -0
- package/drizzle/20260905174009_item_run_history/migration.sql +1 -0
- package/drizzle/20260905174009_item_run_history/snapshot.json +1431 -0
- package/drizzle/20260912111855_operator_intervention/migration.sql +42 -0
- package/drizzle/20260912111855_operator_intervention/snapshot.json +1461 -0
- package/package.json +41 -5
- package/dist/cursor/model.d.ts +0 -5
- package/dist/cursor/model.d.ts.map +0 -1
- package/dist/cursor/model.js +0 -4
- package/dist/cursor/model.js.map +0 -1
- package/dist/cursor/store.d.ts.map +0 -1
- package/dist/cursor/store.js.map +0 -1
- package/dist/engine.d.ts +0 -39
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js.map +0 -1
- package/dist/index.d.ts +0 -20
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -20
- package/dist/index.js.map +0 -1
- package/dist/item-projection/service.d.ts.map +0 -1
- package/dist/item-projection/service.js.map +0 -1
- package/dist/loop-control.d.ts +0 -16
- package/dist/loop-control.d.ts.map +0 -1
- package/dist/loop-control.js.map +0 -1
- package/dist/model.d.ts +0 -185
- package/dist/model.d.ts.map +0 -1
- package/dist/model.js +0 -174
- package/dist/model.js.map +0 -1
- package/dist/repository-label-projection/service.d.ts.map +0 -1
- package/dist/repository-label-projection/service.js.map +0 -1
- package/dist/run-reader.d.ts +0 -20
- package/dist/run-reader.d.ts.map +0 -1
- package/dist/run-reader.js +0 -19
- package/dist/run-reader.js.map +0 -1
- package/dist/workflow.d.ts +0 -14
- package/dist/workflow.d.ts.map +0 -1
- package/dist/workflow.js +0 -105
- package/dist/workflow.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ The default adapter stores usine's Event ledger and Run projections beside Effec
|
|
|
6
6
|
|
|
7
7
|
`CursorStore` keeps typed keys with JSON values in that database. Trigger adapters use it for efficiency-only cursors such as GitHub watermarks and ETags; deleting a cursor causes a safe replay because the Event ledger remains the source of deduplication.
|
|
8
8
|
|
|
9
|
-
`LoopControl` keeps runtime pause state in the same database. Pausing or unpausing a Loop never edits its authored Markdown file. `RunReader.list()` returns lightweight durable Run summaries newest first for observe-and-intervene surfaces, without loading Event payloads or issuing one snapshot read per Run. `RunReader.revision()` lets callers reuse an unchanged projection without rescanning history.
|
|
9
|
+
`LoopControl` keeps runtime pause state in the same database. Pausing or unpausing a Loop never edits its authored Markdown file. `RunReader.list()` returns lightweight durable Run summaries newest first for observe-and-intervene surfaces, without loading Event payloads or issuing one snapshot read per Run. `RunReader.forItems(itemKeys)` reads every saved Run and its Attempts for the selected Items, independently of the recent-history window. `RunReader.revision()` lets callers reuse an unchanged projection without rescanning history.
|
|
10
10
|
|
|
11
11
|
`ItemProjectionReader` and `ItemProjectionWriter` store the latest issue and pull-request snapshots already fetched by trigger polling. Board consumers can read that projection after a restart without making a second GitHub query; `ItemProjectionReader.list` returns the newest Items first, bounded by the caller's `limit`. Item writes update the shared projection revision only when the content hash changes.
|
|
12
12
|
|
|
@@ -15,30 +15,37 @@ The default adapter stores usine's Event ledger and Run projections beside Effec
|
|
|
15
15
|
## Runtime rules
|
|
16
16
|
|
|
17
17
|
- Approval-gated Runs wait for an approval Event before their first Attempt.
|
|
18
|
-
- Infrastructure failures retry automatically, with a cap of three Attempts. Agent and
|
|
19
|
-
- A newer Event supersedes a pending or
|
|
20
|
-
- Revoke stops pending and
|
|
18
|
+
- Infrastructure failures retry automatically, with a cap of three Attempts. Agent, defect, and guard failures wait for an operator retry.
|
|
19
|
+
- A newer Event supersedes a pending, waiting, or queued Run. A running Attempt finishes first, then the engine opens only the newest queued Event for that Item.
|
|
20
|
+
- Revoke stops pending, waiting, and queued Runs. Operator cancellation can also stop a running Attempt.
|
|
21
21
|
- The startup sweep rejoins open workflow executions. If the process stopped during an Attempt, the engine records that Attempt as an infrastructure failure and resumes as N+1.
|
|
22
|
-
- A
|
|
23
|
-
- Attempt channel envelopes are stored in an append-only transcript. Normalized parent and child session events retain their session identity. `RunEngine.transcript` accepts distinct `TranscriptAllQuery`, `TranscriptTailQuery`, and `TranscriptPageQuery` variants, so full diagnostics, bounded live tails, and bounded backward pages cannot be confused. Tail and page queries require a positive row limit. A protocol-
|
|
22
|
+
- A defect escaping `StepExecutor` is saved as `DefectFailure`, preserving its original cause and requiring an operator retry. If the engine itself hits an invariant defect, the workflow suspends for inspection; `RunEngine.reconcile` (or a restart) repairs the projection.
|
|
23
|
+
- Attempt channel envelopes are stored in an append-only transcript. Durable protocol-v3 envelopes remain readable after the live channel upgrades to v4. Normalized parent and child session events retain their session identity. `RunEngine.transcript` accepts distinct `TranscriptAllQuery`, `TranscriptTailQuery`, and `TranscriptPageQuery` variants, so full diagnostics, bounded live tails, and bounded backward pages cannot be confused. Tail and page queries require a positive row limit. A protocol-v4 `Started` frame writes the SDK-selected parent model onto the Attempt.
|
|
24
24
|
- The submitted agent, rendered task, payload snapshot, effective parent model, and per-Attempt child brain survive restarts in the run ledger.
|
|
25
25
|
|
|
26
|
+
The engine's [lifecycle decisions](src/engine/lifecycle.ts) determine Attempt completion, interrupted-Attempt recovery, cancellation, operator retry, and whether a new Event can replace a Run. `RunStore` reads the current records and applies each decision in one transaction, including the Step result and pending workflow commands. Normal completion and restart recovery share the three-Attempt retry cap; reconciliation from a final workflow result closes a typed failure without retrying it. Late executor results cannot settle a replacement Attempt. Effect Workflow continues to own durable execution and signal delivery, as described in [ADR 0003](../../docs/adr/0003-durable-execution-on-effect-workflow.md).
|
|
27
|
+
|
|
26
28
|
## Composition
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
Every subpath is platform-free except the Node local adapter at `@usine/engine/local-sqlite`. Its `layer` supplies local SQLite, Effect `SingleRunner`, workflow registration, and startup reconciliation. Provide a `StepExecutor` implementation at the composition root:
|
|
29
31
|
|
|
30
32
|
```ts
|
|
31
33
|
import { Effect, Layer } from "effect"
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
+
import { WorkerName } from "@usine/core/worker"
|
|
35
|
+
import { InstanceDirectory } from "@usine/core/instance"
|
|
36
|
+
import { StepResult } from "@usine/core/step/result"
|
|
37
|
+
import { StepExecutor } from "@usine/engine/step-executor"
|
|
34
38
|
import { layer } from "@usine/engine/local-sqlite"
|
|
39
|
+
import { RunEngine } from "@usine/engine/engine"
|
|
40
|
+
import { CursorStore } from "@usine/engine/cursor"
|
|
41
|
+
import { LoopControl } from "@usine/engine/loop-control"
|
|
35
42
|
|
|
36
43
|
const StepExecutorLive = Layer.succeed(StepExecutor)({
|
|
37
44
|
reserve: () => Effect.succeed({
|
|
38
|
-
worker:
|
|
45
|
+
worker: WorkerName.make("local"),
|
|
39
46
|
release: Effect.void,
|
|
40
47
|
}),
|
|
41
|
-
execute: ({ event }) => Effect.succeed(new StepResult
|
|
48
|
+
execute: ({ event }) => Effect.succeed(new StepResult({
|
|
42
49
|
message: `Handled ${event.id}`,
|
|
43
50
|
facts: [],
|
|
44
51
|
})),
|
|
@@ -58,7 +65,17 @@ const program = Effect.gen(function* () {
|
|
|
58
65
|
})
|
|
59
66
|
```
|
|
60
67
|
|
|
61
|
-
`
|
|
68
|
+
`@usine/engine/engine` provides `RunEngine`, `Submission`, submission results, and typed engine errors. `@usine/engine/run-reader` provides `RunReader` and its snapshot, summary, and evidence types; `/transcript` provides transcript entries, sequence values, and query variants. Import projection contracts from `/item-projection` and `/repository-label-projection`, and loop pause state with `LoopControl` from `/loop-control`.
|
|
69
|
+
|
|
70
|
+
`RunReader.evidence(runId)` reads the Run snapshot and every transcript entry for its Attempts in one database transaction. The returned `RunEvidence` stays consistent when an Attempt completes or appends output during the read. Transcript entries use append order with no page limit.
|
|
71
|
+
|
|
72
|
+
For local maintenance commands, `runReaderLayer(instanceDirectory)` from `@usine/engine/local-sqlite` provides only `RunReader` using a read-only SQLite connection. It requires an existing runtime database and does not initialize tables, migrate the schema, or change the journal mode. A missing, unreadable, or incompatible database fails through `EnginePersistenceError`.
|
|
73
|
+
|
|
74
|
+
`backup(instanceDirectory, destination)` from `@usine/engine/local-sqlite` copies every runtime table and committed WAL data while other connections remain active. It opens the source read-only, without migrations. The destination must be a new file in an existing directory that supports hard links; the adapter saves a complete owner-only copy without overwriting existing files.
|
|
75
|
+
|
|
76
|
+
`RuntimeStoreBackupError` includes the failed operation (`open`, `stage`, `copy`, or `publish`), both paths, and the underlying error message or code. Structured logs report failures and the number of pages copied on success. If interrupted, the adapter waits for Node's backup to finish before closing its connection and deleting temporary files.
|
|
77
|
+
|
|
78
|
+
`layerWithDatabase` from `@usine/engine/engine` accepts another SQLite-compatible database layer. That layer must provide both `RunDatabase` from `@usine/engine/database` and the matching Effect `SqlClient`, preserving atomic transactions and keeping the run projections beside Effect's cluster tables; the deployment also supplies its platform `Crypto` layer, which the cluster uses to hash over-length deduplication keys. Cloudflare D1 is not supported by Drizzle RC.4 for this use yet because its Effect driver cannot execute the store's transactions.
|
|
62
79
|
|
|
63
80
|
The Drizzle schema is the source of truth for usine tables. After changing it, generate and review a migration with:
|
|
64
81
|
|
|
@@ -67,3 +84,13 @@ pnpm --filter @usine/engine db:generate --name <migration_name>
|
|
|
67
84
|
```
|
|
68
85
|
|
|
69
86
|
Everything is 0.x, so APIs may change. Supply `effect` at the version pinned by the usine workspace.
|
|
87
|
+
|
|
88
|
+
`CycleBudgets` at `@usine/engine/cycle-budget` persists Item-level exhaustion Asks and explicit, idempotent reset Events. `Submission.cycleLimit` defaults to three; schedule adapters supply `None`. Run admission reserves the allowance transactionally and returns `CycleBlocked` when the Item needs a reset. Attempt retries do not charge again, and canceled Runs without Attempts return their reservation. Existing history and queued Item Events acquire the default budget on upgrade.
|
|
89
|
+
|
|
90
|
+
Reset records a fresh polling request. Trigger adapters capture `CycleBudgets.generation(itemKey)` before fetching input and include it in both `Submission.cycleGeneration` and the Event identity. Older observations are superseded at admission, including in-flight polls and queued Events; fresh polling creates a new Event with current execution settings. Labels, restarts, and Loop configuration changes never clear an Ask. See [ADR 0022](../../docs/adr/0022-item-cycle-budgets-and-comment-reviews.md).
|
|
91
|
+
|
|
92
|
+
`RunEngine.drain()` permanently closes Attempt admission for that runtime and waits for admitted Attempts to persist their outcomes. Worker reservations, approval gates, and subsequent retries resume after restarting the runtime. Interrupting the drain wait keeps admission closed. The host supplies a shutdown deadline and closes its runtime scope after the wait; see [ADR 0023](../../docs/adr/0023-graceful-attempt-drain.md).
|
|
93
|
+
|
|
94
|
+
Hosts that handle shutdown during startup can provide `RunEngineShutdown` from `/engine`: a fresh `Deferred<void>` allocated before building the runtime. Completing it immediately closes Attempt admission, including recovered work. After startup completes, `RunEngine.drain()` waits for the admitted outcomes. The server installs this protection before building its runtime.
|
|
95
|
+
|
|
96
|
+
Stored protocol-v5 envelopes remain readable after upgrading the live channel to v6. Session transcript queries retain each session's launch record alongside its latest lifecycle state, so saved harness session ids and worker paths remain available after completion and outside the loaded history page.
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { Context, Effect, Layer, Option
|
|
1
|
+
import { Schema, Context, Effect, Layer, Option } from "effect";
|
|
2
2
|
import { RunDatabase } from "../database/database.ts";
|
|
3
|
-
import { EnginePersistenceError } from "../
|
|
4
|
-
|
|
3
|
+
import { EnginePersistenceError } from "../engine/error.ts";
|
|
4
|
+
/** Identity of one cursor. Cursors only save work, so losing one is safe. */
|
|
5
|
+
export declare const CursorKey: Schema.brand<Schema.NonEmptyString, "CursorKey">;
|
|
6
|
+
export type CursorKey = typeof CursorKey.Type;
|
|
5
7
|
declare const CursorStore_base: Context.ServiceClass<CursorStore, "@usine/engine/CursorStore", {
|
|
6
8
|
readonly read: <T, E extends Schema.Json>(key: CursorKey, schema: Schema.Codec<T, E, never, never>) => Effect.Effect<Option.Option<T>, EnginePersistenceError>;
|
|
7
9
|
readonly write: <T, E extends Schema.Json>(key: CursorKey, schema: Schema.Codec<T, E, never, never>, value: T) => Effect.Effect<void, EnginePersistenceError>;
|
|
8
10
|
}>;
|
|
9
11
|
/**
|
|
10
|
-
* Durable storage for
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* Durable storage for cursors that only save work. The module that owns a key
|
|
13
|
+
* passes its schema per call; a stored value that no longer decodes reads as
|
|
14
|
+
* absent and is logged.
|
|
13
15
|
*/
|
|
14
16
|
export declare class CursorStore extends CursorStore_base {
|
|
15
17
|
static readonly layer: Layer.Layer<CursorStore, never, RunDatabase>;
|
|
16
18
|
}
|
|
17
19
|
export {};
|
|
18
|
-
//# sourceMappingURL=
|
|
20
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/cursor/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,6EAA6E;AAC7E,eAAO,MAAM,SAAS,kDAAwD,CAAC;AAE/E,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAC;;mBAkB3B,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,EACtC,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KACrC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,sBAAsB,CAAC;oBAC5C,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,EACvC,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EACxC,KAAK,EAAE,CAAC,KACL,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,sBAAsB,CAAC;;AAhBpD;;;;GAIG;AACH,qBAAa,WAAY,SAAQ,gBAaD;IAC9B,MAAM,CAAC,QAAQ,CAAC,KAAK,+CAqDnB;CACH"}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { Context, Effect, Layer, Option
|
|
1
|
+
import { Schema, Context, Effect, Layer, Option } from "effect";
|
|
2
2
|
import { eq } from "drizzle-orm";
|
|
3
3
|
import { RunDatabase } from "../database/database.js";
|
|
4
4
|
import { cursors } from "../database/schema.js";
|
|
5
|
-
import { EnginePersistenceError } from "../
|
|
6
|
-
|
|
5
|
+
import { EnginePersistenceError } from "../engine/error.js";
|
|
6
|
+
/** Identity of one cursor. Cursors only save work, so losing one is safe. */
|
|
7
|
+
export const CursorKey = Schema.NonEmptyString.pipe(Schema.brand("CursorKey"));
|
|
7
8
|
const CursorRow = Schema.Struct({ value: Schema.Json });
|
|
8
9
|
const decodeCursorRows = Schema.decodeUnknownEffect(Schema.Array(CursorRow));
|
|
9
10
|
const persistence = (operation) => (effect) => Effect.mapError(effect, (cause) => new EnginePersistenceError({ operation, cause }));
|
|
10
11
|
/**
|
|
11
|
-
* Durable storage for
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Durable storage for cursors that only save work. The module that owns a key
|
|
13
|
+
* passes its schema per call; a stored value that no longer decodes reads as
|
|
14
|
+
* absent and is logged.
|
|
14
15
|
*/
|
|
15
16
|
export class CursorStore extends Context.Service()("@usine/engine/CursorStore") {
|
|
16
17
|
static layer = Layer.effect(CursorStore, Effect.gen(function* () {
|
|
@@ -51,4 +52,4 @@ export class CursorStore extends Context.Service()("@usine/engine/CursorStore")
|
|
|
51
52
|
};
|
|
52
53
|
}));
|
|
53
54
|
}
|
|
54
|
-
//# sourceMappingURL=
|
|
55
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/cursor/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,6EAA6E;AAC7E,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;AAI/E,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;AACxD,MAAM,gBAAgB,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;AAE7E,MAAM,WAAW,GACf,CAAC,SAAiB,EAAE,EAAE,CACtB,CAAU,MAA8B,EAA+C,EAAE,CACvF,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,sBAAsB,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAEzF;;;;GAIG;AACH,MAAM,OAAO,WAAY,SAAQ,OAAO,CAAC,OAAO,EAa7C,CAAC,2BAA2B,CAAC;IAC9B,MAAM,CAAU,KAAK,GAAG,KAAK,CAAC,MAAM,CAClC,WAAW,EACX,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC;QAEpC,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,QAAQ,CAAC,EAC3C,GAAc,EACd,MAAwC;gBAExC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;oBACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,QAAQ;yBAC7B,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;yBACpC,IAAI,CAAC,OAAO,CAAC;yBACb,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;yBACjC,KAAK,CAAC,CAAC,CAAC;yBACR,OAAO,EAAE,CAAC;oBACb,OAAO,KAAK,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;gBAC3C,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC,CAAC;gBAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC7C,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;oBAAE,OAAO,MAAM,CAAC,IAAI,EAAK,CAAC;gBACnD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE;oBACnE,gBAAgB,EAAE,OAAO;iBAC1B,CAAC,CAAC,IAAI,CACL,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EACvB,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CACrB,MAAM,CAAC,UAAU,CAAC,wDAAwD,EAAE;oBAC1E,GAAG;oBACH,MAAM,EAAE,KAAK,CAAC,OAAO;iBACtB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAK,CAAC,CAAC,CACrC,CACF,CAAC;YACJ,CAAC,CAAC;YACF,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,EAC7C,GAAc,EACd,MAAwC,EACxC,KAAQ;gBAER,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAC5D,WAAW,CAAC,uBAAuB,CAAC,CACrC,CAAC;gBACF,KAAK,CAAC,CAAC,QAAQ;qBACZ,MAAM,CAAC,OAAO,CAAC;qBACf,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;qBAC9C,kBAAkB,CAAC;oBAClB,MAAM,EAAE,OAAO,CAAC,SAAS;oBACzB,GAAG,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE;iBAC5B,CAAC;qBACD,OAAO,EAAE;qBACT,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAC;YAC9D,CAAC,CAAC;SACH,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;CACH"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CycleBudgetAsk, CycleBudgetAskId, CycleGeneration } from "@usine/core/cycle-budget";
|
|
2
|
+
import { ItemKey } from "@usine/core/item";
|
|
3
|
+
import { Event } from "@usine/core/event";
|
|
4
|
+
import { Context, Effect, Layer } from "effect";
|
|
5
|
+
import { RunDatabase } from "../database/database.ts";
|
|
6
|
+
import { EnginePersistenceError } from "../engine/error.ts";
|
|
7
|
+
import { CycleBudgetReset, CycleBudgetResetRejectedError } from "./model.ts";
|
|
8
|
+
declare const CycleBudgets_base: Context.ServiceClass<CycleBudgets, "@usine/engine/CycleBudgets", {
|
|
9
|
+
/** Capture this generation before fetching an Item's trigger input and include it in its Event identity. */
|
|
10
|
+
readonly generation: (itemKey: ItemKey) => Effect.Effect<CycleGeneration, EnginePersistenceError>;
|
|
11
|
+
/** All unanswered cycle-budget Asks, including Items whose labels no longer match. */
|
|
12
|
+
readonly asks: () => Effect.Effect<ReadonlyArray<CycleBudgetAsk>, EnginePersistenceError>;
|
|
13
|
+
/** Records an answer for this exact Ask and requests fresh trigger evaluation. Repeats are idempotent. */
|
|
14
|
+
readonly reset: (askId: CycleBudgetAskId, answer: Event) => Effect.Effect<void, EnginePersistenceError | CycleBudgetResetRejectedError>;
|
|
15
|
+
/** Resets still awaiting a durable polling-cursor restart. */
|
|
16
|
+
readonly pendingResets: () => Effect.Effect<ReadonlyArray<CycleBudgetReset>, EnginePersistenceError>;
|
|
17
|
+
/** Acknowledges only this generation after fresh polling progress was durably recorded. */
|
|
18
|
+
readonly acknowledgeReset: (reset: CycleBudgetReset) => Effect.Effect<void, EnginePersistenceError>;
|
|
19
|
+
}>;
|
|
20
|
+
/** Durable Item-level Asks and explicit resets, independent of trigger and notification transports. */
|
|
21
|
+
export declare class CycleBudgets extends CycleBudgets_base {
|
|
22
|
+
static readonly layer: Layer.Layer<CycleBudgets, never, RunDatabase>;
|
|
23
|
+
}
|
|
24
|
+
export { CycleBudgetReset, CycleBudgetResetRejectedError } from "./model.ts";
|
|
25
|
+
//# sourceMappingURL=cycle-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cycle-budget.d.ts","sourceRoot":"","sources":["../../src/cycle-budget/cycle-budget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG1C,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAU,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAQtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;;IAgBzE,4GAA4G;yBACvF,CACnB,OAAO,EAAE,OAAO,KACb,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,sBAAsB,CAAC;IAC3D,sFAAsF;mBACvE,MAAM,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,sBAAsB,CAAC;IACzF,0GAA0G;oBAC1F,CACd,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,KAAK,KACV,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,sBAAsB,GAAG,6BAA6B,CAAC;IAChF,8DAA8D;4BACtC,MAAM,MAAM,CAAC,MAAM,CACzC,aAAa,CAAC,gBAAgB,CAAC,EAC/B,sBAAsB,CACvB;IACD,2FAA2F;+BAChE,CACzB,KAAK,EAAE,gBAAgB,KACpB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,sBAAsB,CAAC;;AAvBpD,uGAAuG;AACvG,qBAAa,YAAa,SAAQ,iBAwBD;IAC/B,MAAM,CAAC,QAAQ,CAAC,KAAK,gDA+InB;CACH;AAED,OAAO,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { CycleBudgetAsk, CycleBudgetAskId, CycleGeneration } from "@usine/core/cycle-budget";
|
|
2
|
+
import { ItemKey } from "@usine/core/item";
|
|
3
|
+
import { Event } from "@usine/core/event";
|
|
4
|
+
import * as RunState from "@usine/core/run/state";
|
|
5
|
+
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
6
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
7
|
+
import { RunDatabase } from "../database/database.js";
|
|
8
|
+
import { cycleReservations, cycleResets, events, itemCycleBudgets, runs, } from "../database/schema.js";
|
|
9
|
+
import { EnginePersistenceError } from "../engine/error.js";
|
|
10
|
+
import { encodeDateTime, encodePayload } from "../store/codec.js";
|
|
11
|
+
import { CycleBudgetReset, CycleBudgetResetRejectedError } from "./model.js";
|
|
12
|
+
import { cycleQueries } from "./queries.js";
|
|
13
|
+
const persistence = (effect) => effect.pipe(Effect.mapError((cause) => cause instanceof CycleBudgetResetRejectedError
|
|
14
|
+
? cause
|
|
15
|
+
: new EnginePersistenceError({ operation: "update cycle budget", cause })));
|
|
16
|
+
/** Durable Item-level Asks and explicit resets, independent of trigger and notification transports. */
|
|
17
|
+
export class CycleBudgets extends Context.Service()("@usine/engine/CycleBudgets") {
|
|
18
|
+
static layer = Layer.effect(CycleBudgets, Effect.gen(function* () {
|
|
19
|
+
const database = yield* RunDatabase;
|
|
20
|
+
const queries = cycleQueries(database);
|
|
21
|
+
const generation = Effect.fn("CycleBudgets.generation")(function* (itemKey) {
|
|
22
|
+
yield* Effect.annotateCurrentSpan({ itemKey });
|
|
23
|
+
const budget = yield* queries
|
|
24
|
+
.get(itemKey)
|
|
25
|
+
.pipe(Effect.mapError((cause) => new EnginePersistenceError({ operation: "read cycle generation", cause })));
|
|
26
|
+
return Option.match(budget, {
|
|
27
|
+
onNone: () => CycleGeneration.make(0),
|
|
28
|
+
onSome: (row) => row.generation,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
const asks = Effect.fn("CycleBudgets.asks")(() => queries
|
|
32
|
+
.asks()
|
|
33
|
+
.pipe(Effect.mapError((cause) => new EnginePersistenceError({ operation: "read cycle Asks", cause }))));
|
|
34
|
+
const reset = Effect.fn("CycleBudgets.reset")(function* (askId, answer) {
|
|
35
|
+
yield* Effect.annotateCurrentSpan({ askId, eventId: answer.id });
|
|
36
|
+
yield* database
|
|
37
|
+
.transaction((transaction) => Effect.gen(function* () {
|
|
38
|
+
const previous = yield* transaction
|
|
39
|
+
.select()
|
|
40
|
+
.from(cycleResets)
|
|
41
|
+
.where(eq(cycleResets.askId, askId))
|
|
42
|
+
.execute();
|
|
43
|
+
if (previous[0]?.eventId === answer.id) {
|
|
44
|
+
yield* Effect.logInfo("Cycle budget reset answer already recorded", {
|
|
45
|
+
askId,
|
|
46
|
+
eventId: answer.id,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const ask = (yield* cycleQueries(transaction).asks()).find(({ id }) => id === askId);
|
|
51
|
+
if (ask === undefined)
|
|
52
|
+
return yield* new CycleBudgetResetRejectedError({ askId });
|
|
53
|
+
const budget = yield* cycleQueries(transaction).get(ask.itemKey);
|
|
54
|
+
if (Option.isNone(budget))
|
|
55
|
+
return yield* Effect.die(new Error("Open cycle Ask has no budget"));
|
|
56
|
+
const generation = budget.value.generation + 1;
|
|
57
|
+
yield* transaction
|
|
58
|
+
.insert(events)
|
|
59
|
+
.values({
|
|
60
|
+
eventId: answer.id,
|
|
61
|
+
dedupKey: answer.dedupKey,
|
|
62
|
+
payloadJson: encodePayload(answer.payload),
|
|
63
|
+
receivedAt: encodeDateTime(answer.receivedAt),
|
|
64
|
+
})
|
|
65
|
+
.execute();
|
|
66
|
+
yield* transaction
|
|
67
|
+
.insert(cycleResets)
|
|
68
|
+
.values({ askId, eventId: answer.id, itemKey: ask.itemKey })
|
|
69
|
+
.execute();
|
|
70
|
+
yield* transaction
|
|
71
|
+
.update(itemCycleBudgets)
|
|
72
|
+
.set({ generation, askJson: null, resetPending: true })
|
|
73
|
+
.where(eq(itemCycleBudgets.itemKey, ask.itemKey))
|
|
74
|
+
.execute();
|
|
75
|
+
// Pending work retains its reservation in the new allowance. Started
|
|
76
|
+
// Runs finish against their original allowance and retries never charge again.
|
|
77
|
+
yield* transaction
|
|
78
|
+
.update(cycleReservations)
|
|
79
|
+
.set({ generation })
|
|
80
|
+
.where(and(eq(cycleReservations.itemKey, ask.itemKey), inArray(cycleReservations.runId, transaction
|
|
81
|
+
.select({ runId: runs.runId })
|
|
82
|
+
.from(runs)
|
|
83
|
+
.where(inArray(sql `json_extract(${runs.stateJson}, '$._tag')`, [
|
|
84
|
+
...RunState.UNSTARTED_STATE_TAGS,
|
|
85
|
+
])))))
|
|
86
|
+
.execute();
|
|
87
|
+
yield* Effect.logInfo("Operator reset Item cycle budget", {
|
|
88
|
+
askId,
|
|
89
|
+
eventId: answer.id,
|
|
90
|
+
itemKey: ask.itemKey,
|
|
91
|
+
repository: ask.repository,
|
|
92
|
+
generation,
|
|
93
|
+
completedCycles: ask.completedCycles,
|
|
94
|
+
counts: ask.counts,
|
|
95
|
+
});
|
|
96
|
+
}))
|
|
97
|
+
.pipe(persistence);
|
|
98
|
+
});
|
|
99
|
+
const pendingResets = Effect.fn("CycleBudgets.pendingResets")(() => queries.pendingResets().pipe(Effect.map((rows) => rows.map((row) => new CycleBudgetReset(row))), Effect.mapError((cause) => new EnginePersistenceError({ operation: "read pending cycle resets", cause }))));
|
|
100
|
+
const acknowledgeReset = Effect.fn("CycleBudgets.acknowledgeReset")(function* (reset) {
|
|
101
|
+
yield* Effect.annotateCurrentSpan({ itemKey: reset.itemKey, generation: reset.generation });
|
|
102
|
+
yield* database
|
|
103
|
+
.update(itemCycleBudgets)
|
|
104
|
+
.set({ resetPending: false })
|
|
105
|
+
.where(and(eq(itemCycleBudgets.itemKey, reset.itemKey), eq(itemCycleBudgets.generation, reset.generation)))
|
|
106
|
+
.execute()
|
|
107
|
+
.pipe(Effect.mapError((cause) => new EnginePersistenceError({ operation: "acknowledge cycle reset", cause })));
|
|
108
|
+
yield* Effect.logInfo("Cycle reset trigger refresh recorded", {
|
|
109
|
+
itemKey: reset.itemKey,
|
|
110
|
+
generation: reset.generation,
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
return { generation, asks, reset, pendingResets, acknowledgeReset };
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
export { CycleBudgetReset, CycleBudgetResetRejectedError } from "./model.js";
|
|
117
|
+
//# sourceMappingURL=cycle-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cycle-budget.js","sourceRoot":"","sources":["../../src/cycle-budget/cycle-budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CycleBudgetAsk } from "@usine/core/cycle-budget";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
declare const CycleBudgetReset_base: Schema.Class<CycleBudgetReset, Schema.Struct<{
|
|
4
|
+
readonly itemKey: Schema.brand<Schema.NonEmptyString, "ItemKey">;
|
|
5
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
6
|
+
readonly generation: Schema.brand<Schema.Int, "CycleGeneration">;
|
|
7
|
+
}>, {}>;
|
|
8
|
+
/** A reset whose Item still needs fresh trigger evaluation; acknowledgement is generation-bound. */
|
|
9
|
+
export declare class CycleBudgetReset extends CycleBudgetReset_base {
|
|
10
|
+
}
|
|
11
|
+
declare const CycleBudgetResetRejectedError_base: Schema.Class<CycleBudgetResetRejectedError, Schema.TaggedStruct<"CycleBudgetResetRejectedError", {
|
|
12
|
+
readonly askId: Schema.brand<Schema.brand<Schema.NonEmptyString, "EventId">, "CycleBudgetAskId">;
|
|
13
|
+
}>, import("effect/Cause").YieldableError>;
|
|
14
|
+
/** The requested Ask is absent, already answered differently, or no longer current. */
|
|
15
|
+
export declare class CycleBudgetResetRejectedError extends CycleBudgetResetRejectedError_base {
|
|
16
|
+
}
|
|
17
|
+
export declare const BudgetRow: Schema.Struct<{
|
|
18
|
+
readonly itemKey: Schema.brand<Schema.NonEmptyString, "ItemKey">;
|
|
19
|
+
readonly repository: Schema.brand<Schema.String, "RepositoryName">;
|
|
20
|
+
readonly generation: Schema.brand<Schema.Int, "CycleGeneration">;
|
|
21
|
+
readonly ask: Schema.OptionFromNullOr<typeof CycleBudgetAsk>;
|
|
22
|
+
readonly resetPending: Schema.Boolean;
|
|
23
|
+
}>;
|
|
24
|
+
export type BudgetRow = typeof BudgetRow.Type;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/cycle-budget/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAqC,MAAM,0BAA0B,CAAC;AAG7F,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;;;AAEhC,oGAAoG;AACpG,qBAAa,gBAAiB,SAAQ,qBAIpC;CAAG;;;;AAEL,uFAAuF;AACvF,qBAAa,6BAA8B,SAAQ,kCAEY;CAAG;AAElE,eAAO,MAAM,SAAS;;;;;;EAIpB,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CycleBudgetAsk, CycleBudgetAskId, CycleGeneration } from "@usine/core/cycle-budget";
|
|
2
|
+
import { ItemKey } from "@usine/core/item";
|
|
3
|
+
import { RepositoryName } from "@usine/core/repository";
|
|
4
|
+
import { Schema } from "effect";
|
|
5
|
+
/** A reset whose Item still needs fresh trigger evaluation; acknowledgement is generation-bound. */
|
|
6
|
+
export class CycleBudgetReset extends Schema.Class("CycleBudgetReset")({
|
|
7
|
+
itemKey: ItemKey,
|
|
8
|
+
repository: RepositoryName,
|
|
9
|
+
generation: CycleGeneration,
|
|
10
|
+
}) {
|
|
11
|
+
}
|
|
12
|
+
/** The requested Ask is absent, already answered differently, or no longer current. */
|
|
13
|
+
export class CycleBudgetResetRejectedError extends Schema.TaggedError("CycleBudgetResetRejectedError")("CycleBudgetResetRejectedError", { askId: CycleBudgetAskId }) {
|
|
14
|
+
}
|
|
15
|
+
export const BudgetRow = Schema.Struct({
|
|
16
|
+
...CycleBudgetReset.fields,
|
|
17
|
+
ask: Schema.OptionFromNullOr(CycleBudgetAsk),
|
|
18
|
+
resetPending: Schema.Boolean,
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/cycle-budget/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC7F,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,oGAAoG;AACpG,MAAM,OAAO,gBAAiB,SAAQ,MAAM,CAAC,KAAK,CAAmB,kBAAkB,CAAC,CAAC;IACvF,OAAO,EAAE,OAAO;IAChB,UAAU,EAAE,cAAc;IAC1B,UAAU,EAAE,eAAe;CAC5B,CAAC;CAAG;AAEL,uFAAuF;AACvF,MAAM,OAAO,6BAA8B,SAAQ,MAAM,CAAC,WAAW,CACnE,+BAA+B,CAChC,CAAC,+BAA+B,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAAG;AAElE,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;IACrC,GAAG,gBAAgB,CAAC,MAAM;IAC1B,GAAG,EAAE,MAAM,CAAC,gBAAgB,CAAC,cAAc,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAC,OAAO;CAC7B,CAAC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CycleBudgetAsk } from "@usine/core/cycle-budget";
|
|
2
|
+
import { Effect, Option, Schema } from "effect";
|
|
3
|
+
import type { RunDatabaseClient } from "../database/database.ts";
|
|
4
|
+
import type { Submission } from "../engine/submission.ts";
|
|
5
|
+
/** Transaction-local admission and reservation operations shared with the Run store. */
|
|
6
|
+
export declare const cycleQueries: (database: RunDatabaseClient) => {
|
|
7
|
+
admit: (submission: Submission) => Effect.Effect<Option.Option<CycleBudgetAsk>, import("drizzle-orm/effect-core").EffectDrizzleQueryError | Schema.SchemaError, never>;
|
|
8
|
+
reserve: (submission: Submission, runId: string & import("effect/Brand").Brand<"RunId">) => Effect.Effect<undefined, import("drizzle-orm/effect-core").EffectDrizzleQueryError | Schema.SchemaError, never>;
|
|
9
|
+
get: (itemKey: string & import("effect/Brand").Brand<"ItemKey">) => Effect.Effect<Option.Option<{
|
|
10
|
+
readonly itemKey: string & import("effect/Brand").Brand<"ItemKey">;
|
|
11
|
+
readonly repository: string & import("effect/Brand").Brand<"RepositoryName">;
|
|
12
|
+
readonly generation: number & import("effect/Brand").Brand<"CycleGeneration">;
|
|
13
|
+
readonly ask: Option.Option<CycleBudgetAsk>;
|
|
14
|
+
readonly resetPending: boolean;
|
|
15
|
+
}>, import("drizzle-orm/effect-core").EffectDrizzleQueryError | Schema.SchemaError, never>;
|
|
16
|
+
asks: () => Effect.Effect<CycleBudgetAsk[], import("drizzle-orm/effect-core").EffectDrizzleQueryError | Schema.SchemaError, never>;
|
|
17
|
+
pendingResets: () => Effect.Effect<readonly {
|
|
18
|
+
readonly itemKey: string & import("effect/Brand").Brand<"ItemKey">;
|
|
19
|
+
readonly repository: string & import("effect/Brand").Brand<"RepositoryName">;
|
|
20
|
+
readonly generation: number & import("effect/Brand").Brand<"CycleGeneration">;
|
|
21
|
+
readonly ask: Option.Option<CycleBudgetAsk>;
|
|
22
|
+
readonly resetPending: boolean;
|
|
23
|
+
}[], import("drizzle-orm/effect-core").EffectDrizzleQueryError | Schema.SchemaError, never>;
|
|
24
|
+
isCurrentEvent: (submission: Submission) => Effect.Effect<boolean, import("drizzle-orm/effect-core").EffectDrizzleQueryError | Schema.SchemaError, never>;
|
|
25
|
+
currentEvent: import("drizzle-orm").SQL<unknown>;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/cycle-budget/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAoC,MAAM,0BAA0B,CAAC;AAM5F,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAQjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAc1D,wFAAwF;AACxF,eAAO,MAAM,YAAY,aAAc,iBAAiB;;;;;;;;;;;;;;;;;;;;CAgLvD,CAAC"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { CycleBudgetAsk, CycleBudgetAskId, LoopCycleCount } from "@usine/core/cycle-budget";
|
|
2
|
+
import { ItemKey } from "@usine/core/item";
|
|
3
|
+
import { EventId } from "@usine/core/event";
|
|
4
|
+
import * as RunState from "@usine/core/run/state";
|
|
5
|
+
import { RunId } from "@usine/core/run";
|
|
6
|
+
import { and, asc, eq, exists, isNotNull, ne, notInArray, or, sql } from "drizzle-orm";
|
|
7
|
+
import { Effect, Option, Schema } from "effect";
|
|
8
|
+
import { attempts, cycleReservations, itemCycleBudgets, runs, triggerEvents, } from "../database/schema.js";
|
|
9
|
+
import { BudgetRow } from "./model.js";
|
|
10
|
+
const budgetSelection = {
|
|
11
|
+
itemKey: itemCycleBudgets.itemKey,
|
|
12
|
+
repository: itemCycleBudgets.repository,
|
|
13
|
+
generation: itemCycleBudgets.generation,
|
|
14
|
+
ask: itemCycleBudgets.askJson,
|
|
15
|
+
resetPending: itemCycleBudgets.resetPending,
|
|
16
|
+
};
|
|
17
|
+
const decodeBudget = Schema.decodeUnknownEffect(Schema.Array(BudgetRow), {
|
|
18
|
+
onExcessProperty: "error",
|
|
19
|
+
});
|
|
20
|
+
/** Transaction-local admission and reservation operations shared with the Run store. */
|
|
21
|
+
export const cycleQueries = (database) => {
|
|
22
|
+
// Apply the same immutable observation generation at admission and before
|
|
23
|
+
// queue ordering, so stale Events cannot hide eligible work after a reset.
|
|
24
|
+
const currentEvent = eq(triggerEvents.cycleGeneration, sql `coalesce((
|
|
25
|
+
select ${itemCycleBudgets.generation} from ${itemCycleBudgets}
|
|
26
|
+
where ${itemCycleBudgets.itemKey} = ${triggerEvents.itemKey}
|
|
27
|
+
), 0)`);
|
|
28
|
+
const get = Effect.fn("CycleBudget.get")(function* (itemKey) {
|
|
29
|
+
yield* Effect.annotateCurrentSpan({ itemKey });
|
|
30
|
+
const rows = yield* database
|
|
31
|
+
.select(budgetSelection)
|
|
32
|
+
.from(itemCycleBudgets)
|
|
33
|
+
.where(eq(itemCycleBudgets.itemKey, itemKey))
|
|
34
|
+
.execute()
|
|
35
|
+
.pipe(Effect.flatMap(decodeBudget));
|
|
36
|
+
return Option.fromNullishOr(rows[0]);
|
|
37
|
+
});
|
|
38
|
+
const counts = Effect.fn("CycleBudget.counts")(function* (budget) {
|
|
39
|
+
yield* Effect.annotateCurrentSpan({ itemKey: budget.itemKey, generation: budget.generation });
|
|
40
|
+
const started = exists(database
|
|
41
|
+
.select({ id: attempts.attemptId })
|
|
42
|
+
.from(attempts)
|
|
43
|
+
.where(eq(attempts.runId, runs.runId)));
|
|
44
|
+
const state = sql `json_extract(${runs.stateJson}, '$._tag')`;
|
|
45
|
+
const rows = yield* database
|
|
46
|
+
.select({
|
|
47
|
+
loop: cycleReservations.loop,
|
|
48
|
+
admitted: sql `count(*)`,
|
|
49
|
+
completed: sql `sum(case when ${notInArray(state, [...RunState.OPEN_STATE_TAGS])} and ${started} then 1 else 0 end)`,
|
|
50
|
+
})
|
|
51
|
+
.from(cycleReservations)
|
|
52
|
+
.innerJoin(runs, eq(runs.runId, cycleReservations.runId))
|
|
53
|
+
.where(and(eq(cycleReservations.itemKey, budget.itemKey), eq(cycleReservations.generation, budget.generation), or(ne(state, "Canceled"), started)))
|
|
54
|
+
.groupBy(cycleReservations.loop)
|
|
55
|
+
.orderBy(asc(cycleReservations.loop))
|
|
56
|
+
.execute();
|
|
57
|
+
return yield* Schema.decodeUnknownEffect(Schema.Array(LoopCycleCount))(rows, {
|
|
58
|
+
onExcessProperty: "error",
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
const admit = Effect.fn("CycleBudget.admit")(function* (submission) {
|
|
62
|
+
yield* Effect.annotateCurrentSpan({ itemKey: submission.itemKey, loop: submission.loop });
|
|
63
|
+
const current = yield* get(submission.itemKey);
|
|
64
|
+
if (Option.isSome(current) && Option.isSome(current.value.ask)) {
|
|
65
|
+
yield* Effect.logInfo("Item cycle budget remains exhausted", {
|
|
66
|
+
itemKey: submission.itemKey,
|
|
67
|
+
askId: current.value.ask.value.id,
|
|
68
|
+
loop: submission.loop,
|
|
69
|
+
});
|
|
70
|
+
return current.value.ask;
|
|
71
|
+
}
|
|
72
|
+
if (Option.isNone(submission.cycleLimit))
|
|
73
|
+
return Option.none();
|
|
74
|
+
yield* database
|
|
75
|
+
.insert(itemCycleBudgets)
|
|
76
|
+
.values({ itemKey: submission.itemKey, repository: submission.repository })
|
|
77
|
+
.onConflictDoNothing()
|
|
78
|
+
.execute();
|
|
79
|
+
const budget = yield* get(submission.itemKey);
|
|
80
|
+
if (Option.isNone(budget))
|
|
81
|
+
return yield* Effect.die(new Error("Cycle budget missing after insertion"));
|
|
82
|
+
const participants = yield* counts(budget.value);
|
|
83
|
+
const completed = participants.find(({ loop }) => loop === submission.loop);
|
|
84
|
+
if (completed === undefined || completed.admitted < submission.cycleLimit.value)
|
|
85
|
+
return Option.none();
|
|
86
|
+
const ask = new CycleBudgetAsk({
|
|
87
|
+
id: CycleBudgetAskId.make(EventId.make(JSON.stringify([submission.event.id, budget.value.generation]))),
|
|
88
|
+
itemKey: submission.itemKey,
|
|
89
|
+
repository: submission.repository,
|
|
90
|
+
loop: submission.loop,
|
|
91
|
+
limit: submission.cycleLimit.value,
|
|
92
|
+
counts: [completed, ...participants.filter(({ loop }) => loop !== submission.loop)],
|
|
93
|
+
});
|
|
94
|
+
yield* database
|
|
95
|
+
.update(itemCycleBudgets)
|
|
96
|
+
.set({ askJson: Schema.encodeSync(CycleBudgetAsk)(ask) })
|
|
97
|
+
.where(eq(itemCycleBudgets.itemKey, submission.itemKey))
|
|
98
|
+
.execute();
|
|
99
|
+
yield* Effect.logWarning("Item cycle budget exhausted; operator reset required", {
|
|
100
|
+
itemKey: submission.itemKey,
|
|
101
|
+
repository: submission.repository,
|
|
102
|
+
askId: ask.id,
|
|
103
|
+
loop: ask.loop,
|
|
104
|
+
limit: ask.limit,
|
|
105
|
+
completedCycles: ask.completedCycles,
|
|
106
|
+
counts: ask.counts,
|
|
107
|
+
});
|
|
108
|
+
return Option.some(ask);
|
|
109
|
+
});
|
|
110
|
+
const reserve = Effect.fn("CycleBudget.reserve")(function* (submission, runId) {
|
|
111
|
+
if (Option.isNone(submission.cycleLimit))
|
|
112
|
+
return;
|
|
113
|
+
yield* Effect.annotateCurrentSpan({
|
|
114
|
+
runId,
|
|
115
|
+
itemKey: submission.itemKey,
|
|
116
|
+
loop: submission.loop,
|
|
117
|
+
});
|
|
118
|
+
const budget = yield* get(submission.itemKey);
|
|
119
|
+
if (Option.isNone(budget))
|
|
120
|
+
return yield* Effect.die(new Error("Run admitted without its cycle budget"));
|
|
121
|
+
yield* database
|
|
122
|
+
.insert(cycleReservations)
|
|
123
|
+
.values({
|
|
124
|
+
runId,
|
|
125
|
+
itemKey: submission.itemKey,
|
|
126
|
+
loop: submission.loop,
|
|
127
|
+
generation: budget.value.generation,
|
|
128
|
+
})
|
|
129
|
+
.onConflictDoNothing()
|
|
130
|
+
.execute();
|
|
131
|
+
yield* Effect.logInfo("Item cycle reserved", {
|
|
132
|
+
runId,
|
|
133
|
+
itemKey: submission.itemKey,
|
|
134
|
+
loop: submission.loop,
|
|
135
|
+
generation: budget.value.generation,
|
|
136
|
+
counts: yield* counts(budget.value),
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
const asks = Effect.fn("CycleBudget.asks")(function* () {
|
|
140
|
+
const rows = yield* database
|
|
141
|
+
.select(budgetSelection)
|
|
142
|
+
.from(itemCycleBudgets)
|
|
143
|
+
.where(isNotNull(itemCycleBudgets.askJson))
|
|
144
|
+
.execute()
|
|
145
|
+
.pipe(Effect.flatMap(decodeBudget));
|
|
146
|
+
return rows.flatMap(({ ask }) => Option.toArray(ask));
|
|
147
|
+
});
|
|
148
|
+
const pendingResets = Effect.fn("CycleBudget.pendingResets")(function* () {
|
|
149
|
+
return yield* database
|
|
150
|
+
.select(budgetSelection)
|
|
151
|
+
.from(itemCycleBudgets)
|
|
152
|
+
.where(eq(itemCycleBudgets.resetPending, true))
|
|
153
|
+
.execute()
|
|
154
|
+
.pipe(Effect.flatMap(decodeBudget));
|
|
155
|
+
});
|
|
156
|
+
const isCurrentEvent = Effect.fn("CycleBudget.isCurrentEvent")(function* (submission) {
|
|
157
|
+
yield* Effect.annotateCurrentSpan({
|
|
158
|
+
eventId: submission.event.id,
|
|
159
|
+
itemKey: submission.itemKey,
|
|
160
|
+
});
|
|
161
|
+
const budget = yield* get(submission.itemKey);
|
|
162
|
+
const generation = Option.match(budget, { onNone: () => 0, onSome: (row) => row.generation });
|
|
163
|
+
const current = submission.cycleGeneration === generation;
|
|
164
|
+
if (!current)
|
|
165
|
+
yield* Effect.logInfo("Event observation was superseded by a cycle reset", {
|
|
166
|
+
eventId: submission.event.id,
|
|
167
|
+
itemKey: submission.itemKey,
|
|
168
|
+
observedGeneration: submission.cycleGeneration,
|
|
169
|
+
generation,
|
|
170
|
+
});
|
|
171
|
+
return current;
|
|
172
|
+
});
|
|
173
|
+
return { admit, reserve, get, asks, pendingResets, isCurrentEvent, currentEvent };
|
|
174
|
+
};
|
|
175
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/cycle-budget/queries.ts"],"names":[],"mappings":"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"}
|