rcf-lite 0.22.0 → 0.23.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/CHANGELOG.md +20 -0
- package/bin/rcf.js +13 -2
- package/blueprints/application-account-settings/docs/topics.md +2 -0
- package/blueprints/application-admin-console/docs/topics.md +2 -0
- package/blueprints/application-api-rest/docs/topics.md +2 -0
- package/blueprints/application-charts/docs/topics.md +2 -0
- package/blueprints/application-dashboard/docs/topics.md +2 -0
- package/blueprints/application-datatable/docs/topics.md +2 -0
- package/blueprints/application-empty-error-states/docs/topics.md +2 -0
- package/blueprints/application-error-handling/docs/topics.md +2 -0
- package/blueprints/application-file-upload/docs/topics.md +2 -0
- package/blueprints/application-forms-wizard/docs/topics.md +2 -0
- package/blueprints/application-notifications-in-app/docs/topics.md +2 -0
- package/blueprints/application-onboarding-tour/docs/topics.md +2 -0
- package/blueprints/application-spa/docs/topics.md +3 -1
- package/blueprints/delivery-ci-workflows/docs/topics.md +2 -0
- package/blueprints/deploy-cloudflare-workers/CHANGELOG.md +69 -0
- package/blueprints/deploy-cloudflare-workers/README.md +12 -0
- package/blueprints/deploy-cloudflare-workers/blueprint.json +234 -36
- package/blueprints/deploy-cloudflare-workers/contributions/adrs/adr-1306-deploy-cloudflare-workers-spa-shape.json +30 -0
- package/blueprints/deploy-cloudflare-workers/contributions/probes/assets-manifest-scan.mjs +207 -0
- package/blueprints/deploy-cloudflare-workers/contributions/requirements/deploy-cloudflare-workers-req-013.json +18 -0
- package/blueprints/deploy-cloudflare-workers/contributions/requirements/deploy-cloudflare-workers-req-014.json +18 -0
- package/blueprints/deploy-cloudflare-workers/contributions/user-stories/deploy-cloudflare-workers-us-12113.json +36 -0
- package/blueprints/deploy-cloudflare-workers/contributions/user-stories/deploy-cloudflare-workers-us-12114.json +27 -0
- package/blueprints/deploy-cloudflare-workers/contributions/user-stories/deploy-cloudflare-workers-us-12115.json +27 -0
- package/blueprints/deploy-cloudflare-workers/docs/topics.md +3 -1
- package/blueprints/deploy-cloudflare-workers/guide/deploy-cloudflare-workers.md +25 -0
- package/blueprints/email-smtp-resend/docs/topics.md +2 -0
- package/blueprints/jobs-background/CHANGELOG.md +16 -0
- package/blueprints/jobs-background/README.md +105 -0
- package/blueprints/jobs-background/blueprint.json +149 -0
- package/blueprints/jobs-background/contributions/adrs/adr-3101-jobs-background-model.json +27 -0
- package/blueprints/jobs-background/contributions/adrs/adr-3102-jobs-background-scheduler-mode.json +25 -0
- package/blueprints/jobs-background/contributions/adrs/adr-3103-jobs-background-requires-applied-capabilities.json +25 -0
- package/blueprints/jobs-background/contributions/adrs/adr-3104-jobs-background-timeout-floor.json +25 -0
- package/blueprints/jobs-background/contributions/probes/apply-time-override.mjs +95 -0
- package/blueprints/jobs-background/contributions/probes/apply-time-refusal.mjs +82 -0
- package/blueprints/jobs-background/contributions/probes/event-secrecy.mjs +69 -0
- package/blueprints/jobs-background/contributions/probes/fake-clock-cron.mjs +65 -0
- package/blueprints/jobs-background/contributions/probes/probe-utils.mjs +91 -0
- package/blueprints/jobs-background/contributions/probes/retry-and-fail.mjs +65 -0
- package/blueprints/jobs-background/contributions/probes/run-apply-time-override.mjs +10 -0
- package/blueprints/jobs-background/contributions/probes/run-apply-time-refusal.mjs +11 -0
- package/blueprints/jobs-background/contributions/probes/run-event-secrecy.mjs +11 -0
- package/blueprints/jobs-background/contributions/probes/run-fake-clock-cron.mjs +11 -0
- package/blueprints/jobs-background/contributions/probes/run-retry-and-fail.mjs +11 -0
- package/blueprints/jobs-background/contributions/requirements/jobs-background-req-001.json +18 -0
- package/blueprints/jobs-background/contributions/requirements/jobs-background-req-002.json +18 -0
- package/blueprints/jobs-background/contributions/requirements/jobs-background-req-003.json +18 -0
- package/blueprints/jobs-background/contributions/requirements/jobs-background-req-004.json +18 -0
- package/blueprints/jobs-background/contributions/requirements/jobs-background-req-005.json +18 -0
- package/blueprints/jobs-background/contributions/requirements/jobs-background-req-006.json +18 -0
- package/blueprints/jobs-background/contributions/tacs/tac-3101-jobs-background-job-definition.json +50 -0
- package/blueprints/jobs-background/contributions/tacs/tac-3102-jobs-background-scheduler.json +50 -0
- package/blueprints/jobs-background/contributions/tacs/tac-3103-jobs-background-run-log.json +49 -0
- package/blueprints/jobs-background/contributions/user-stories/jobs-background-us-30101.json +25 -0
- package/blueprints/jobs-background/contributions/user-stories/jobs-background-us-30102.json +25 -0
- package/blueprints/jobs-background/contributions/user-stories/jobs-background-us-30103.json +25 -0
- package/blueprints/jobs-background/contributions/user-stories/jobs-background-us-30104.json +25 -0
- package/blueprints/jobs-background/contributions/user-stories/jobs-background-us-30105.json +25 -0
- package/blueprints/jobs-background/contributions/user-stories/jobs-background-us-30106.json +25 -0
- package/blueprints/jobs-background/contributions/user-stories/jobs-background-us-30107.json +25 -0
- package/blueprints/jobs-background/contributions/user-stories/jobs-background-us-30108.json +25 -0
- package/blueprints/jobs-background/contributions/user-stories/jobs-background-us-30109.json +25 -0
- package/blueprints/jobs-background/docs/topics.md +62 -0
- package/blueprints/jobs-background/guide/jobs-background.md +147 -0
- package/blueprints/messaging-queue-cloudflare/CHANGELOG.md +15 -0
- package/blueprints/messaging-queue-cloudflare/README.md +125 -0
- package/blueprints/messaging-queue-cloudflare/blueprint.json +67 -0
- package/blueprints/messaging-queue-cloudflare/contributions/adrs/adr-3001-messaging-queue-cloudflare-adapter.json +30 -0
- package/blueprints/messaging-queue-cloudflare/contributions/adrs/adr-3002-messaging-queue-cloudflare-delivery-semantics.json +25 -0
- package/blueprints/messaging-queue-cloudflare/contributions/adrs/adr-3003-messaging-queue-cloudflare-max-attempts-floor.json +25 -0
- package/blueprints/messaging-queue-cloudflare/contributions/adrs/adr-3004-messaging-queue-cloudflare-batch-defaults.json +25 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/event-secrecy.mjs +137 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/probe-utils.mjs +82 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/producer-facade-ready.mjs +35 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/publish-to-delivery.mjs +94 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/real-account-concurrency-smoke.mjs +54 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/retry-and-dlq.mjs +87 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/run-event-secrecy.mjs +5 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/run-producer-facade-ready.mjs +11 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/run-publish-to-delivery.mjs +5 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/run-real-account-concurrency-smoke.mjs +5 -0
- package/blueprints/messaging-queue-cloudflare/contributions/probes/run-retry-and-dlq.mjs +5 -0
- package/blueprints/messaging-queue-cloudflare/contributions/requirements/messaging-queue-cloudflare-req-001.json +18 -0
- package/blueprints/messaging-queue-cloudflare/contributions/requirements/messaging-queue-cloudflare-req-002.json +18 -0
- package/blueprints/messaging-queue-cloudflare/contributions/requirements/messaging-queue-cloudflare-req-003.json +18 -0
- package/blueprints/messaging-queue-cloudflare/contributions/requirements/messaging-queue-cloudflare-req-004.json +18 -0
- package/blueprints/messaging-queue-cloudflare/contributions/requirements/messaging-queue-cloudflare-req-005.json +18 -0
- package/blueprints/messaging-queue-cloudflare/contributions/requirements/messaging-queue-cloudflare-req-006.json +18 -0
- package/blueprints/messaging-queue-cloudflare/contributions/tacs/tac-3001-messaging-queue-cloudflare-producer-facade.json +56 -0
- package/blueprints/messaging-queue-cloudflare/contributions/tacs/tac-3002-messaging-queue-cloudflare-consumer-registration.json +40 -0
- package/blueprints/messaging-queue-cloudflare/contributions/tacs/tac-3003-messaging-queue-cloudflare-event-sink.json +39 -0
- package/blueprints/messaging-queue-cloudflare/contributions/user-stories/messaging-queue-cloudflare-us-29101.json +34 -0
- package/blueprints/messaging-queue-cloudflare/contributions/user-stories/messaging-queue-cloudflare-us-29102.json +34 -0
- package/blueprints/messaging-queue-cloudflare/contributions/user-stories/messaging-queue-cloudflare-us-29103.json +25 -0
- package/blueprints/messaging-queue-cloudflare/contributions/user-stories/messaging-queue-cloudflare-us-29104.json +25 -0
- package/blueprints/messaging-queue-cloudflare/contributions/user-stories/messaging-queue-cloudflare-us-29105.json +25 -0
- package/blueprints/messaging-queue-cloudflare/contributions/user-stories/messaging-queue-cloudflare-us-29106.json +25 -0
- package/blueprints/messaging-queue-cloudflare/contributions/user-stories/messaging-queue-cloudflare-us-29107.json +25 -0
- package/blueprints/messaging-queue-cloudflare/contributions/user-stories/messaging-queue-cloudflare-us-29108.json +25 -0
- package/blueprints/messaging-queue-cloudflare/docs/topics.md +62 -0
- package/blueprints/messaging-queue-cloudflare/guide/messaging-queue-cloudflare.md +110 -0
- package/blueprints/object-storage-s3/docs/topics.md +2 -0
- package/blueprints/observability-essentials/docs/topics.md +2 -0
- package/blueprints/observability-logging/docs/topics.md +2 -0
- package/blueprints/observability-probe-endpoints/docs/topics.md +2 -0
- package/blueprints/persistence-data-d1/docs/topics.md +2 -0
- package/blueprints/persistence-data-postgres/docs/topics.md +2 -0
- package/blueprints/persistence-data-sqlite/docs/topics.md +2 -0
- package/blueprints/security-auth-clerk/docs/topics.md +2 -0
- package/blueprints/security-auth-keycloak/docs/topics.md +2 -0
- package/blueprints/security-auth-magic-link/docs/topics.md +2 -0
- package/blueprints/security-auth-oauth2/docs/topics.md +2 -0
- package/blueprints/security-secrets-management/docs/topics.md +2 -0
- package/fixtures/canary-manifest.json +6 -6
- package/package.json +1 -1
- package/rcf/code-nodes/cn-206.json +14 -0
- package/rcf/code-nodes/cn-207.json +13 -0
- package/rcf/code-nodes/cn-208.json +14 -0
- package/rcf/code-nodes/cn-209.json +14 -0
- package/rcf/code-nodes/cn-210.json +13 -0
- package/rcf/code-nodes/cn-211.json +12 -0
- package/rcf/code-nodes/cn-212.json +12 -0
- package/rcf/code-nodes/cn-213.json +12 -0
- package/rcf/code-nodes/cn-214.json +15 -0
- package/rcf/code-nodes/cn-215.json +13 -0
- package/rcf/code-nodes/cn-216.json +14 -0
- package/rcf/code-nodes/cn-217.json +13 -0
- package/rcf/fbs/fbs-062.json +20 -0
- package/rcf/fbs/fbs-063.json +22 -0
- package/rcf/fbs/fbs-064.json +23 -0
- package/rcf/requirements/req-029.json +57 -0
- package/rcf/requirements/req-030.json +67 -0
- package/rcf/requirements/req-042.json +59 -0
- package/rcf/requirements/req-043.json +39 -0
- package/rcf/test-suites/ts-072.json +62 -0
- package/rcf/test-suites/ts-073.json +38 -0
- package/rcf/test-suites/ts-074.json +30 -0
- package/rcf/test-suites/ts-075.json +38 -0
- package/rcf/test-suites/ts-076.json +70 -0
- package/rcf/user-stories/us-2901.json +45 -0
- package/rcf/user-stories/us-2902.json +36 -0
- package/rcf/user-stories/us-3001.json +45 -0
- package/rcf/user-stories/us-4201.json +69 -0
- package/rcf/user-stories/us-4301.json +78 -0
- package/releases/releases.yaml +11 -1
- package/src/blueprint/apply.js +7 -4
- package/src/blueprint/capabilities.js +25 -0
- package/src/cli/blueprint.js +8 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for jobs-background probes.
|
|
3
|
+
*
|
|
4
|
+
* Runtime-dependency posture: probes import the fixture's in-memory
|
|
5
|
+
* queue-driver plus the T-4 jobs-runtime, scheduler and run-log from the
|
|
6
|
+
* sample-app fixture's src/ tree so rcf-lite itself gains no new runtime
|
|
7
|
+
* dependency (round-5 spec section 5, brief section 4). The T-3
|
|
8
|
+
* in-memory queue-driver realises the Cloudflare Queues binding shape;
|
|
9
|
+
* the T-4 jobs-runtime consumes messages from the driver, dispatches to
|
|
10
|
+
* job-definition modules, and fires the four lifecycle events on the
|
|
11
|
+
* T-4-owned run-log sink (whitelist { event, jobId, jobName, attempts,
|
|
12
|
+
* duration, timestamp } plus optional terminalErrorCode).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { dirname, resolve } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
|
|
21
|
+
// Project root four levels above blueprints/jobs-background/contributions/probes/
|
|
22
|
+
export const PROJECT_ROOT = resolve(HERE, '..', '..', '..', '..');
|
|
23
|
+
export const FIXTURE_DIR = resolve(PROJECT_ROOT, 'packages/rcf-lite/test/fixtures/infra-s3-and-queue');
|
|
24
|
+
export const REPORT_DIR = resolve(PROJECT_ROOT, '.rcf/reports/blueprints/jobs-background');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compute the aggregate verdict per spec section 3.4:
|
|
28
|
+
* - fail if any result carries verdict fail
|
|
29
|
+
* - warn if any result carries verdict warn and no fail
|
|
30
|
+
* - pass otherwise
|
|
31
|
+
*/
|
|
32
|
+
export function aggregate(results) {
|
|
33
|
+
if (results.some((r) => r.verdict === 'fail')) return 'fail';
|
|
34
|
+
if (results.some((r) => r.verdict === 'warn')) return 'warn';
|
|
35
|
+
return 'pass';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Write the per-blueprint probe report at
|
|
40
|
+
* .rcf/reports/blueprints/jobs-background/<probeName>.json
|
|
41
|
+
* per spec section 3.4.
|
|
42
|
+
*/
|
|
43
|
+
export async function writeReport({ probeName, engine, results, extra = {} }) {
|
|
44
|
+
await mkdir(REPORT_DIR, { recursive: true });
|
|
45
|
+
const report = {
|
|
46
|
+
slug: 'jobs-background',
|
|
47
|
+
probeName,
|
|
48
|
+
runAt: new Date().toISOString(),
|
|
49
|
+
engine,
|
|
50
|
+
results,
|
|
51
|
+
aggregateVerdict: aggregate(results),
|
|
52
|
+
...extra,
|
|
53
|
+
};
|
|
54
|
+
const path = resolve(REPORT_DIR, `${probeName}.json`);
|
|
55
|
+
await writeFile(path, JSON.stringify(report, null, 2), 'utf8');
|
|
56
|
+
return { report, path };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Drive an async main() and exit 0 on aggregate pass, 1 otherwise.
|
|
61
|
+
* Prints the report JSON to stdout for the gate-reviewer to read.
|
|
62
|
+
*/
|
|
63
|
+
export async function runShim(probeName, engine, mainFn) {
|
|
64
|
+
try {
|
|
65
|
+
const { results, extra } = normaliseMain(await mainFn());
|
|
66
|
+
const { report, path } = await writeReport({ probeName, engine, results, extra });
|
|
67
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
68
|
+
process.stdout.write(`report written to ${path}\n`);
|
|
69
|
+
process.exit(report.aggregateVerdict === 'pass' ? 0 : 1);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
const results = [{
|
|
72
|
+
anchorAcId: 'unknown',
|
|
73
|
+
verdict: 'fail',
|
|
74
|
+
detail: `probe threw: ${err && err.message ? err.message : String(err)}`,
|
|
75
|
+
}];
|
|
76
|
+
const { report, path } = await writeReport({ probeName, engine, results });
|
|
77
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
78
|
+
process.stderr.write(`probe error: ${err && err.stack ? err.stack : String(err)}\n`);
|
|
79
|
+
process.stderr.write(`report written to ${path}\n`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function normaliseMain(value) {
|
|
85
|
+
if (Array.isArray(value)) return { results: value, extra: {} };
|
|
86
|
+
if (value && Array.isArray(value.results)) {
|
|
87
|
+
const { results, ...extra } = value;
|
|
88
|
+
return { results, extra };
|
|
89
|
+
}
|
|
90
|
+
throw new Error('probe main must return an array or an object with a results[] field');
|
|
91
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry-and-fail probe.
|
|
3
|
+
*
|
|
4
|
+
* With SIMULATE_HANDLER_THROW=true on the shared fixture, the jobs-runtime
|
|
5
|
+
* throws a retryable error on every dispatch. Schedules a one-shot job
|
|
6
|
+
* with send-welcome-email's maxAttempts=3. Drives the driver's delivery
|
|
7
|
+
* loop until the message either terminals or DLQs. Asserts three
|
|
8
|
+
* jobStarted events with the same jobId and attempts 1, 2, 3 followed by
|
|
9
|
+
* a terminal jobFailed with a terminalErrorCode.
|
|
10
|
+
*
|
|
11
|
+
* Anchors AC-jobs-retryOnHandlerFailure.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createQueuePair } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/queue-driver.mjs';
|
|
15
|
+
import { createProducer, queueConfigFromEnv } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/producer.mjs';
|
|
16
|
+
import { createRunLog } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/job-run-log.mjs';
|
|
17
|
+
import { loadJobs, createJobsRuntime } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/jobs-runtime.mjs';
|
|
18
|
+
import { createScheduler, createFakeClock } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/scheduler.mjs';
|
|
19
|
+
import { PROJECT_ROOT } from './probe-utils.mjs';
|
|
20
|
+
import { resolve } from 'node:path';
|
|
21
|
+
|
|
22
|
+
export default async function runProbe() {
|
|
23
|
+
const cfg = queueConfigFromEnv();
|
|
24
|
+
// maxRetries on the driver matches send-welcome-email.maxAttempts=3.
|
|
25
|
+
const pair = createQueuePair({ queueName: cfg.queueName, dlqName: cfg.dlqName, maxRetries: 3 });
|
|
26
|
+
const events = [];
|
|
27
|
+
const runLog = createRunLog({ upstream: (e) => events.push(e) });
|
|
28
|
+
const producer = createProducer({
|
|
29
|
+
binding: pair.producer,
|
|
30
|
+
queueName: cfg.queueName,
|
|
31
|
+
onEvent: () => {},
|
|
32
|
+
});
|
|
33
|
+
await producer.ready;
|
|
34
|
+
const jobsDir = resolve(PROJECT_ROOT, 'packages/rcf-lite/test/fixtures/infra-s3-and-queue/jobs');
|
|
35
|
+
const registry = await loadJobs(jobsDir);
|
|
36
|
+
const runtime = createJobsRuntime({
|
|
37
|
+
jobRegistry: registry,
|
|
38
|
+
runLog,
|
|
39
|
+
env: { SIMULATE_HANDLER_THROW: 'true' },
|
|
40
|
+
});
|
|
41
|
+
const clock = createFakeClock(0);
|
|
42
|
+
const scheduler = createScheduler({ mode: 'inProcess', clock, publisher: producer, runLog });
|
|
43
|
+
scheduler.delayed({ jobName: 'send-welcome-email', delayMs: 0, input: { userId: 7, email: 'ok@example.com' } });
|
|
44
|
+
clock.advance(1);
|
|
45
|
+
await scheduler.tick();
|
|
46
|
+
await runtime.drain({ driverState: pair.state, maxIterations: 32 });
|
|
47
|
+
const jobStartedEvents = events.filter((e) => e.event === 'jobStarted' && e.jobName === 'send-welcome-email');
|
|
48
|
+
const jobFailed = events.find((e) => e.event === 'jobFailed' && e.jobName === 'send-welcome-email');
|
|
49
|
+
const attemptsSeq = jobStartedEvents.map((e) => e.attempts);
|
|
50
|
+
const jobIds = new Set(jobStartedEvents.map((e) => e.jobId));
|
|
51
|
+
const dlqInvoked = pair.state.dlq.length > 0;
|
|
52
|
+
const results = [];
|
|
53
|
+
const attemptsOk = attemptsSeq.length === 3 && attemptsSeq[0] === 1 && attemptsSeq[1] === 2 && attemptsSeq[2] === 3;
|
|
54
|
+
const oneJobId = jobIds.size === 1;
|
|
55
|
+
const failedOk = !!jobFailed && typeof jobFailed.terminalErrorCode === 'string';
|
|
56
|
+
const pass = attemptsOk && oneJobId && failedOk;
|
|
57
|
+
results.push({
|
|
58
|
+
anchorAcId: 'AC-jobs-retryOnHandlerFailure',
|
|
59
|
+
verdict: pass ? 'pass' : 'fail',
|
|
60
|
+
detail: pass
|
|
61
|
+
? `three jobStarted events attempts=[1,2,3] same jobId; jobFailed carries terminalErrorCode=${jobFailed.terminalErrorCode}`
|
|
62
|
+
: `attemptsSeq=${JSON.stringify(attemptsSeq)}; jobIds.size=${jobIds.size}; jobFailed=${JSON.stringify(jobFailed)}`,
|
|
63
|
+
});
|
|
64
|
+
return { results, extra: { events, dlqInvoked, attemptsSeq } };
|
|
65
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shim: run apply-time-override against a scratch bare project and
|
|
3
|
+
* write .rcf/reports/blueprints/jobs-background/apply-time-override.json.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import runProbe from './apply-time-override.mjs';
|
|
7
|
+
import { runShim } from './probe-utils.mjs';
|
|
8
|
+
|
|
9
|
+
const engine = { kind: 'cli', tool: 'rcf define blueprint add --allow-no-queue-yet (bare scratch project)', healthy: true };
|
|
10
|
+
runShim('apply-time-override', engine, runProbe);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shim: run apply-time-refusal against a scratch project (bare init, no
|
|
3
|
+
* applied blueprints) and write
|
|
4
|
+
* .rcf/reports/blueprints/jobs-background/apply-time-refusal.json.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import runProbe from './apply-time-refusal.mjs';
|
|
8
|
+
import { runShim } from './probe-utils.mjs';
|
|
9
|
+
|
|
10
|
+
const engine = { kind: 'cli', tool: 'rcf define blueprint add (bare scratch project)', healthy: true };
|
|
11
|
+
runShim('apply-time-refusal', engine, runProbe);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shim: run event-secrecy against the fixture jobs-runtime + in-memory
|
|
3
|
+
* queue seam with a PII fixture input and write
|
|
4
|
+
* .rcf/reports/blueprints/jobs-background/event-secrecy.json.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import runProbe from './event-secrecy.mjs';
|
|
8
|
+
import { runShim } from './probe-utils.mjs';
|
|
9
|
+
|
|
10
|
+
const engine = { kind: 'queues+jobs', image: 'cloudflare/wrangler-dev-equivalent (in-memory driver) + jobs-runtime SIMULATE_PII_IN_JOB_INPUT=true', healthy: true };
|
|
11
|
+
runShim('event-secrecy', engine, runProbe);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shim: run fake-clock-cron against the fixture's in-memory queue seam +
|
|
3
|
+
* fake-clock scheduler + jobs-runtime and write
|
|
4
|
+
* .rcf/reports/blueprints/jobs-background/fake-clock-cron.json.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import runProbe from './fake-clock-cron.mjs';
|
|
8
|
+
import { runShim } from './probe-utils.mjs';
|
|
9
|
+
|
|
10
|
+
const engine = { kind: 'queues+scheduler', image: 'cloudflare/wrangler-dev-equivalent (in-memory driver) + inProcess scheduler with injected fake clock', healthy: true };
|
|
11
|
+
runShim('fake-clock-cron', engine, runProbe);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shim: run retry-and-fail against the fixture jobs-runtime + in-memory
|
|
3
|
+
* queue seam and write
|
|
4
|
+
* .rcf/reports/blueprints/jobs-background/retry-and-fail.json.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import runProbe from './retry-and-fail.mjs';
|
|
8
|
+
import { runShim } from './probe-utils.mjs';
|
|
9
|
+
|
|
10
|
+
const engine = { kind: 'queues+jobs', image: 'cloudflare/wrangler-dev-equivalent (in-memory driver) + jobs-runtime SIMULATE_HANDLER_THROW=true', healthy: true };
|
|
11
|
+
runShim('retry-and-fail', engine, runProbe);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "jobs-background-REQ-001",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Requires an applied queue capability; refuses apply on a bare project with exit 3 and the stable message id jobs-background-no-queue",
|
|
5
|
+
"description": "The jobs-background blueprint composes on an applied queue capability via the T-5 mechanism from the visual round. The apply verb refuses on a bare project (no applied blueprint declares capabilities: [queue]) with exit code 3 and a stable, dashboard-friendly message id jobs-background-no-queue on the first stderr line. The refusal message also names the shipped provider messaging-queue-cloudflare and the override flag --allow-no-queue-yet explicitly, per infra round 5 spec section 10 Q3 default asserting BOTH exit code AND stable message id. The override flag records a note on the sidecar rcf/blueprints/jobs-background.applied.json under the queue family so rcf define validate later flags surfaces gated on queue that never activated.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "jobs",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "A stable message id is the operator-facing surface an autolint or dashboard binds to; asserting it in the probe pins the id against silent drift. The override flag lets a project scaffold without the queue applied yet, provided the note lands so a later validation pass can flag the gap. The queue provider is named explicitly so an operator hitting the refusal has the shipped answer in hand without a shelf-wide grep.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:jobs-background",
|
|
12
|
+
"category:00-composition"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "jobs-background-REQ-002",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Job-definition contract: name, handler, input schema (opaque per project), retry policy, timeout",
|
|
5
|
+
"description": "A job definition is a module in the applying project's jobs directory (default ./jobs/, elicited at apply time) exporting a shape { name, handler, inputSchema, retryPolicy, timeoutMs }. Name is a stable string; handler is an async function receiving the message body and returning a value or throwing; inputSchema is opaque per project (the applying project's own validation library); retryPolicy is { maxAttempts, backoff }; timeoutMs is a positive integer (default 60000, floor 1000, ceiling bounded by the applied queue consumer wall-clock ceiling per ADR-3104). The jobs runtime registers every job at boot on the applied queue consumer and dispatches by name.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "jobs",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "A one-shape job definition keeps the discipline uniform across the applying project's job modules; the applying project owns input validation without the blueprint prescribing a validation library.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:jobs-background",
|
|
12
|
+
"category:01-definition"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "jobs-background-REQ-003",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Scheduler contract: POSIX cron string and one-shot delayed",
|
|
5
|
+
"description": "The scheduler surface accepts two schedule shapes: a POSIX cron string (five fields, per POSIX cron) attached to a job definition for recurring runs, and a one-shot delayed schedule with an ISO-8601 fireAt or a positive delayMs. The scheduler publishes a message to the applied queue at the elicited fire time; the jobs runtime consumes and dispatches. The scheduler mode is elicited at apply time under ADR-3102 (inProcess, workerCron, external) and a reserved workflows value ships in the v1.1.0 minor per section 5.7 of the spec.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "jobs",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "POSIX cron is the widely understood recurring surface; one-shot delayed covers the deferred-send / retry-later case without a second contract. The elicited mode keeps the shipped facade portable across Node long-lived processes, Cloudflare Cron Triggers, and external cron runners without a facade re-shape.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:jobs-background",
|
|
12
|
+
"category:02-scheduler"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "jobs-background-REQ-004",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Retry contract: handler throws retryable error re-delivers via the applied queue; max-attempts elicited within the applied queue's own ceiling",
|
|
5
|
+
"description": "A handler that throws a retryable error causes the applied queue to redeliver the message per its own retry contract; the jobs runtime records the attempt counter on each jobStarted event and inherits max-attempts from the applied queue's own ceiling (Cloudflare Queues ceiling 100 per ADR-3003 in the messaging-queue-cloudflare blueprint). The elicited retryPolicy on a job definition ({ maxAttempts, backoff: constant | exponential | linear }) is applied within that ceiling. After maxAttempts terminal jobStarted events with the same jobId and incrementing attempt counter, jobFailed lands with the terminal error code and no further re-delivery.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "jobs",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "Retry inheritance from the applied queue keeps the transport authority in one place (the queue's own contract) while letting the job author express the domain retry surface (backoff shape, max-attempts within the ceiling).",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:jobs-background",
|
|
12
|
+
"category:03-retry"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "jobs-background-REQ-005",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Job-run event log: jobScheduled, jobStarted, jobCompleted, jobFailed with metadata-only fields",
|
|
5
|
+
"description": "The jobs runtime fires four lifecycle events on the injected event sink: jobScheduled at the moment the scheduler enqueues a message; jobStarted at the moment the consumer begins dispatching a message to a handler; jobCompleted at the moment a handler returns without throwing; jobFailed at the moment the applied queue's max-attempts is exhausted with a terminal error code. Each event record carries only the metadata fields { jobId, jobName, attempts, duration, timestamp } (plus a terminalErrorCode on jobFailed). No job input fields, no handler output fields, no user id, no email, no SSN. The event whitelist is enforced at the sink boundary.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "jobs",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "A metadata-only event log lets the applying project route the events through its logging surface without leaking domain PII by default. Domain-side observability (recording the input, the output, the error surface) is the applying project's own concern under its logging companion.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:jobs-background",
|
|
12
|
+
"category:04-run-log"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "jobs-background-REQ-006",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Operator-facing surface (elicited): cli, httpEndpoint, or none",
|
|
5
|
+
"description": "The operator-facing surface for triggering ad-hoc runs, inspecting the run log, and re-driving a failed job is elicited at apply time between three shipped shapes: cli (a Node CLI shim the applying project builds against the jobs runtime), httpEndpoint (a small HTTP surface the applying project mounts through its own HTTP shape), or none (no operator surface; the run log is the only observability). The blueprint does not ship the CLI or the HTTP endpoint itself; it ships the contract they build against so the applying project can hoist a thin surface without a facade re-shape.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "jobs",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "Operator surfaces sit above the run-log contract but their exact shape varies by applying project (CLI-first estates vs HTTP-first APIs). The elicited choice records the answer without prescribing a UI framework or an HTTP router.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:jobs-background",
|
|
12
|
+
"category:05-operator-surface"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
18
|
+
}
|
package/blueprints/jobs-background/contributions/tacs/tac-3101-jobs-background-job-definition.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tacId": "TAC-3101-jobs-background-job-definition",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"name": "Jobs-background job-definition module contract",
|
|
8
|
+
"purpose": "The shape every job-definition module in the applying project's jobs directory exports. One shape carries name, handler, inputSchema (opaque per project), retryPolicy, and timeoutMs. The jobs runtime registers every job at boot on the applied queue consumer and dispatches by name.",
|
|
9
|
+
"responsibilities": [
|
|
10
|
+
"Declare a job by exporting { name, handler, inputSchema, retryPolicy, timeoutMs } (AC-30102-1, AC-jobs-retryOnHandlerFailure).",
|
|
11
|
+
"Bound handler wall-clock via timeoutMs; jobs runtime cancels a handler that exceeds timeoutMs and treats it as a retryable error (AC-jobs-retryOnHandlerFailure).",
|
|
12
|
+
"Respect the applied queue's max-attempts ceiling; retryPolicy.maxAttempts must not exceed the ceiling (AC-jobs-retryOnHandlerFailure).",
|
|
13
|
+
"Refuse composition on a bare project via the requiresAppliedCapabilities gate (AC-jobs-requiresQueue)."
|
|
14
|
+
],
|
|
15
|
+
"internalStructure": "Each job module in the applying project's jobs/ directory (default ./jobs/, elicited) exports a plain object. The jobs runtime scans the directory at boot, validates the shape, and registers handlers keyed by name on the applied queue consumer's dispatch table. Dispatch is by message.name; no runtime type reflection.",
|
|
16
|
+
"interfaces": [
|
|
17
|
+
{
|
|
18
|
+
"name": "jobModule.default",
|
|
19
|
+
"kind": "shape",
|
|
20
|
+
"description": "The default export shape: { name: string, handler: async (input) => any, inputSchema: opaque, retryPolicy: { maxAttempts: positive integer, backoff: 'exponential' | 'constant' | 'linear' }, timeoutMs: positive integer }."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "registerJobs",
|
|
24
|
+
"kind": "factory",
|
|
25
|
+
"description": "The jobs runtime entry point: registerJobs({ jobsDir, queueConsumer, onEvent }) reads jobModule files, validates the shape, and registers dispatch handlers."
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"dependencies": [
|
|
29
|
+
{
|
|
30
|
+
"name": "Scheduler",
|
|
31
|
+
"kind": "tac",
|
|
32
|
+
"tacId": "TAC-3102-jobs-background-scheduler",
|
|
33
|
+
"description": "The scheduler enqueues messages by job name; the job-definition module is the target."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "Run log",
|
|
37
|
+
"kind": "tac",
|
|
38
|
+
"tacId": "TAC-3103-jobs-background-run-log",
|
|
39
|
+
"description": "The runtime fires the four lifecycle events on the injected event sink for every dispatch."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "Applied queue capability",
|
|
43
|
+
"kind": "external",
|
|
44
|
+
"description": "Composed on the applied blueprint declaring capabilities: [queue] via the T-5 mechanism; refuses apply on a bare project per REQ-001."
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"tradeoffs": "The v1.0.0 job-definition shape does not prescribe an inputSchema validator; the applying project owns validation. A later minor could add an optional validate hook if a shipped validation library becomes the shelf default. The dispatch table is by job name; two modules exporting the same name collide at boot and refuse.",
|
|
48
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
49
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
50
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tacId": "TAC-3102-jobs-background-scheduler",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"name": "Jobs-background scheduler",
|
|
8
|
+
"purpose": "The scheduler surface publishes messages to the applied queue on POSIX cron strings and one-shot delayed shapes. The scheduler mode is elicited (inProcess, workerCron, external) at ADR-3102; a reserved v1.1.0 workflows mode adds a fourth enum value per section 5.7 of the spec.",
|
|
9
|
+
"responsibilities": [
|
|
10
|
+
"Parse a POSIX cron string on a job definition and enqueue a message on each fire (AC-30103-1, AC-jobs-scheduledRunsOnCron).",
|
|
11
|
+
"Accept a one-shot delayed shape (delayMs or ISO-8601 fireAt) and enqueue the message when the elicited fire time is reached (AC-30103-1).",
|
|
12
|
+
"Expose a fake-clock seam so probes can drive the scheduler deterministically without wall-clock waits (AC-jobs-scheduledRunsOnCron).",
|
|
13
|
+
"Refuse workerCron mode when the applied queue has no cron surface (documented in the guide's Known limitations section)."
|
|
14
|
+
],
|
|
15
|
+
"internalStructure": "One scheduler module carrying a start({ clock, publisher, onEvent }) entry point where clock is the injected clock (real system clock in production, the fake-clock seam under the probe). A long-lived setInterval per cron string in inProcess mode; a set of setTimeouts per one-shot delay. The fake-clock seam is a { now, advance(ms) } object the module reads instead of Date.now / setTimeout when injected.",
|
|
16
|
+
"interfaces": [
|
|
17
|
+
{
|
|
18
|
+
"name": "createScheduler",
|
|
19
|
+
"kind": "factory",
|
|
20
|
+
"description": "createScheduler({ mode, clock, publisher, onEvent }) -> Scheduler; mode is one of inProcess, workerCron, external."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "scheduler.schedule",
|
|
24
|
+
"kind": "method",
|
|
25
|
+
"description": "schedule({ jobName, cron?: string, fireAt?: string, delayMs?: number, body?: any }): enqueues per the cron or delayed shape."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "onEvent",
|
|
29
|
+
"kind": "callback",
|
|
30
|
+
"description": "Structured-event sink invoked with jobScheduled records: { event, jobId, jobName, attempts: 0, timestamp }."
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"dependencies": [
|
|
34
|
+
{
|
|
35
|
+
"name": "Job-definition module",
|
|
36
|
+
"kind": "tac",
|
|
37
|
+
"tacId": "TAC-3101-jobs-background-job-definition",
|
|
38
|
+
"description": "The scheduler enqueues by jobName targeting a registered job-definition module."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "Run log",
|
|
42
|
+
"kind": "tac",
|
|
43
|
+
"tacId": "TAC-3103-jobs-background-run-log",
|
|
44
|
+
"description": "jobScheduled events fire on the injected event sink."
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"tradeoffs": "The inProcess mode is unsuited to a Cloudflare Worker deploy (Worker instances have no long-lived setInterval); the workerCron mode addresses that at ADR-3102. The v1.1.0 workflows mode adds Cloudflare Workflows step.do orchestration without changing the outward scheduler contract.",
|
|
48
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
49
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
50
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tacId": "TAC-3103-jobs-background-run-log",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"name": "Jobs-background run-log event sink",
|
|
8
|
+
"purpose": "The lifecycle-event sink for jobScheduled, jobStarted, jobCompleted and jobFailed events. The event whitelist { jobId, jobName, attempts, duration, timestamp } (plus terminalErrorCode on jobFailed) is enforced at this sink boundary; no domain job input, no handler output, no user id, no email, no SSN.",
|
|
9
|
+
"responsibilities": [
|
|
10
|
+
"Emit the four lifecycle events at the four moments in the runtime (AC-jobs-scheduledRunsOnCron, AC-jobs-retryOnHandlerFailure, AC-jobs-eventSecrecy).",
|
|
11
|
+
"Enforce the metadata-only whitelist at the sink boundary regardless of what the domain job body carries (AC-jobs-eventSecrecy).",
|
|
12
|
+
"Carry the attempts counter on every jobStarted and jobFailed record so the retry chain is observable (AC-jobs-retryOnHandlerFailure)."
|
|
13
|
+
],
|
|
14
|
+
"internalStructure": "One event-sink adapter module exporting createRunLog({ upstream }) -> RunLog whose method fire({ event, ... }) filters through the whitelist and forwards to the upstream sink (the logging companion supplies the upstream). The sink is stateless per event; the runtime holds no history.",
|
|
15
|
+
"interfaces": [
|
|
16
|
+
{
|
|
17
|
+
"name": "createRunLog",
|
|
18
|
+
"kind": "factory",
|
|
19
|
+
"description": "createRunLog({ upstream }) -> RunLog; upstream is the applied logger companion's event sink."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "runLog.fire",
|
|
23
|
+
"kind": "method",
|
|
24
|
+
"description": "fire({ event, jobId, jobName, attempts, duration, timestamp, terminalErrorCode? }): validates the whitelist and forwards the record; unknown fields are dropped."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "eventEnvelope",
|
|
28
|
+
"kind": "shape",
|
|
29
|
+
"description": "The record shape: { event: 'jobScheduled' | 'jobStarted' | 'jobCompleted' | 'jobFailed', jobId: string, jobName: string, attempts: non-negative integer, duration: non-negative number, timestamp: ISO-8601 string, terminalErrorCode?: string (only on jobFailed) }."
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"dependencies": [
|
|
33
|
+
{
|
|
34
|
+
"name": "Job-definition module",
|
|
35
|
+
"kind": "tac",
|
|
36
|
+
"tacId": "TAC-3101-jobs-background-job-definition",
|
|
37
|
+
"description": "Every dispatched handler produces two events (jobStarted, jobCompleted or jobFailed)."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "Scheduler",
|
|
41
|
+
"kind": "tac",
|
|
42
|
+
"tacId": "TAC-3102-jobs-background-scheduler",
|
|
43
|
+
"description": "The scheduler fires jobScheduled at enqueue time."
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"tradeoffs": "The whitelist is strict; a project that needs additional structured metadata (a correlation id, a tenant id) hoists a companion-side envelope through the applied logger rather than widening this contract. That keeps the shipped surface small and defensible against PII leaks; the price is one more integration step for projects with rich domain context.",
|
|
47
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
48
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
49
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "jobs-background-US-30101",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "jobs-background-REQ-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Apply refuses on a bare project with exit 3 and the stable message id",
|
|
8
|
+
"asA": "operator applying jobs-background on a fresh project with no queue provider applied yet",
|
|
9
|
+
"iWant": "the apply verb to refuse with exit 3, the stable message id tag on the first stderr line, an explicit reference to the shipped queue provider messaging-queue-cloudflare, and the override flag --allow-no-queue-yet",
|
|
10
|
+
"soThat": "a bare apply cannot silently ship a jobs-background surface with no queue behind it, and a dashboard or lint that binds to the stable message id keeps working across future refusal-text edits",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-jobs-requiresQueue",
|
|
14
|
+
"description": "On a fresh init scratch project with NO queue provider applied, rcf define blueprint add ./blueprints/jobs-background returns exit code 3; stderr contains the substring [jobs-background-no-queue] on the first line; stderr contains the substring messaging-queue-cloudflare naming the shipped provider; stderr contains the substring --allow-no-queue-yet naming the override flag. Nothing is written to rcf/manifest.json under blueprints[] for jobs-background.",
|
|
15
|
+
"given": "a fresh rcf init scratch project with no applied blueprints",
|
|
16
|
+
"when": "rcf define blueprint add ./blueprints/jobs-background runs",
|
|
17
|
+
"then": "exit code is 3; stderr first-line grep matches [jobs-background-no-queue]; stderr grep matches messaging-queue-cloudflare; stderr grep matches --allow-no-queue-yet; the manifest has no jobs-background entry",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3101-jobs-background-job-definition"],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "jobs-background-US-30102",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "jobs-background-REQ-002",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Job definition contract with name, handler, inputSchema, retryPolicy and timeoutMs",
|
|
8
|
+
"asA": "developer authoring a job in an applying project",
|
|
9
|
+
"iWant": "one shipped job-definition shape { name, handler, inputSchema, retryPolicy, timeoutMs } that the jobs runtime registers at boot on the applied queue consumer",
|
|
10
|
+
"soThat": "every job in the project follows one uniform contract without the blueprint prescribing an input-validation library or a handler-callback style",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-30102-1",
|
|
14
|
+
"description": "The two toy job-definition modules under packages/rcf-lite/test/fixtures/infra-s3-and-queue/jobs/ (send-welcome-email.mjs and refresh-cache.mjs) each export a plain object with name (string), handler (async function), inputSchema (opaque, non-null), retryPolicy ({ maxAttempts: positive integer, backoff: one of exponential | constant | linear }), and timeoutMs (positive integer). The jobs runtime under src/jobs-runtime.mjs registers every job by name at boot and dispatches by message.name.",
|
|
15
|
+
"given": "the shared sample-app fixture with the T-4 jobs-runtime wired on top of the T-3 in-memory queue seam",
|
|
16
|
+
"when": "the fixture jobs runtime boots and reads the jobs/ directory",
|
|
17
|
+
"then": "both toy job modules load without throwing; the runtime's registry has entries keyed by name whose values carry handler, inputSchema, retryPolicy and timeoutMs; dispatch by message.name calls the matching handler",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3101-jobs-background-job-definition"],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "jobs-background-US-30103",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "jobs-background-REQ-003",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Scheduler accepts POSIX cron string and one-shot delayed",
|
|
8
|
+
"asA": "developer scheduling a job to run every minute (POSIX cron) or once after a delay",
|
|
9
|
+
"iWant": "the scheduler surface to accept both a POSIX cron string and a one-shot delayed shape (delayMs or fireAt) and publish the corresponding message to the applied queue at the elicited fire time",
|
|
10
|
+
"soThat": "one contract covers both recurring and deferred sends without a second scheduler surface",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-30103-1",
|
|
14
|
+
"description": "The scheduler under packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/scheduler.mjs accepts a POSIX cron string on a job definition (refresh-cache.mjs carries cron: '* * * * *') and publishes a message to the applied queue when the fake-clock advances past the fire time. The scheduler also accepts a one-shot delayed schedule (delayMs) and publishes when the fake-clock advances past the fireAt time.",
|
|
15
|
+
"given": "the fixture scheduler booted in inProcess mode with a fake-clock seam",
|
|
16
|
+
"when": "the fake clock advances one POSIX cron minute or past a scheduled delayMs",
|
|
17
|
+
"then": "one message per fire lands on the applied in-memory queue with the target job.name and an ISO-8601 timestamp",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3102-jobs-background-scheduler"],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "jobs-background-US-30104",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "jobs-background-REQ-004",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Retry: handler throws retryable error re-delivers via applied queue with attempts counter",
|
|
8
|
+
"asA": "developer authoring a job that can fail transiently",
|
|
9
|
+
"iWant": "the applied queue to redeliver a failed message per the job's elicited retryPolicy within the applied queue's max-attempts ceiling, with the attempt counter incrementing on each jobStarted event",
|
|
10
|
+
"soThat": "transient failures self-heal within maxAttempts and terminal failures are recorded once via jobFailed with a terminal error code",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-jobs-retryOnHandlerFailure",
|
|
14
|
+
"description": "With SIMULATE_HANDLER_THROW=true set on the shared sample-app fixture, the retry-and-fail probe schedules a job whose handler throws a retryable error; the applied in-memory queue redelivers the message per retryPolicy.maxAttempts. Three jobStarted events fire on the sink with the same jobId and attempts counter 1, 2, 3; after the third failure jobFailed fires with a terminal error code and no further re-delivery follows.",
|
|
15
|
+
"given": "the fixture with SIMULATE_HANDLER_THROW=true and the retry-and-fail probe running against the in-memory queue seam",
|
|
16
|
+
"when": "the failing job is scheduled",
|
|
17
|
+
"then": "three jobStarted records with the same jobId and attempts 1,2,3; one jobFailed with a terminalErrorCode; no fourth jobStarted",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3101-jobs-background-job-definition", "TAC-3103-jobs-background-run-log"],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "jobs-background-US-30105",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "jobs-background-REQ-005",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Job-run event log: four moments with metadata-only whitelist",
|
|
8
|
+
"asA": "operator observing background-job activity through the run log",
|
|
9
|
+
"iWant": "each event record to carry only the whitelisted metadata fields { jobId, jobName, attempts, duration, timestamp } (plus terminalErrorCode on jobFailed), and NO job input, NO handler output, NO user id, NO email, NO SSN",
|
|
10
|
+
"soThat": "domain PII cannot leak through the run log by accident even if the job carries PII in its input by design",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-jobs-eventSecrecy",
|
|
14
|
+
"description": "With SIMULATE_PII_IN_JOB_INPUT=true set on the shared sample-app fixture, the event-secrecy probe schedules a job whose input carries a PII fixture body { userId: 1234, ssn: '123-45-6789', email: 'test@example.com' } and drives it through completion. Every event record on the sink (jobScheduled, jobStarted, jobCompleted) carries the whitelist { jobId, jobName, attempts, duration, timestamp } and NO input fields, NO output fields, NO literal 1234, NO literal '123-45-6789', NO literal 'test@example.com'.",
|
|
15
|
+
"given": "the fixture with SIMULATE_PII_IN_JOB_INPUT=true and the event-secrecy probe running against the in-memory queue seam",
|
|
16
|
+
"when": "the PII-input job is scheduled",
|
|
17
|
+
"then": "every event record whitelisted; grep on the serialised event stream returns zero matches for the PII fixture text",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3103-jobs-background-run-log"],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|