@cosmicdrift/kumiko-framework 0.326.0 → 0.327.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/package.json +4 -4
- package/src/api/__tests__/auth-middleware-transport.test.ts +23 -1
- package/src/api/__tests__/client-ip.test.ts +20 -0
- package/src/api/__tests__/origin-middleware.test.ts +65 -0
- package/src/api/__tests__/websocket-route.test.ts +552 -0
- package/src/api/auth-middleware.ts +29 -7
- package/src/api/client-ip.ts +15 -2
- package/src/api/index.ts +24 -1
- package/src/api/jwt.ts +5 -0
- package/src/api/origin-middleware.ts +27 -1
- package/src/api/server.ts +161 -2
- package/src/api/websocket-route.ts +327 -0
- package/src/bun-db/query.ts +7 -5
- package/src/changes.json +18 -0
- package/src/crypto/__tests__/system-event-pii.test.ts +77 -0
- package/src/crypto/event-pii.ts +20 -4
- package/src/crypto/system-event-pii.ts +57 -0
- package/src/db/__tests__/apply-entity-event-removed-column.integration.test.ts +109 -0
- package/src/engine/__tests__/describe-workflow-step-error.test.ts +17 -0
- package/src/engine/__tests__/steps-mail-send.test.ts +6 -5
- package/src/engine/__tests__/steps-webhook-send.test.ts +16 -8
- package/src/engine/define-feature.ts +1 -0
- package/src/engine/feature-builder-state.ts +3 -0
- package/src/engine/feature-ui-extensions.ts +52 -0
- package/src/engine/index.ts +5 -0
- package/src/engine/steps/describe-workflow-step-error.ts +6 -0
- package/src/engine/steps/mail-send.ts +5 -1
- package/src/engine/steps/retry.ts +13 -1
- package/src/engine/steps/webhook-send.ts +6 -16
- package/src/engine/types/index.ts +7 -0
- package/src/event-store/provenance-append.ts +1 -1
- package/src/event-store/transfer.ts +2 -2
- package/src/jobs/__tests__/jobs.integration.test.ts +31 -0
- package/src/jobs/job-runner.ts +18 -0
- package/src/pipeline/append-event-core.ts +1 -8
- package/src/pipeline/irreversible-operation-gate.ts +4 -3
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// commits it together with the root row's ownership write — see
|
|
9
9
|
// move-entity-graph.ts's file header for why that matters).
|
|
10
10
|
|
|
11
|
+
import { AGGREGATE_TRANSFERRED_EVENT_TYPE } from "../crypto/system-event-pii";
|
|
11
12
|
import type { DbRunner } from "../db";
|
|
12
13
|
import {
|
|
13
14
|
deleteSnapshotsForAggregates,
|
|
@@ -15,12 +16,11 @@ import {
|
|
|
15
16
|
transferEventRows,
|
|
16
17
|
} from "../db/queries/event-store-transfer";
|
|
17
18
|
import type { TenantId } from "../engine/types";
|
|
18
|
-
import { SYSTEM_EVENT_PREFIX } from "../pipeline/append-event-core";
|
|
19
19
|
import { generateId } from "../utils/ids";
|
|
20
20
|
import { append } from "./event-store";
|
|
21
21
|
|
|
22
22
|
export const AGGREGATE_TRANSFER_STREAM_TYPE = "aggregate-transfer";
|
|
23
|
-
export
|
|
23
|
+
export { AGGREGATE_TRANSFERRED_EVENT_TYPE };
|
|
24
24
|
|
|
25
25
|
export type TransferAggregateStreamsArgs = {
|
|
26
26
|
readonly sourceTenantId: TenantId;
|
|
@@ -1530,3 +1530,34 @@ describe("boot gates", () => {
|
|
|
1530
1530
|
expect(() => createRegistry([validStringForm])).not.toThrow();
|
|
1531
1531
|
});
|
|
1532
1532
|
});
|
|
1533
|
+
|
|
1534
|
+
describe("runner lifecycle: immediate stop (fw#1805)", () => {
|
|
1535
|
+
test("stop() right after construction leaves no unhandled rejection", async () => {
|
|
1536
|
+
const unhandled: unknown[] = [];
|
|
1537
|
+
const onUnhandled = (reason: unknown) => {
|
|
1538
|
+
unhandled.push(reason);
|
|
1539
|
+
};
|
|
1540
|
+
process.on("unhandledRejection", onUnhandled);
|
|
1541
|
+
try {
|
|
1542
|
+
// @timeout-exception: #1805 the 0-4ms delay is the variable under test (stop during connect), nothing to poll for
|
|
1543
|
+
for (let i = 0; i < 20; i++) {
|
|
1544
|
+
const runner = createJobRunner({
|
|
1545
|
+
registry: createRegistry([testFeature]),
|
|
1546
|
+
context: {},
|
|
1547
|
+
redisUrl,
|
|
1548
|
+
consumerLane: "worker",
|
|
1549
|
+
queueNamePrefix: `kumiko-test-stop-${Date.now()}-${i}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1550
|
+
});
|
|
1551
|
+
// Deterministic 0-4ms delays hit the window where the Queue
|
|
1552
|
+
// connection is still 'initializing'.
|
|
1553
|
+
await sleep(i % 5);
|
|
1554
|
+
await runner.stop();
|
|
1555
|
+
}
|
|
1556
|
+
// Rejections surface on a later tick.
|
|
1557
|
+
await sleep(200);
|
|
1558
|
+
} finally {
|
|
1559
|
+
process.off("unhandledRejection", onUnhandled);
|
|
1560
|
+
}
|
|
1561
|
+
expect(unhandled).toEqual([]);
|
|
1562
|
+
});
|
|
1563
|
+
});
|
package/src/jobs/job-runner.ts
CHANGED
|
@@ -1181,6 +1181,24 @@ export function createJobRunner(options: JobRunnerOptions): JobRunner {
|
|
|
1181
1181
|
await worker.close();
|
|
1182
1182
|
worker = null;
|
|
1183
1183
|
}
|
|
1184
|
+
// BullMQ close() while a Queue's connection is still 'initializing'
|
|
1185
|
+
// rejects the in-flight INFO with "Connection is closed." on an emitter
|
|
1186
|
+
// without listeners -> unhandled rejection (fw#1805). Wait for readiness
|
|
1187
|
+
// first; a failed/timed-out wait must not block the close itself.
|
|
1188
|
+
const readinessTimeout = timeoutReject(
|
|
1189
|
+
bootRedisTimeoutMs,
|
|
1190
|
+
`job-runner: Queue not ready within ${bootRedisTimeoutMs}ms during stop()`,
|
|
1191
|
+
);
|
|
1192
|
+
try {
|
|
1193
|
+
await Promise.race([
|
|
1194
|
+
Promise.all([queues.api.waitUntilReady(), queues.worker.waitUntilReady()]),
|
|
1195
|
+
readinessTimeout.promise,
|
|
1196
|
+
]);
|
|
1197
|
+
} catch {
|
|
1198
|
+
// close() below still has to run
|
|
1199
|
+
} finally {
|
|
1200
|
+
readinessTimeout.cancel();
|
|
1201
|
+
}
|
|
1184
1202
|
await Promise.all([queues.api.close(), queues.worker.close()]);
|
|
1185
1203
|
if (lockRedis) {
|
|
1186
1204
|
// quit() drains in-flight commands; disconnect() would cancel them
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { requestContext } from "../api/request-context";
|
|
2
|
+
import { SYSTEM_EVENT_PREFIX } from "../crypto/system-event-pii";
|
|
2
3
|
import type { DbRunner } from "../db/connection";
|
|
3
4
|
import { qnScope, toKebab } from "../engine/qualified-name";
|
|
4
5
|
import type { AppendEventArgs, Registry, TenantId } from "../engine/types";
|
|
@@ -32,14 +33,6 @@ export type AppendDomainEventCoreDeps = {
|
|
|
32
33
|
readonly callerFeature?: string;
|
|
33
34
|
};
|
|
34
35
|
|
|
35
|
-
// System-event prefix: events under this namespace bypass the registry +
|
|
36
|
-
// ownership checks. Reserved for framework-internal coordination (step-
|
|
37
|
-
// engine deferred dispatch, lifecycle signals). The matching MSP filters
|
|
38
|
-
// on the literal type-string. Apps cannot write into "kumiko:system:*"
|
|
39
|
-
// directly — only framework step implementations call unsafeAppendEvent
|
|
40
|
-
// with these types.
|
|
41
|
-
export const SYSTEM_EVENT_PREFIX = "kumiko:system:";
|
|
42
|
-
|
|
43
36
|
export async function appendDomainEventCore(
|
|
44
37
|
deps: AppendDomainEventCoreDeps,
|
|
45
38
|
args: AppendEventArgs,
|
|
@@ -15,10 +15,11 @@ export function isIrreversibleEntityVerb(
|
|
|
15
15
|
|
|
16
16
|
// The gate reads the directly dispatched entry handler; a write dispatched inside a deferred
|
|
17
17
|
// context (afterCommit hook, job, consumer) gets its own entryHandler and is gated too. Direct
|
|
18
|
-
// executor/KMS calls without one stay ungated in exactly
|
|
18
|
+
// executor/KMS calls without one stay ungated in exactly three cases: deleting derived data
|
|
19
19
|
// rebuildable from a reversible source (document-ingest-foundation's forget-extract-with-file-ref
|
|
20
|
-
// consumer, re-created on fileRef.restored),
|
|
21
|
-
// cron scan (forget cleanup, retention, tenant destroy)
|
|
20
|
+
// consumer, re-created on fileRef.restored), pipelines started by a risk "high" handler or a
|
|
21
|
+
// cron scan (forget cleanup, retention, tenant destroy), and the step-dispatcher erasing the
|
|
22
|
+
// per-dispatch record key once the outcome is recorded. A deferred context can't return the
|
|
22
23
|
// reason to the agent synchronously, so the gate sits at the primitives.
|
|
23
24
|
function assertEntryHandlerHighRisk(args: {
|
|
24
25
|
reason: FrameworkReason;
|