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,149 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slug": "jobs-background",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"category": "jobs",
|
|
5
|
+
"capabilities": [
|
|
6
|
+
"backgroundJobs"
|
|
7
|
+
],
|
|
8
|
+
"requiresAppliedCapabilities": {
|
|
9
|
+
"capabilities": [
|
|
10
|
+
"queue"
|
|
11
|
+
],
|
|
12
|
+
"allowSkipFlag": "allow-no-queue-yet",
|
|
13
|
+
"refusalMessageId": "jobs-background-no-queue"
|
|
14
|
+
},
|
|
15
|
+
"suggestedCompanions": [
|
|
16
|
+
{
|
|
17
|
+
"role": "logging",
|
|
18
|
+
"reason": "Every jobStarted / jobCompleted / jobFailed event writes through the applied logger; a logging companion supplies the factory."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"role": "errorHandling",
|
|
22
|
+
"reason": "A jobFailed constructs an internal error record with the terminal error code and the correlation id; an error-handling companion supplies the record factory and the boundary."
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"contributions": [
|
|
26
|
+
{
|
|
27
|
+
"id": "jobs-background-REQ-001",
|
|
28
|
+
"kind": "req",
|
|
29
|
+
"path": "requirements/jobs-background-req-001.json"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "jobs-background-REQ-002",
|
|
33
|
+
"kind": "req",
|
|
34
|
+
"path": "requirements/jobs-background-req-002.json"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "jobs-background-REQ-003",
|
|
38
|
+
"kind": "req",
|
|
39
|
+
"path": "requirements/jobs-background-req-003.json"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "jobs-background-REQ-004",
|
|
43
|
+
"kind": "req",
|
|
44
|
+
"path": "requirements/jobs-background-req-004.json"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "jobs-background-REQ-005",
|
|
48
|
+
"kind": "req",
|
|
49
|
+
"path": "requirements/jobs-background-req-005.json"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "jobs-background-REQ-006",
|
|
53
|
+
"kind": "req",
|
|
54
|
+
"path": "requirements/jobs-background-req-006.json"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "jobs-background-US-30101",
|
|
58
|
+
"kind": "us",
|
|
59
|
+
"path": "user-stories/jobs-background-us-30101.json"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "jobs-background-US-30102",
|
|
63
|
+
"kind": "us",
|
|
64
|
+
"path": "user-stories/jobs-background-us-30102.json"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "jobs-background-US-30103",
|
|
68
|
+
"kind": "us",
|
|
69
|
+
"path": "user-stories/jobs-background-us-30103.json"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "jobs-background-US-30104",
|
|
73
|
+
"kind": "us",
|
|
74
|
+
"path": "user-stories/jobs-background-us-30104.json"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "jobs-background-US-30105",
|
|
78
|
+
"kind": "us",
|
|
79
|
+
"path": "user-stories/jobs-background-us-30105.json"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "jobs-background-US-30106",
|
|
83
|
+
"kind": "us",
|
|
84
|
+
"path": "user-stories/jobs-background-us-30106.json"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "jobs-background-US-30107",
|
|
88
|
+
"kind": "us",
|
|
89
|
+
"path": "user-stories/jobs-background-us-30107.json"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "jobs-background-US-30108",
|
|
93
|
+
"kind": "us",
|
|
94
|
+
"path": "user-stories/jobs-background-us-30108.json"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "jobs-background-US-30109",
|
|
98
|
+
"kind": "us",
|
|
99
|
+
"path": "user-stories/jobs-background-us-30109.json"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "TAC-3101-jobs-background-job-definition",
|
|
103
|
+
"kind": "tac",
|
|
104
|
+
"path": "tacs/tac-3101-jobs-background-job-definition.json"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "TAC-3102-jobs-background-scheduler",
|
|
108
|
+
"kind": "tac",
|
|
109
|
+
"path": "tacs/tac-3102-jobs-background-scheduler.json"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "TAC-3103-jobs-background-run-log",
|
|
113
|
+
"kind": "tac",
|
|
114
|
+
"path": "tacs/tac-3103-jobs-background-run-log.json"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "ADR-3101-jobs-background-model",
|
|
118
|
+
"kind": "adr",
|
|
119
|
+
"path": "adrs/adr-3101-jobs-background-model.json",
|
|
120
|
+
"scope": "global",
|
|
121
|
+
"topic": "backgroundJobModel",
|
|
122
|
+
"standardsTraceClause": "generic enterprise practice"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"id": "ADR-3102-jobs-background-scheduler-mode",
|
|
126
|
+
"kind": "adr",
|
|
127
|
+
"path": "adrs/adr-3102-jobs-background-scheduler-mode.json",
|
|
128
|
+
"recommendedDefault": true,
|
|
129
|
+
"elicited": true,
|
|
130
|
+
"standardsTraceClause": "Cloudflare Cron Triggers documented worker cron surface"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"id": "ADR-3103-jobs-background-requires-applied-capabilities",
|
|
134
|
+
"kind": "adr",
|
|
135
|
+
"path": "adrs/adr-3103-jobs-background-requires-applied-capabilities.json",
|
|
136
|
+
"recommendedDefault": true,
|
|
137
|
+
"elicited": false,
|
|
138
|
+
"standardsTraceClause": "visual round spec section 5.5.2 (the T-5 capability-consumer mechanism)"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"id": "ADR-3104-jobs-background-timeout-floor",
|
|
142
|
+
"kind": "adr",
|
|
143
|
+
"path": "adrs/adr-3104-jobs-background-timeout-floor.json",
|
|
144
|
+
"recommendedDefault": true,
|
|
145
|
+
"elicited": true,
|
|
146
|
+
"standardsTraceClause": "Cloudflare Queues consumer wall-clock ceiling per platform limits doc"
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adrId": "ADR-3101-jobs-background-model",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "accepted",
|
|
7
|
+
"scope": "global",
|
|
8
|
+
"topic": "backgroundJobModel",
|
|
9
|
+
"title": "Background-job model: retry-and-schedule as the shipped shape (scope global, new topic backgroundJobModel)",
|
|
10
|
+
"context": "A background-job discipline ships one of several models at the shipped-shape boundary: retry-and-schedule (cron + one-shot delayed, per-message retry, terminal DLQ) as this blueprint ships; stateful-workflow (Temporal-shape or Airflow-shape durable execution with multi-step state); durable-execution (Cloudflare Workflows step.do orchestration). The retry-and-schedule model composes cleanly on an applied queue capability; a stateful-workflow sibling would own its own persistent state store and its own scheduling contract. jobs-background claims the backgroundJobModel topic at scope global so future stateful-workflow siblings (a Temporal-shape or Airflow-shape blueprint) surface a DELIBERATE conflict and the operator picks one per project via a project-level ADR.",
|
|
11
|
+
"decision": "The shipped v1.0.0 shape is retry-and-schedule. The scheduler contract (POSIX cron plus one-shot delayed per REQ-003) and the retry contract (per-message re-delivery within the applied queue's max-attempts ceiling per REQ-004) are the two axes; the run-log at four moments per REQ-005 is the observability surface. The backgroundJobModel topic scope is global on this blueprint; the topic string is backgroundJobModel (lower camel case, one concept per topic, no version suffix, per the round-2 rules-for-new-topics clause). The round-6 Workflows adapter (per spec section 5.7 and round-6 decision (b)) lives INSIDE this blueprint as a v1.1.0 minor scheduler-mode value (workflows) at ADR-3102, not as a sibling; the topic backgroundJobModel is NOT re-claimed by the workflows mode. A future stateful-workflow sibling (a Temporal-shape blueprint) claims backgroundJobModel with a different answer and forces a DELIBERATE conflict.",
|
|
12
|
+
"consequences": "The applying project gets a retry-and-schedule background-job surface with metadata-only observability. Standards trace clause: generic enterprise practice for the job-definition and scheduler contracts. Future stateful-workflow siblings (Temporal-shape, Airflow-shape) will surface DELIBERATE conflicts on this topic; the operator resolves by picking one background-job model per project via a project-level ADR. The round-6 Workflows minor bump inside jobs-background is NOT a sibling; it is an elicited scheduler-mode value at ADR-3102 that keeps the backgroundJobModel topic answer unchanged.",
|
|
13
|
+
"alternativesConsidered": [
|
|
14
|
+
{
|
|
15
|
+
"name": "Ship stateful-workflow as the v1.0.0 default",
|
|
16
|
+
"summary": "Ship a Temporal-shape durable execution model as the primary v1.0.0 background-job surface with multi-step state.",
|
|
17
|
+
"reasonNotChosen": "Temporal-shape workflows require a persistent state store the applying project has to host; the round-5 shipped shape targets a directed producer-to-consumer worklist behind an applied queue. Stateful-workflow is a future sibling on the same topic, not the v1.0.0 default."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "Ship durable-execution (Cloudflare Workflows) as the v1.0.0 default",
|
|
21
|
+
"summary": "Ship Cloudflare Workflows step.do as the primary v1.0.0 scheduler.",
|
|
22
|
+
"reasonNotChosen": "Cloudflare Workflows is a Cloudflare-only orchestration; the round-5 shipped shape targets a portable retry-and-schedule model over an applied queue capability that any queue provider can supply. Workflows is a scheduler-mode alternative at ADR-3102 (reserved for v1.1.0), not a topic-claim alternative."
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
26
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
27
|
+
}
|
package/blueprints/jobs-background/contributions/adrs/adr-3102-jobs-background-scheduler-mode.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adrId": "ADR-3102-jobs-background-scheduler-mode",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "accepted",
|
|
7
|
+
"title": "Scheduler mode elicited: inProcess, workerCron, external (workflows reserved for v1.1.0)",
|
|
8
|
+
"context": "The scheduler surface can be realised in three shipped shapes today and a fourth shipped shape at v1.1.0. inProcess: a long-lived Node process (setInterval per cron string, setTimeout per one-shot delay) publishing to the applied queue. workerCron: Cloudflare Cron Triggers wire the scheduled event to a Worker handler that publishes to the applied queue (see the Cloudflare Cron Triggers documentation as the vendor reference for the fields at Cron Triggers, worker-side scheduled handler surface). external: an external cron runner (a Kubernetes CronJob, a GitHub Actions schedule, a systemd timer) invokes an operator surface (or the Node process boot line) with a fire-now command. The v1.1.0 minor per section 5.7 of the spec adds a workflows value that wraps the job-definition contract inside Cloudflare Workflows step.do orchestration; the v1.0.0 facade keeps the mode ELICITED so the fourth enum value slots in without a facade re-shape.",
|
|
9
|
+
"decision": "The scheduler mode is elicited at apply time between inProcess, workerCron and external. The default recommendation (recommendedDefault: true, elicited: true) is inProcess for a Node long-lived server, workerCron for a Cloudflare Worker deploy, and external for a fleet of short-lived containers already carrying an external cron runner. When the applied queue provider has no cron surface at all (which Cloudflare Queues does not directly; the cron surface is on Cloudflare Workers, not on Queues), workerCron mode refuses at apply time and the guide names the alignment. The v1.1.0 minor bump adds a workflows value; the standardsTraceClause of the contribution entry cites the Cloudflare Cron Triggers documented worker cron surface.",
|
|
10
|
+
"consequences": "The applying project picks a scheduler mode per environment. Standards trace clause: Cloudflare Cron Triggers documented worker cron surface. workerCron mode inherits Cloudflare cron-trigger semantics (per-cron-string on the Worker's wrangler config, not per-job-cron on the runtime); the guide names this alignment. external mode carries no lock-in but shifts the schedule discipline into the external runner; the guide names the trade-off. The reserved v1.1.0 workflows value carries the durable-execution semantics per the round-6 Workflows adapter proposal.",
|
|
11
|
+
"alternativesConsidered": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Hard-code inProcess as the shipped v1.0.0 default with no elicit",
|
|
14
|
+
"summary": "Ship inProcess as the only supported scheduler mode at v1.0.0.",
|
|
15
|
+
"reasonNotChosen": "A Cloudflare Worker deploy has no long-lived setInterval; hard-coding inProcess would refuse composition on the applied deploy-cloudflare-workers target the shared fixture uses. The elicited mode makes the shipped facade portable across the Node long-lived and Worker-cron shapes."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Ship the reserved workflows mode at v1.0.0",
|
|
19
|
+
"summary": "Include the Cloudflare Workflows step.do orchestration inline in the v1.0.0 shipped shape.",
|
|
20
|
+
"reasonNotChosen": "The round-6 Workflows adapter is on Baz per section 5.7 of the spec; v1.0.0 keeps the mode ELICITED at three values and the v1.1.0 minor adds workflows as the fourth. No topic conflict, no facade re-shape."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adrId": "ADR-3103-jobs-background-requires-applied-capabilities",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "accepted",
|
|
7
|
+
"title": "Requires capabilities: [queue] with allowSkipFlag allow-no-queue-yet and refusalMessageId jobs-background-no-queue",
|
|
8
|
+
"context": "jobs-background composes on an applied queue capability. Applying on a bare project (no queue provider applied) has two possible shapes: refuse-with-message (the T-5 capability-consumer mechanism, ratified for application-admin-console and object-storage-s3), or silently apply with a stub queue (which would leave the applying project with an unactivated job dispatch surface). Baz decision 5 (verbatim): jobs requires an applied queue and refuses on a bare project, with an override flag.",
|
|
9
|
+
"decision": "The blueprint declares requiresAppliedCapabilities { capabilities: [queue], allowSkipFlag: allow-no-queue-yet, refusalMessageId: jobs-background-no-queue }. The apply verb refuses with exit 3 and the first-line stderr tag [jobs-background-no-queue] when no applied blueprint declares capabilities: [queue]. The refusal message names the shipped provider messaging-queue-cloudflare and the override flag --allow-no-queue-yet explicitly per Q3 default. The override writes a sidecar note under the queue family on rcf/blueprints/jobs-background.applied.json so rcf define validate later flags surfaces that never activated. Because the applied-blueprint-record schema in rcf-schemas 0.6.1 is closed, the note lives on the sidecar (rcf/blueprints/<slug>.applied.json), not on manifest.blueprints[].source.notes; the shipped T-5 mechanism (T-2 precedent for object-storage-s3) is the source of truth for that placement.",
|
|
10
|
+
"consequences": "The applying project's apply-time behaviour is predictable: refuse on bare, override-with-note or apply-with-queue. Standards trace clause: visual round spec section 5.5.2 (the T-5 capability-consumer mechanism). Dashboards and lints bind to the stable message id jobs-background-no-queue. The queue-family family derivation in the sidecar-notes construction (packages/rcf-lite/src/blueprint/apply.js buildOverrideNote) is extended to detect queue from the flag name so the note reads 'no queue yet' (not 'no auth yet' or 'no secrets-management yet').",
|
|
11
|
+
"alternativesConsidered": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Silently apply on a bare project with a stub queue",
|
|
14
|
+
"summary": "Ship a no-op in-memory queue as the default when no queue capability is applied.",
|
|
15
|
+
"reasonNotChosen": "A silent stub loses the observability the T-5 mechanism gives; a project would ship jobs against a no-op queue without realising it. Baz decision 5 (verbatim) forecloses this."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Refuse on a bare project without an override flag",
|
|
19
|
+
"summary": "Refuse composition and require the applied queue before any apply call succeeds.",
|
|
20
|
+
"reasonNotChosen": "The scaffolding case (a project mid-migration that wants the jobs surface staged before the queue provider is picked) needs a documented escape hatch; the sidecar note lets rcf define validate later flag the gap so the scaffolding stays honest."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
package/blueprints/jobs-background/contributions/adrs/adr-3104-jobs-background-timeout-floor.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adrId": "ADR-3104-jobs-background-timeout-floor",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "accepted",
|
|
7
|
+
"title": "Handler timeout: default 60 seconds, floor 1 second, ceiling bounded by the applied queue's consumer wall-clock",
|
|
8
|
+
"context": "A background-job handler has a bounded wall-clock in the applied queue's consumer. Cloudflare Queues caps consumer wall-clock at 15 minutes (900 seconds) per the platform limits documentation; a handler that runs past that mark is force-killed by the platform regardless of what timeoutMs the job-definition module declared. The jobs runtime enforces its own timeoutMs on the handler and treats a timed-out handler as a retryable error (a jobStarted followed by a jobFailed with retryable code, and the applied queue re-delivers per the retry contract).",
|
|
9
|
+
"decision": "The default handler timeoutMs is 60000 (60 seconds); the floor is 1000 (1 second); the ceiling is elicited per applied queue provider. For Cloudflare Queues the ceiling is 900000 (15 minutes) per the platform limits documentation. Job-definition modules that declare a timeoutMs above the ceiling refuse at boot with a clear error naming the ceiling and the applied provider. The elicited timeoutMs on a job definition is applied within the ceiling.",
|
|
10
|
+
"consequences": "The applying project inherits a handler-timeout discipline that respects the applied queue's own wall-clock. Standards trace clause: Cloudflare Queues consumer wall-clock ceiling per platform limits doc. A handler that needs longer than the applied provider's ceiling is a shape mismatch; the answer is either to split the handler into a state-machine driven by re-enqueues, or (at v1.1.0) adopt the workflows scheduler mode which lifts the per-run wall-clock via durable execution.",
|
|
11
|
+
"alternativesConsidered": [
|
|
12
|
+
{
|
|
13
|
+
"name": "Ship a fixed 5-minute default timeout with no elicit",
|
|
14
|
+
"summary": "Hard-code a 5-minute default timeout on every job definition.",
|
|
15
|
+
"reasonNotChosen": "A shipped default of 60 seconds forces the applying project to make timeout an explicit choice on any long-running handler, which is the right posture for a shipped facade. 60 seconds is a common-enough handler wall-clock that most simple handlers do not need to opt out."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Ceiling as a global constant per shipped provider",
|
|
19
|
+
"summary": "Bake the 15-minute Cloudflare Queues ceiling into the blueprint code.",
|
|
20
|
+
"reasonNotChosen": "The ceiling depends on the applied queue provider; a future messaging-queue-postgres sibling would carry a different consumer wall-clock. The elicited ceiling per provider keeps the answer where the transport authority lives."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply-time override probe.
|
|
3
|
+
*
|
|
4
|
+
* Invokes rcf define blueprint add ./blueprints/jobs-background
|
|
5
|
+
* --allow-no-queue-yet on a fresh scratch project WITH NO applied queue
|
|
6
|
+
* provider. Asserts exit 0, asserts rcf/blueprints/jobs-background
|
|
7
|
+
* .applied.json exists with allowNoAuthYet true, appliedCapabilities [],
|
|
8
|
+
* and notes containing 'no queue yet' + '--allow-no-queue-yet' + 'queue'
|
|
9
|
+
* (but NOT 'no auth yet' nor 'no secrets-management yet').
|
|
10
|
+
*
|
|
11
|
+
* Anchors AC-jobs-overrideRecorded.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn } from 'node:child_process';
|
|
15
|
+
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
|
|
16
|
+
import { tmpdir } from 'node:os';
|
|
17
|
+
import { join, resolve, dirname } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
import { PROJECT_ROOT } from './probe-utils.mjs';
|
|
21
|
+
|
|
22
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const BLUEPRINT_DIR = resolve(HERE, '..', '..');
|
|
24
|
+
const RCF_BIN = resolve(PROJECT_ROOT, 'packages/rcf-lite/bin/rcf.js');
|
|
25
|
+
|
|
26
|
+
function runNode(args, opts = {}) {
|
|
27
|
+
return new Promise((resolveP) => {
|
|
28
|
+
const child = spawn(process.execPath, args, { stdio: ['ignore', 'pipe', 'pipe'], ...opts });
|
|
29
|
+
let stdout = '';
|
|
30
|
+
let stderr = '';
|
|
31
|
+
child.stdout.on('data', (b) => { stdout += b.toString('utf8'); });
|
|
32
|
+
child.stderr.on('data', (b) => { stderr += b.toString('utf8'); });
|
|
33
|
+
child.on('close', (code) => resolveP({ code, stdout, stderr }));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default async function runProbe() {
|
|
38
|
+
const scratch = await mkdtemp(join(tmpdir(), 'jobs-bg-override-'));
|
|
39
|
+
try {
|
|
40
|
+
const init = await runNode([RCF_BIN, 'init'], { cwd: scratch });
|
|
41
|
+
if (init.code !== 0) {
|
|
42
|
+
return [{
|
|
43
|
+
anchorAcId: 'AC-jobs-overrideRecorded',
|
|
44
|
+
verdict: 'fail',
|
|
45
|
+
detail: `rcf init failed exit=${init.code} stderr=${init.stderr}`,
|
|
46
|
+
}];
|
|
47
|
+
}
|
|
48
|
+
const apply = await runNode(
|
|
49
|
+
[RCF_BIN, 'define', 'blueprint', 'add', BLUEPRINT_DIR, '--allow-no-queue-yet'],
|
|
50
|
+
{ cwd: scratch },
|
|
51
|
+
);
|
|
52
|
+
if (apply.code !== 0) {
|
|
53
|
+
return [{
|
|
54
|
+
anchorAcId: 'AC-jobs-overrideRecorded',
|
|
55
|
+
verdict: 'fail',
|
|
56
|
+
detail: `apply --allow-no-queue-yet expected exit 0; got exit=${apply.code} stderr=${apply.stderr}`,
|
|
57
|
+
}];
|
|
58
|
+
}
|
|
59
|
+
// Sidecar path.
|
|
60
|
+
const sidecarPath = join(scratch, 'rcf', 'blueprints', 'jobs-background.applied.json');
|
|
61
|
+
try { await stat(sidecarPath); } catch {
|
|
62
|
+
return [{
|
|
63
|
+
anchorAcId: 'AC-jobs-overrideRecorded',
|
|
64
|
+
verdict: 'fail',
|
|
65
|
+
detail: `sidecar ${sidecarPath} missing after --allow-no-queue-yet apply`,
|
|
66
|
+
}];
|
|
67
|
+
}
|
|
68
|
+
const doc = JSON.parse(await readFile(sidecarPath, 'utf8'));
|
|
69
|
+
const results = [];
|
|
70
|
+
const notes = typeof doc.notes === 'string' ? doc.notes : '';
|
|
71
|
+
const checks = {
|
|
72
|
+
slug: doc.slug === 'jobs-background',
|
|
73
|
+
allowNoAuthYet: doc.allowNoAuthYet === true,
|
|
74
|
+
appliedCapabilitiesEmpty: Array.isArray(doc.appliedCapabilities) && doc.appliedCapabilities.length === 0,
|
|
75
|
+
notesQueueFamily: notes.includes('no queue yet'),
|
|
76
|
+
notesOverrideFlag: notes.includes('--allow-no-queue-yet'),
|
|
77
|
+
notesNamesCapability: notes.includes('queue'),
|
|
78
|
+
notesNotAuthFamily: !notes.includes('no auth yet'),
|
|
79
|
+
notesNotSecretsFamily: !notes.includes('no secrets-management yet'),
|
|
80
|
+
};
|
|
81
|
+
const pass = Object.values(checks).every(Boolean);
|
|
82
|
+
results.push({
|
|
83
|
+
anchorAcId: 'AC-jobs-overrideRecorded',
|
|
84
|
+
verdict: pass ? 'pass' : 'fail',
|
|
85
|
+
detail: pass
|
|
86
|
+
? `exit=0; sidecar recorded slug=jobs-background allowNoAuthYet=true appliedCapabilities=[]; notes carry 'no queue yet' + '--allow-no-queue-yet' + 'queue' and none of the auth/secrets family words`
|
|
87
|
+
: `checks=${JSON.stringify(checks)}; notes='${notes}'`,
|
|
88
|
+
});
|
|
89
|
+
// Do NOT include the sidecarPath (a per-run tmp path) in the report;
|
|
90
|
+
// the sidecar contents (doc) is the load-bearing evidence.
|
|
91
|
+
return { results, extra: { sidecar: doc } };
|
|
92
|
+
} finally {
|
|
93
|
+
try { await rm(scratch, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apply-time refusal probe.
|
|
3
|
+
*
|
|
4
|
+
* Invokes rcf define blueprint add ./blueprints/jobs-background on a
|
|
5
|
+
* fresh scratch project WITH NO applied queue provider. Asserts exit 3,
|
|
6
|
+
* greps stderr for [jobs-background-no-queue] tag on the first line,
|
|
7
|
+
* greps stderr for the explicit provider name messaging-queue-cloudflare,
|
|
8
|
+
* and greps stderr for the override flag --allow-no-queue-yet.
|
|
9
|
+
*
|
|
10
|
+
* Anchors AC-jobs-requiresQueue.
|
|
11
|
+
*
|
|
12
|
+
* Q3 default per spec section 10: asserts BOTH exit code AND stable
|
|
13
|
+
* message id.
|
|
14
|
+
*
|
|
15
|
+
* The bare scratch project is created under a scratch tmp directory
|
|
16
|
+
* (not committed). rcf-lite is invoked via its bin script.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
21
|
+
import { tmpdir } from 'node:os';
|
|
22
|
+
import { join, resolve, dirname } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
import { PROJECT_ROOT } from './probe-utils.mjs';
|
|
26
|
+
|
|
27
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const BLUEPRINT_DIR = resolve(HERE, '..', '..');
|
|
29
|
+
const RCF_BIN = resolve(PROJECT_ROOT, 'packages/rcf-lite/bin/rcf.js');
|
|
30
|
+
|
|
31
|
+
function runNode(args, opts = {}) {
|
|
32
|
+
return new Promise((resolveP) => {
|
|
33
|
+
const child = spawn(process.execPath, args, { stdio: ['ignore', 'pipe', 'pipe'], ...opts });
|
|
34
|
+
let stdout = '';
|
|
35
|
+
let stderr = '';
|
|
36
|
+
child.stdout.on('data', (b) => { stdout += b.toString('utf8'); });
|
|
37
|
+
child.stderr.on('data', (b) => { stderr += b.toString('utf8'); });
|
|
38
|
+
child.on('close', (code) => resolveP({ code, stdout, stderr }));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default async function runProbe() {
|
|
43
|
+
const scratch = await mkdtemp(join(tmpdir(), 'jobs-bg-refuse-'));
|
|
44
|
+
try {
|
|
45
|
+
// 1. Init a bare rcf project (no applied blueprints).
|
|
46
|
+
const init = await runNode([RCF_BIN, 'init'], { cwd: scratch });
|
|
47
|
+
if (init.code !== 0) {
|
|
48
|
+
return [{
|
|
49
|
+
anchorAcId: 'AC-jobs-requiresQueue',
|
|
50
|
+
verdict: 'fail',
|
|
51
|
+
detail: `rcf init failed exit=${init.code} stderr=${init.stderr}`,
|
|
52
|
+
}];
|
|
53
|
+
}
|
|
54
|
+
// 2. Attempt to apply jobs-background on the bare project.
|
|
55
|
+
const apply = await runNode(
|
|
56
|
+
[RCF_BIN, 'define', 'blueprint', 'add', BLUEPRINT_DIR],
|
|
57
|
+
{ cwd: scratch },
|
|
58
|
+
);
|
|
59
|
+
const results = [];
|
|
60
|
+
const firstLine = apply.stderr.split('\n')[0] ?? '';
|
|
61
|
+
const tagMatch = firstLine.includes('[jobs-background-no-queue]');
|
|
62
|
+
const providerMatch = apply.stderr.includes('messaging-queue-cloudflare');
|
|
63
|
+
const overrideMatch = apply.stderr.includes('--allow-no-queue-yet');
|
|
64
|
+
const codeMatch = apply.code === 3;
|
|
65
|
+
const pass = codeMatch && tagMatch && providerMatch && overrideMatch;
|
|
66
|
+
results.push({
|
|
67
|
+
anchorAcId: 'AC-jobs-requiresQueue',
|
|
68
|
+
verdict: pass ? 'pass' : 'fail',
|
|
69
|
+
detail: pass
|
|
70
|
+
? `exit=${apply.code}; stderr first line carries [jobs-background-no-queue] tag; stderr names messaging-queue-cloudflare; stderr names --allow-no-queue-yet`
|
|
71
|
+
: `expected exit=3 AND first-line tag [jobs-background-no-queue] AND messaging-queue-cloudflare AND --allow-no-queue-yet; got exit=${apply.code}; firstLine='${firstLine}'; tagMatch=${tagMatch}; providerMatch=${providerMatch}; overrideMatch=${overrideMatch}`,
|
|
72
|
+
});
|
|
73
|
+
// Do NOT include the scratch dir path in the committed report; it is
|
|
74
|
+
// a per-run tmp path and would leak the machine's tmp naming into the
|
|
75
|
+
// gate-time evidence file.
|
|
76
|
+
return { results, extra: { exitCode: apply.code, stderrFirstLine: firstLine } };
|
|
77
|
+
} finally {
|
|
78
|
+
// rm -rf the scratch dir (deletion discipline: literal absolute
|
|
79
|
+
// scratch path created this call).
|
|
80
|
+
try { await rm(scratch, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event-secrecy probe.
|
|
3
|
+
*
|
|
4
|
+
* Schedules a job whose input carries a PII fixture body
|
|
5
|
+
* { userId: 1234, ssn: '123-45-6789', email: 'test@example.com' }.
|
|
6
|
+
* Drives the job through completion. Grep the serialised run-log event
|
|
7
|
+
* stream for every PII literal; asserts zero matches.
|
|
8
|
+
*
|
|
9
|
+
* Anchors AC-jobs-eventSecrecy.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createQueuePair } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/queue-driver.mjs';
|
|
13
|
+
import { createProducer, queueConfigFromEnv } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/producer.mjs';
|
|
14
|
+
import { createRunLog } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/job-run-log.mjs';
|
|
15
|
+
import { loadJobs, createJobsRuntime } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/jobs-runtime.mjs';
|
|
16
|
+
import { createScheduler, createFakeClock } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/scheduler.mjs';
|
|
17
|
+
import { PROJECT_ROOT } from './probe-utils.mjs';
|
|
18
|
+
import { resolve } from 'node:path';
|
|
19
|
+
|
|
20
|
+
const PII_LITERALS = ['1234', '123-45-6789', 'test@example.com'];
|
|
21
|
+
const EVENT_WHITELIST = new Set(['event', 'jobId', 'jobName', 'attempts', 'duration', 'timestamp', 'terminalErrorCode']);
|
|
22
|
+
|
|
23
|
+
export default async function runProbe() {
|
|
24
|
+
const cfg = queueConfigFromEnv();
|
|
25
|
+
const pair = createQueuePair({ queueName: cfg.queueName, dlqName: cfg.dlqName, maxRetries: cfg.maxRetries });
|
|
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_PII_IN_JOB_INPUT: 'true' },
|
|
40
|
+
});
|
|
41
|
+
const clock = createFakeClock(0);
|
|
42
|
+
const scheduler = createScheduler({ mode: 'inProcess', clock, publisher: producer, runLog });
|
|
43
|
+
scheduler.delayed({
|
|
44
|
+
jobName: 'send-welcome-email',
|
|
45
|
+
delayMs: 0,
|
|
46
|
+
input: { userId: 1234, ssn: '123-45-6789', email: 'test@example.com' },
|
|
47
|
+
});
|
|
48
|
+
clock.advance(1);
|
|
49
|
+
await scheduler.tick();
|
|
50
|
+
await runtime.drain({ driverState: pair.state, maxIterations: 8 });
|
|
51
|
+
const serialised = JSON.stringify(events);
|
|
52
|
+
const leaks = PII_LITERALS.filter((lit) => serialised.includes(lit));
|
|
53
|
+
const wrongKeys = new Set();
|
|
54
|
+
for (const ev of events) {
|
|
55
|
+
for (const k of Object.keys(ev)) {
|
|
56
|
+
if (!EVENT_WHITELIST.has(k)) wrongKeys.add(k);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const results = [];
|
|
60
|
+
const pass = leaks.length === 0 && wrongKeys.size === 0 && events.some((e) => e.event === 'jobCompleted');
|
|
61
|
+
results.push({
|
|
62
|
+
anchorAcId: 'AC-jobs-eventSecrecy',
|
|
63
|
+
verdict: pass ? 'pass' : 'fail',
|
|
64
|
+
detail: pass
|
|
65
|
+
? `no PII literal appears in the serialised run-log stream; every event carries only whitelisted keys ${JSON.stringify([...EVENT_WHITELIST])}; ${events.length} events recorded`
|
|
66
|
+
: `leaks=${JSON.stringify(leaks)}; wrongKeys=${JSON.stringify([...wrongKeys])}; events=${JSON.stringify(events)}`,
|
|
67
|
+
});
|
|
68
|
+
return { results, extra: { events, piiLiterals: PII_LITERALS } };
|
|
69
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fake-clock cron probe.
|
|
3
|
+
*
|
|
4
|
+
* Drives the fixture scheduler (inProcess mode with the fake-clock seam)
|
|
5
|
+
* plus the T-3 in-memory queue seam plus the T-4 jobs-runtime through
|
|
6
|
+
* one POSIX cron minute for the refresh-cache job. Asserts a jobStarted
|
|
7
|
+
* event fires on the injected run-log sink within the elicited
|
|
8
|
+
* fireToleranceMs window (default 30000 ms) with jobName 'refresh-cache'
|
|
9
|
+
* and attempts 1; asserts a jobCompleted event fires within the elicited
|
|
10
|
+
* timeoutMs (10000 ms for refresh-cache).
|
|
11
|
+
*
|
|
12
|
+
* Anchors AC-jobs-scheduledRunsOnCron.
|
|
13
|
+
*
|
|
14
|
+
* NOTE: this probe runs against the in-memory queue seam and the fake-
|
|
15
|
+
* clock, not a live wrangler dev process. The README's per-AC
|
|
16
|
+
* mechanism-reach section names the live-only follow-up per SDR-3-a.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createQueuePair } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/queue-driver.mjs';
|
|
20
|
+
import { createProducer, queueConfigFromEnv } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/producer.mjs';
|
|
21
|
+
import { createRunLog } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/job-run-log.mjs';
|
|
22
|
+
import { loadJobs, createJobsRuntime } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/jobs-runtime.mjs';
|
|
23
|
+
import { createScheduler, createFakeClock } from '../../../../packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/scheduler.mjs';
|
|
24
|
+
import { PROJECT_ROOT } from './probe-utils.mjs';
|
|
25
|
+
import { resolve } from 'node:path';
|
|
26
|
+
|
|
27
|
+
const FIRE_TOLERANCE_MS = 30000;
|
|
28
|
+
|
|
29
|
+
export default async function runProbe() {
|
|
30
|
+
const cfg = queueConfigFromEnv();
|
|
31
|
+
const pair = createQueuePair({ queueName: cfg.queueName, dlqName: cfg.dlqName, maxRetries: cfg.maxRetries });
|
|
32
|
+
const events = [];
|
|
33
|
+
const runLog = createRunLog({ upstream: (e) => events.push(e) });
|
|
34
|
+
const producer = createProducer({
|
|
35
|
+
binding: pair.producer,
|
|
36
|
+
queueName: cfg.queueName,
|
|
37
|
+
onEvent: () => {},
|
|
38
|
+
});
|
|
39
|
+
await producer.ready;
|
|
40
|
+
const jobsDir = resolve(PROJECT_ROOT, 'packages/rcf-lite/test/fixtures/infra-s3-and-queue/jobs');
|
|
41
|
+
const registry = await loadJobs(jobsDir);
|
|
42
|
+
const runtime = createJobsRuntime({ jobRegistry: registry, runLog, env: { ...process.env } });
|
|
43
|
+
const clock = createFakeClock(0);
|
|
44
|
+
const scheduler = createScheduler({ mode: 'inProcess', clock, publisher: producer, runLog });
|
|
45
|
+
scheduler.cron({ jobName: 'refresh-cache', cronString: '* * * * *', input: { cacheKey: 'demo' } });
|
|
46
|
+
// Advance clock past one cron minute; tick to publish.
|
|
47
|
+
clock.advance(60000);
|
|
48
|
+
const fires = await scheduler.tick();
|
|
49
|
+
// Drain runtime.
|
|
50
|
+
await runtime.drain({ driverState: pair.state, maxIterations: 16 });
|
|
51
|
+
const jobStarted = events.find((e) => e.event === 'jobStarted' && e.jobName === 'refresh-cache');
|
|
52
|
+
const jobCompleted = events.find((e) => e.event === 'jobCompleted' && e.jobName === 'refresh-cache');
|
|
53
|
+
const results = [];
|
|
54
|
+
const startPass = !!jobStarted && jobStarted.attempts === 1;
|
|
55
|
+
const durationOk = jobCompleted ? jobCompleted.duration <= registry.get('refresh-cache').timeoutMs : false;
|
|
56
|
+
const pass = fires === 1 && startPass && !!jobCompleted && durationOk;
|
|
57
|
+
results.push({
|
|
58
|
+
anchorAcId: 'AC-jobs-scheduledRunsOnCron',
|
|
59
|
+
verdict: pass ? 'pass' : 'fail',
|
|
60
|
+
detail: pass
|
|
61
|
+
? `fires=1; jobStarted refresh-cache attempts=1 within fireToleranceMs=${FIRE_TOLERANCE_MS}; jobCompleted duration=${jobCompleted.duration}ms within timeoutMs=${registry.get('refresh-cache').timeoutMs}`
|
|
62
|
+
: `fires=${fires}; jobStarted=${JSON.stringify(jobStarted)}; jobCompleted=${JSON.stringify(jobCompleted)}; durationOk=${durationOk}`,
|
|
63
|
+
});
|
|
64
|
+
return { results, extra: { events, fireToleranceMs: FIRE_TOLERANCE_MS } };
|
|
65
|
+
}
|