@smthrs/control 1.0.0-rc.3 → 1.0.0-rc.4
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/cjs/ControlLive.d.ts.map +1 -1
- package/dist/cjs/ControlLive.js +21 -8
- package/dist/cjs/ControlLive.js.map +2 -2
- package/dist/cjs/ControlRuntime.d.ts.map +1 -1
- package/dist/cjs/ControlRuntime.js +64 -60
- package/dist/cjs/ControlRuntime.js.map +2 -2
- package/dist/cjs/SqlControlRuntime.d.ts.map +1 -1
- package/dist/cjs/SqlControlRuntime.js +3 -2
- package/dist/cjs/SqlControlRuntime.js.map +2 -2
- package/dist/esm/ControlLive.d.ts.map +1 -1
- package/dist/esm/ControlLive.js +5 -1
- package/dist/esm/ControlLive.js.map +1 -1
- package/dist/esm/ControlRuntime.d.ts.map +1 -1
- package/dist/esm/ControlRuntime.js +2 -1
- package/dist/esm/ControlRuntime.js.map +1 -1
- package/dist/esm/SqlControlRuntime.d.ts.map +1 -1
- package/dist/esm/SqlControlRuntime.js +2 -2
- package/dist/esm/SqlControlRuntime.js.map +1 -1
- package/docs/guides/postgres-tests.md +2 -0
- package/package.json +17 -17
- package/src/ControlLive.ts +22 -8
- package/src/ControlRuntime.ts +66 -62
- package/src/SqlControlRuntime.ts +4 -2
|
@@ -3,3 +3,5 @@
|
|
|
3
3
|
The default unit suite needs no PostgreSQL server. Set `SMITHERS_TEST_PG_URL` to run `test/SqlControlRuntimePostgres.test.ts` against a dedicated test database.
|
|
4
4
|
|
|
5
5
|
`//packages/smithers/control:postgresInventory` runs the same assertions on Linux with the `adapterPostgresDatabase` service and loopback networking. It checks filtered inventory pagination, duplicate and absent IDs, empty filters, large ID sets, and time boundaries.
|
|
6
|
+
|
|
7
|
+
The package `test` target runs the suite twice, on SQLite and on a real PostgreSQL server, and merges the coverage before it checks the 100% floors. The PostgreSQL pass runs this file, so the PostgreSQL-only inventory filter counts toward coverage. Run the same matrix from the package directory with `pnpm run test:matrix`.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smthrs/control",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.4",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"smthrs": {
|
|
7
7
|
"group": "agent"
|
|
@@ -356,27 +356,27 @@
|
|
|
356
356
|
"tag": "next"
|
|
357
357
|
},
|
|
358
358
|
"dependencies": {
|
|
359
|
-
"@smthrs/model": "1.0.0-rc.
|
|
360
|
-
"@smthrs/canonical": "1.0.0-rc.
|
|
361
|
-
"@smthrs/crypto": "1.0.0-rc.
|
|
362
|
-
"@smthrs/database": "1.0.0-rc.
|
|
363
|
-
"@smthrs/journal": "1.0.0-rc.
|
|
364
|
-
"@smthrs/notifications": "1.0.0-rc.
|
|
365
|
-
"@smthrs/plan": "1.0.0-rc.
|
|
366
|
-
"@smthrs/registry": "1.0.0-rc.
|
|
367
|
-
"@smthrs/run-store": "1.0.0-rc.
|
|
368
|
-
"@smthrs/flow": "1.0.0-rc.
|
|
359
|
+
"@smthrs/model": "1.0.0-rc.4",
|
|
360
|
+
"@smthrs/canonical": "1.0.0-rc.4",
|
|
361
|
+
"@smthrs/crypto": "1.0.0-rc.4",
|
|
362
|
+
"@smthrs/database": "1.0.0-rc.4",
|
|
363
|
+
"@smthrs/journal": "1.0.0-rc.4",
|
|
364
|
+
"@smthrs/notifications": "1.0.0-rc.4",
|
|
365
|
+
"@smthrs/plan": "1.0.0-rc.4",
|
|
366
|
+
"@smthrs/registry": "1.0.0-rc.4",
|
|
367
|
+
"@smthrs/run-store": "1.0.0-rc.4",
|
|
368
|
+
"@smthrs/flow": "1.0.0-rc.4"
|
|
369
369
|
},
|
|
370
370
|
"devDependencies": {
|
|
371
371
|
"@effect/language-service": "0.87.2",
|
|
372
372
|
"@effect/vitest": "4.0.0-rc.115",
|
|
373
373
|
"@eslint/js": "9.39.5",
|
|
374
|
-
"@smthrs/core": "1.0.0-rc.
|
|
375
|
-
"@smthrs/engine-store": "1.0.0-rc.
|
|
376
|
-
"@smthrs/jj": "1.0.0-rc.
|
|
377
|
-
"@smthrs/plan-store": "1.0.0-rc.
|
|
378
|
-
"@smthrs/step-cache": "1.0.0-rc.
|
|
379
|
-
"@smthrs/time-travel": "1.0.0-rc.
|
|
374
|
+
"@smthrs/core": "1.0.0-rc.4",
|
|
375
|
+
"@smthrs/engine-store": "1.0.0-rc.4",
|
|
376
|
+
"@smthrs/jj": "1.0.0-rc.4",
|
|
377
|
+
"@smthrs/plan-store": "1.0.0-rc.4",
|
|
378
|
+
"@smthrs/step-cache": "1.0.0-rc.4",
|
|
379
|
+
"@smthrs/time-travel": "1.0.0-rc.4",
|
|
380
380
|
"@types/node": "26.4.1",
|
|
381
381
|
"@vitest/coverage-v8": "5.0.0",
|
|
382
382
|
"dprint": "0.57.1",
|
package/src/ControlLive.ts
CHANGED
|
@@ -207,7 +207,11 @@ const AttributedResumeInput = Schema.Struct({
|
|
|
207
207
|
principal: Schema.optional(Principal)
|
|
208
208
|
})
|
|
209
209
|
const AttributedRunInput = Schema.Union([
|
|
210
|
-
Schema.Struct({
|
|
210
|
+
Schema.Struct({
|
|
211
|
+
...RunInputSchema.members[0].fields,
|
|
212
|
+
principal: Schema.optional(Principal),
|
|
213
|
+
reservedRunId: Schema.optional(Schema.NonEmptyString)
|
|
214
|
+
}),
|
|
211
215
|
Schema.Struct({ ...RunInputSchema.members[1].fields, principal: Schema.optional(Principal) })
|
|
212
216
|
])
|
|
213
217
|
const AttributedSignalInput = Schema.Struct({
|
|
@@ -995,12 +999,16 @@ export const layer: Layer.Layer<
|
|
|
995
999
|
// A detached live host keeps its fence. Delegate the admitted event
|
|
996
1000
|
// wake to it, just as a node decision does, instead of leaving an idle
|
|
997
1001
|
// retained module with input it will never get a boundary to drain.
|
|
998
|
-
Effect.catchTag(
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1002
|
+
Effect.catchTag(
|
|
1003
|
+
"/control/ClaimLost",
|
|
1004
|
+
(refusal) =>
|
|
1005
|
+
refusal.parkedBy === undefined ? Effect.void : Effect.gen(function*() {
|
|
1006
|
+
const delegated = yield* runtime.requestResume(run.runId).pipe(
|
|
1007
|
+
Effect.catchTag("/control/InvalidInput", () => Effect.succeed(undefined))
|
|
1008
|
+
)
|
|
1009
|
+
if (delegated !== undefined) yield* emit(run.runId, "control.run.resumed", { runId: run.runId })
|
|
1010
|
+
})
|
|
1011
|
+
),
|
|
1004
1012
|
Effect.catchTag("/control/RunNotFound", () => Effect.void),
|
|
1005
1013
|
Effect.catchTag(
|
|
1006
1014
|
"/control/CodeDrift",
|
|
@@ -1763,7 +1771,13 @@ export const layer: Layer.Layer<
|
|
|
1763
1771
|
principal,
|
|
1764
1772
|
fingerprint("run", principal, input),
|
|
1765
1773
|
Effect.gen(function*() {
|
|
1766
|
-
const launched = yield* runtime.launch(
|
|
1774
|
+
const launched = yield* runtime.launch(
|
|
1775
|
+
input.planId,
|
|
1776
|
+
input.digest,
|
|
1777
|
+
input.envelope,
|
|
1778
|
+
principal,
|
|
1779
|
+
input.reservedRunId
|
|
1780
|
+
)
|
|
1767
1781
|
if (launched._tag === "Parked") {
|
|
1768
1782
|
return { ...launched.receipt, receiptId: input.idempotencyKey }
|
|
1769
1783
|
}
|
package/src/ControlRuntime.ts
CHANGED
|
@@ -1182,73 +1182,77 @@ export const layerMemory = (options: MemoryOptions = {}): Layer.Layer<ControlRun
|
|
|
1182
1182
|
}
|
|
1183
1183
|
}
|
|
1184
1184
|
),
|
|
1185
|
-
launch: Effect.fn("ControlRuntime.launch")(
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1185
|
+
launch: Effect.fn("ControlRuntime.launch")(
|
|
1186
|
+
function*(planId, requestedDigest, envelope, principal, reservedRunId) {
|
|
1187
|
+
const plan = yield* Effect.fromOption(
|
|
1188
|
+
Option.fromNullishOr(plans.get(planId)),
|
|
1189
|
+
() => new PlanNotFound({ planId })
|
|
1190
|
+
)
|
|
1191
|
+
if (plan.card.digest !== requestedDigest) {
|
|
1192
|
+
return yield* new PlanDigestMismatch({
|
|
1193
|
+
planId,
|
|
1194
|
+
expected: plan.card.digest,
|
|
1195
|
+
actual: requestedDigest
|
|
1196
|
+
})
|
|
1197
|
+
}
|
|
1198
|
+
if (!sameEnvelope(plan.card.envelope, envelope)) {
|
|
1199
|
+
return yield* new EnvelopeMismatch({
|
|
1200
|
+
planId,
|
|
1201
|
+
expected: canonical(plan.card.envelope),
|
|
1202
|
+
actual: canonical(envelope)
|
|
1203
|
+
})
|
|
1204
|
+
}
|
|
1205
|
+
if (plan.decision === "pending") {
|
|
1206
|
+
return {
|
|
1207
|
+
_tag: "Parked",
|
|
1208
|
+
receipt: {
|
|
1209
|
+
_tag: "Parked",
|
|
1210
|
+
receiptId: `launch:${planId}`,
|
|
1211
|
+
planId,
|
|
1212
|
+
status: "waiting-approval"
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (plan.decision !== "approved") {
|
|
1217
|
+
return yield* new PlanDenied({ planId })
|
|
1218
|
+
}
|
|
1219
|
+
const sequence = ++runSequence
|
|
1220
|
+
const runId = reservedRunId ?? `run-${sequence}`
|
|
1221
|
+
if (runs.has(runId)) {
|
|
1222
|
+
return yield* new PersistenceError({ operation: "reserve a run", message: "Run identity already exists" })
|
|
1223
|
+
}
|
|
1224
|
+
const fence = `fence-${++fenceSequence}`
|
|
1225
|
+
const timestamp = now()
|
|
1226
|
+
const summary: RunSummary = {
|
|
1227
|
+
runId,
|
|
1228
|
+
flowId: plan.card.flowId,
|
|
1229
|
+
status: "accepted",
|
|
1199
1230
|
planId,
|
|
1200
|
-
|
|
1201
|
-
|
|
1231
|
+
planDigest: plan.card.digest,
|
|
1232
|
+
...(plan.card.executionDigest === undefined ? {} : { executionDigest: plan.card.executionDigest }),
|
|
1233
|
+
...(options.engineVersion === undefined ? {} : { engineVersion: options.engineVersion }),
|
|
1234
|
+
ownerId: "memory-owner",
|
|
1235
|
+
...(principal === undefined ? {} : { launchedBy: { id: principal.id, kind: principal.kind } }),
|
|
1236
|
+
...(plan.card.envelope.budget.deadline === undefined
|
|
1237
|
+
? {}
|
|
1238
|
+
: { deadlineAt: timestamp + plan.card.envelope.budget.deadline }),
|
|
1239
|
+
createdAt: timestamp,
|
|
1240
|
+
updatedAt: timestamp
|
|
1241
|
+
}
|
|
1242
|
+
runs.set(runId, {
|
|
1243
|
+
summary: snapshot(summary),
|
|
1244
|
+
fence,
|
|
1245
|
+
localFence: fence,
|
|
1246
|
+
sequence: runSequence,
|
|
1247
|
+
signals: []
|
|
1202
1248
|
})
|
|
1203
|
-
}
|
|
1204
|
-
if (plan.decision === "pending") {
|
|
1205
1249
|
return {
|
|
1206
|
-
_tag: "
|
|
1207
|
-
receipt: {
|
|
1208
|
-
|
|
1209
|
-
receiptId: `launch:${planId}`,
|
|
1210
|
-
planId,
|
|
1211
|
-
status: "waiting-approval"
|
|
1212
|
-
}
|
|
1250
|
+
_tag: "Started",
|
|
1251
|
+
receipt: accepted(`launch:${planId}:${runId}`, runId),
|
|
1252
|
+
run: snapshot(summary)
|
|
1213
1253
|
}
|
|
1214
1254
|
}
|
|
1215
|
-
|
|
1216
|
-
return yield* new PlanDenied({ planId })
|
|
1217
|
-
}
|
|
1218
|
-
const sequence = ++runSequence
|
|
1219
|
-
const runId = reservedRunId ?? `run-${sequence}`
|
|
1220
|
-
if (runs.has(runId)) return yield* new PersistenceError({ operation: "reserve a run", message: "Run identity already exists" })
|
|
1221
|
-
const fence = `fence-${++fenceSequence}`
|
|
1222
|
-
const timestamp = now()
|
|
1223
|
-
const summary: RunSummary = {
|
|
1224
|
-
runId,
|
|
1225
|
-
flowId: plan.card.flowId,
|
|
1226
|
-
status: "accepted",
|
|
1227
|
-
planId,
|
|
1228
|
-
planDigest: plan.card.digest,
|
|
1229
|
-
...(plan.card.executionDigest === undefined ? {} : { executionDigest: plan.card.executionDigest }),
|
|
1230
|
-
...(options.engineVersion === undefined ? {} : { engineVersion: options.engineVersion }),
|
|
1231
|
-
ownerId: "memory-owner",
|
|
1232
|
-
...(principal === undefined ? {} : { launchedBy: { id: principal.id, kind: principal.kind } }),
|
|
1233
|
-
...(plan.card.envelope.budget.deadline === undefined
|
|
1234
|
-
? {}
|
|
1235
|
-
: { deadlineAt: timestamp + plan.card.envelope.budget.deadline }),
|
|
1236
|
-
createdAt: timestamp,
|
|
1237
|
-
updatedAt: timestamp
|
|
1238
|
-
}
|
|
1239
|
-
runs.set(runId, {
|
|
1240
|
-
summary: snapshot(summary),
|
|
1241
|
-
fence,
|
|
1242
|
-
localFence: fence,
|
|
1243
|
-
sequence: runSequence,
|
|
1244
|
-
signals: []
|
|
1245
|
-
})
|
|
1246
|
-
return {
|
|
1247
|
-
_tag: "Started",
|
|
1248
|
-
receipt: accepted(`launch:${planId}:${runId}`, runId),
|
|
1249
|
-
run: snapshot(summary)
|
|
1250
|
-
}
|
|
1251
|
-
}),
|
|
1255
|
+
),
|
|
1252
1256
|
getRun: Effect.fn("ControlRuntime.getRun")((runId) =>
|
|
1253
1257
|
Effect.map(requireRun(runId), (run) => snapshot(run.summary))
|
|
1254
1258
|
),
|
package/src/SqlControlRuntime.ts
CHANGED
|
@@ -692,7 +692,10 @@ const makeRuntime = (
|
|
|
692
692
|
// so the walk stops at one rather than telling a reader that a run
|
|
693
693
|
// which can proceed cannot.
|
|
694
694
|
...(pendingWaits === undefined ? {} : { pendingWaits }),
|
|
695
|
-
...(pendingWaits === undefined || !pendingWaits.some((wait) =>
|
|
695
|
+
...(pendingWaits === undefined || !pendingWaits.some((wait) =>
|
|
696
|
+
wait.reason === ControlExecutor.humanWaitReason
|
|
697
|
+
) ||
|
|
698
|
+
terminal(base.status)
|
|
696
699
|
? {}
|
|
697
700
|
: { status: "waiting-approval" as const }),
|
|
698
701
|
...(pendingResume === undefined ? {} : { pendingResume }),
|
|
@@ -1393,7 +1396,6 @@ const makeRuntime = (
|
|
|
1393
1396
|
const ids = JSON.stringify([...new Set(filters.runIds)])
|
|
1394
1397
|
conditions.push(
|
|
1395
1398
|
sql.onDialectOrElse({
|
|
1396
|
-
/* v8 ignore next -- PostgreSQL adapter; covered by //packages/smithers/control:postgresInventory */
|
|
1397
1399
|
pg: () => sql`runs.run_id IN (SELECT jsonb_array_elements_text(${ids}::jsonb))`,
|
|
1398
1400
|
orElse: () => sql`runs.run_id IN (SELECT value FROM json_each(${ids}))`
|
|
1399
1401
|
})
|