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,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "messaging-queue-cloudflare-REQ-004",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Dead-letter contract: elicited max-attempts default 3, DLQ retention 4 days, dead_letter_queue field on the consumer",
|
|
5
|
+
"description": "After the elicited max-attempts (default 3 per the Cloudflare Queues documented default) is reached on a message, the message lands on the configured dead-letter queue (DLQ) and the facade fires messageDeadLettered on the sink with the stable message-id, the queue name, and the terminal attempt count. The DLQ is declared per-consumer in wrangler config through the dead_letter_queue field (per https://developers.cloudflare.com/queues/configuration/dead-letter-queues/); the field syntax in wrangler.toml is dead_letter_queue = \"<dlq-queue-name>\" alongside max_retries on the [[queues.consumers]] block. The DLQ retention defaults to 4 days when no active consumer is attached per the same Cloudflare DLQ documentation. Consumer code can attach a DLQ consumer to drain the DLQ (via wrangler queues consumer add) for inspection or replay per the same Cloudflare DLQ documentation.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "messaging",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "A DLQ contract with a bounded retention and an observable landing event is what stops a bad message from blocking the queue indefinitely, and gives the operator a receipt for the failure. The 4-day retention default matches the Cloudflare Queues DLQ documentation and is long enough for a human to inspect, short enough that the DLQ does not become a silent archive.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:messaging-queue-cloudflare",
|
|
12
|
+
"category:04-dead-letter"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "messaging-queue-cloudflare-REQ-005",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Batch consume: elicited batch size default 10 max 100, batch timeout default 5 seconds max 60 seconds",
|
|
5
|
+
"description": "The consumer reads messages in batches whose size is elicited (default 10, bounded above by the Cloudflare max 100 messages per batch per https://developers.cloudflare.com/queues/platform/limits/) and whose batch-timeout (batch wait) is elicited (default 5 seconds, bounded above by the Cloudflare max 60 seconds per the same limits doc). Batch settings live on the [[queues.consumers]] block in wrangler config as max_batch_size and max_batch_timeout. When the batch is full or the timeout fires, the Worker's queue handler receives the whole batch in one invocation; the facade dispatches per-message to the registered handler and acknowledges per-message. A batch member returning retry does not roll the whole batch back; only that message is redelivered.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "messaging",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "Per-message ack in a batch invocation is what enables the retry-and-DLQ contract to compose with batched delivery; without it a single bad message poisons the whole batch. The elicited defaults sit well below the Cloudflare maxes so the facade shape stays composable when a project runs closer to the ceiling.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:messaging-queue-cloudflare",
|
|
12
|
+
"category:05-batch"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reqId": "messaging-queue-cloudflare-REQ-006",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"title": "Lifecycle events with metadata-only fields: producerReady, messagePublished, messageAcked, messageDeadLettered",
|
|
5
|
+
"description": "The facade fires four lifecycle events on the injected event sink: producerReady (once per facade instance, on the first successful ready-check), messagePublished (once per successful publish), messageAcked (once per successful ack), messageDeadLettered (once per DLQ landing). Every event record carries a metadata-only field whitelist: messageId, queueName, attempts (integer, 0 for producerReady and messagePublished), timestamp (ISO 8601 UTC). No event record contains the message body, any header value, any consumer-context field, or any producer-context field. The whitelist is enforced in code (the sink adapter refuses forbidden fields); a call site that hands a body-bearing payload to the sink is refused at author-side review.",
|
|
6
|
+
"category": "functional",
|
|
7
|
+
"domain": "messaging",
|
|
8
|
+
"priority": "must",
|
|
9
|
+
"rationale": "A metadata-only event whitelist is what turns the event sink into a leak-proof observability seam. The applied logging companion writes these records verbatim; the applied error-handling companion constructs error records from messageDeadLettered records. Neither ever sees the message body through this surface, which is what allows the shipped shape to compose with a PII-bearing body without redaction gymnastics at every log site.",
|
|
10
|
+
"tags": [
|
|
11
|
+
"blueprint:messaging-queue-cloudflare",
|
|
12
|
+
"category:06-events"
|
|
13
|
+
],
|
|
14
|
+
"version": "1.0.0",
|
|
15
|
+
"status": "approved",
|
|
16
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
17
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
18
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tacId": "TAC-3001-messaging-queue-cloudflare-producer-facade",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"name": "Messaging-queue-cloudflare producer facade",
|
|
8
|
+
"purpose": "The single project-side module that dereferences the Cloudflare Queues binding (env.<queueBinding>), exposes narrow named domain verbs (publish, publishBatch) to consumer code, and emits structured lifecycle events to the event sink (TAC-3003). Consumers never touch the raw binding outside this module; the facade is the one place the vendor surface lives. The adapter reference is opaque at this boundary so a Workers-side native adapter, a future sibling (an SQS shim, the reserved messaging-queue-postgres sibling) or a fixture-time in-memory adapter slots in without a facade re-shape.",
|
|
9
|
+
"responsibilities": [
|
|
10
|
+
"Open the producer on boot against the elicited queue binding; hold no other reference to the Queues vendor surface; export no symbol that leaks the raw binding (AC-29101-1, AC-29101-2).",
|
|
11
|
+
"Expose named domain verbs (publish, publishBatch) as named exports with typed parameters; refuse a raw-binding passthrough on the public surface (AC-29102-1).",
|
|
12
|
+
"Emit producerReady exactly once per facade instance on the injected event sink with the queueName (AC-29101-1); serialise pre-ready publishes behind the ready-check.",
|
|
13
|
+
"Emit messagePublished per publish and messageAcked per successful consumer return, both metadata-only per TAC-3003 (AC-29102-1, AC-29102-2, AC-29105-1).",
|
|
14
|
+
"Attach a producer-supplied trace-id to the message headers on every publish so downstream consumers can stitch enqueue-to-process (AC-29102-1)."
|
|
15
|
+
],
|
|
16
|
+
"internalStructure": "One module exporting a createProducer({ binding, queueName, adapter, onEvent }) factory returning a Producer whose methods are the named domain verbs. The binding dereference lives at the top of this module; the factory captures the binding, wires the sink, and hands back closures. Consumers destructure the verbs they need at their top level and hold the facade reference for the process lifetime.",
|
|
17
|
+
"interfaces": [
|
|
18
|
+
{
|
|
19
|
+
"name": "createProducer",
|
|
20
|
+
"kind": "factory",
|
|
21
|
+
"description": "createProducer({ binding, queueName, adapter, onEvent }) -> Producer. The sole entry point every publisher calls. Opens against the binding; wires the sink; hands back a Producer whose methods are the named domain verbs."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "onEvent",
|
|
25
|
+
"kind": "callback",
|
|
26
|
+
"description": "Structured-event sink invoked with { event, ts, messageId, queueName, attempts } for producerReady, messagePublished, messageAcked, messageDeadLettered events. Never invoked with message body values, header values or consumer-context fields."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "producer.verbs",
|
|
30
|
+
"kind": "read-write-api",
|
|
31
|
+
"description": "The named domain verbs the facade exposes: publish(body, headers), publishBatch(messages)."
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"dependencies": [
|
|
35
|
+
{
|
|
36
|
+
"name": "Consumer registration",
|
|
37
|
+
"kind": "tac",
|
|
38
|
+
"tacId": "TAC-3002-messaging-queue-cloudflare-consumer-registration",
|
|
39
|
+
"description": "The consumer surface on the Worker queue handler that pairs with this producer; both share the sink and the queueName."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "Event sink",
|
|
43
|
+
"kind": "tac",
|
|
44
|
+
"tacId": "TAC-3003-messaging-queue-cloudflare-event-sink",
|
|
45
|
+
"description": "The lifecycle-event sink contract the facade emits through with metadata-only field discipline."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "Queue binding via deploy-cloudflare-workers",
|
|
49
|
+
"kind": "external",
|
|
50
|
+
"description": "The applied deploy-cloudflare-workers blueprint supplies the wrangler config in which the queue binding is declared; the facade reads the binding at boot. Composition is transitive through the wrangler binding, not through the capability mechanism, so no requiresAppliedCapabilities block is declared."
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"tradeoffs": "The facade holds a single Producer instance per queue at v1.0.0; a project publishing to multiple queues creates multiple facade instances. The adapter reference is opaque at ADR-3001 so a future SQS shim, an in-memory adapter, or the reserved messaging-queue-postgres sibling slots in as a per-target module swap; the outward interface stays unchanged.",
|
|
54
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
55
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
56
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tacId": "TAC-3002-messaging-queue-cloudflare-consumer-registration",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"name": "Messaging-queue-cloudflare consumer registration on the Worker queue handler",
|
|
8
|
+
"purpose": "The consumer registration surface on the Worker's queue handler that pairs with the producer facade (TAC-3001). Reads a batch of messages from the batch envelope, dispatches per-message to the registered handler, acknowledges per-message on success, returns retry on retryable failure, and drives the retry-to-DLQ trajectory bounded by the elicited max-attempts (ADR-3003) and batch settings (ADR-3004).",
|
|
9
|
+
"responsibilities": [
|
|
10
|
+
"Provide createConsumer({ handler, onEvent }) returning a queue-handler function (env, ctx) that reads the batch envelope, dispatches per-message, and acks or retries per return (AC-29102-2, AC-29103-1, AC-29105-1).",
|
|
11
|
+
"Expose the elicited batch size (max_batch_size default 10, ceiling 100) and batch timeout (max_batch_timeout default 5 seconds, ceiling 60 seconds) as parameters realised through wrangler config on the [[queues.consumers]] block (AC-29105-1).",
|
|
12
|
+
"Classify a handler throw as retryable or terminal and re-deliver retryable throws via return retry with the attempt counter increment observable on subsequent deliveries (AC-29103-1).",
|
|
13
|
+
"Emit messageDeadLettered on the sink when the DLQ landing observation surfaces (via a Worker DLQ-consumer wiring the fixture DLQ-inspector helper exercises for the shipped shape) (AC-29104-1)."
|
|
14
|
+
],
|
|
15
|
+
"internalStructure": "One module exporting createConsumer({ handler, onEvent }) that returns a queue handler (batch, env, ctx). The handler shape matches the Cloudflare Queues Worker queue-handler contract per https://developers.cloudflare.com/queues/configuration/javascript-apis/#consumer. Per-message ack semantics come from the message envelope's ack and retry methods.",
|
|
16
|
+
"interfaces": [
|
|
17
|
+
{
|
|
18
|
+
"name": "createConsumer",
|
|
19
|
+
"kind": "factory",
|
|
20
|
+
"description": "createConsumer({ handler, onEvent }) -> (batch, env, ctx) => Promise<void>. The exported queue handler the Worker registers under the [triggers] queue binding. The handler is invoked once per delivered message and returns a value used to classify success/retry/terminal."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"dependencies": [
|
|
24
|
+
{
|
|
25
|
+
"name": "Producer facade",
|
|
26
|
+
"kind": "tac",
|
|
27
|
+
"tacId": "TAC-3001-messaging-queue-cloudflare-producer-facade",
|
|
28
|
+
"description": "The producer this consumer pairs with; both share the sink and the queueName."
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "Event sink",
|
|
32
|
+
"kind": "tac",
|
|
33
|
+
"tacId": "TAC-3003-messaging-queue-cloudflare-event-sink",
|
|
34
|
+
"description": "The lifecycle-event sink contract the consumer emits through with metadata-only field discipline."
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"tradeoffs": "Per-message ack over batch-level ack matches Cloudflare's Queues Worker queue-handler contract; a batch-level rollback would be simpler but would poison-batch every peer of a single bad message and defeat the DLQ contract. The elicited batch defaults sit well below Cloudflare's maxes so tightening in a project is a config change, not a code change.",
|
|
38
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
39
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
40
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tacId": "TAC-3003-messaging-queue-cloudflare-event-sink",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"name": "Messaging-queue-cloudflare lifecycle event sink",
|
|
8
|
+
"purpose": "The metadata-only lifecycle event sink surface both the producer facade (TAC-3001) and the consumer registration (TAC-3002) emit through. Fires four events (producerReady, messagePublished, messageAcked, messageDeadLettered) with a rigid field whitelist; the whitelist is enforced in code so a call site handing a body-bearing payload to the sink is refused by the sink adapter before the record leaves the module.",
|
|
9
|
+
"responsibilities": [
|
|
10
|
+
"Expose an onEvent callback shape { event, ts, messageId, queueName, attempts } to the facade and consumer registration (AC-29106-1).",
|
|
11
|
+
"Enforce the field whitelist: exactly {event, ts, messageId, queueName, attempts} are allowed on every record; any additional field on the payload is stripped at the sink boundary and the sink adapter surfaces a soft warning if a caller attempts to hand it a body-bearing payload (AC-29106-1).",
|
|
12
|
+
"Never invoke the callback with body bytes, header values, consumer-context fields, producer-context fields, or PII from the fixture body (AC-29106-1)."
|
|
13
|
+
],
|
|
14
|
+
"internalStructure": "One module exporting createSinkAdapter({ onEvent }) that wraps the caller's onEvent with the field-whitelist filter. The wrapped adapter is what the producer facade and the consumer registration receive; the raw onEvent is never invoked from inside the facade or the consumer.",
|
|
15
|
+
"interfaces": [
|
|
16
|
+
{
|
|
17
|
+
"name": "createSinkAdapter",
|
|
18
|
+
"kind": "factory",
|
|
19
|
+
"description": "createSinkAdapter({ onEvent }) -> (record) => void. Filters record fields to the whitelist and passes the filtered record to the caller-supplied onEvent."
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"dependencies": [
|
|
23
|
+
{
|
|
24
|
+
"name": "Producer facade",
|
|
25
|
+
"kind": "tac",
|
|
26
|
+
"tacId": "TAC-3001-messaging-queue-cloudflare-producer-facade",
|
|
27
|
+
"description": "Emits producerReady, messagePublished, messageAcked through the sink adapter."
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "Consumer registration",
|
|
31
|
+
"kind": "tac",
|
|
32
|
+
"tacId": "TAC-3002-messaging-queue-cloudflare-consumer-registration",
|
|
33
|
+
"description": "Emits messageDeadLettered through the sink adapter after the DLQ landing observation surfaces."
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"tradeoffs": "A hard whitelist in code beats a documented-only convention: the sink cannot be misused because the boundary strips forbidden fields before they reach the caller's log. The trade-off is that adding a new whitelist field is a v1.1.0 minor bump, not a project-side config; the shipped shape is deliberate to keep the leak-proof property.",
|
|
37
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
38
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
39
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "messaging-queue-cloudflare-US-29101",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "messaging-queue-cloudflare-REQ-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "One producer facade module opens the Queues binding on boot and emits producerReady with the queue name",
|
|
8
|
+
"asA": "developer whose Worker or Node service needs a directed producer-to-consumer worklist behind a swappable boundary",
|
|
9
|
+
"iWant": "one producer facade module to hold the Cloudflare Queues binding reference and expose typed domain verbs (publish, publishBatch), with a producerReady lifecycle event on the first successful ready-check",
|
|
10
|
+
"soThat": "the boundary between my domain and the Queues vendor surface is the only place that has to move when the adapter changes, and boot ordering is observable via the event",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-29101-1",
|
|
14
|
+
"description": "On process boot, the facade opens against the elicited queue binding (env.RCF_TEST_QUEUE on the fixture); once the first successful ready-check returns, a producerReady event fires on the injected event sink with a payload containing queueName. The facade exposes typed named domain verbs (publish, publishBatch) and does not re-export the raw Queues binding.",
|
|
15
|
+
"given": "a Worker or Node process with the facade module imported and its factory invoked with the elicited binding and queue name",
|
|
16
|
+
"when": "the process boots and the facade ready-check returns",
|
|
17
|
+
"then": "a producerReady event fires on the injected event sink with a payload containing queueName; the facade exposes named domain verbs (publish, publishBatch) and does not re-export the raw Queues binding",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "AC-29101-2",
|
|
23
|
+
"description": "Exactly one source-tree module inside the facade directory holds the reference to the Cloudflare Queues binding (env.RCF_TEST_QUEUE in the fixture); every other module that publishes imports the facade and calls its named domain verbs, verified by a targeted grep on the applied fixture.",
|
|
24
|
+
"given": "the applied project's source tree after apply",
|
|
25
|
+
"when": "grep for a Queues binding dereference walks the tree outside the facade directory",
|
|
26
|
+
"then": "no match is found outside the facade directory; the facade's public surface exports named domain verbs only, no raw binding reference",
|
|
27
|
+
"testable": true,
|
|
28
|
+
"scope": "runtime"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"tacIds": ["TAC-3001-messaging-queue-cloudflare-producer-facade"],
|
|
32
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
33
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "messaging-queue-cloudflare-US-29102",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "messaging-queue-cloudflare-REQ-002",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Publish delivers to the consumer with matching body, message-id and producer-supplied trace-id",
|
|
8
|
+
"asA": "developer publishing a domain event through the producer facade",
|
|
9
|
+
"iWant": "a message published through publish() to reach the registered consumer with body byte-equal and headers including a stable message-id and my producer-supplied trace-id",
|
|
10
|
+
"soThat": "downstream traceability across the enqueue-to-process boundary is a byte-for-byte property, not a hopeful convention",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-29102-1",
|
|
14
|
+
"description": "Given a message published through the facade with a producer-supplied trace-id in headers, when the consumer subscribes on the same queue, then the received body equals the published body byte-for-byte and the received headers include the stable message-id assigned by the platform on send plus the producer-supplied trace-id, verified across all five messages in the publish-to-delivery probe.",
|
|
15
|
+
"given": "5 messages published through the facade with producer-supplied trace-ids",
|
|
16
|
+
"when": "the consumer processes the queue batch",
|
|
17
|
+
"then": "all 5 messages are received with body byte-equal, message-id present and stable per message across the delivery, and the producer-supplied trace-id present on the message headers",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "AC-29102-2",
|
|
23
|
+
"description": "Given a successful consumer return on a message, when the facade dispatches the batch, then a messageAcked event fires on the injected sink with messageId and queueName matching the received message; the message is not redelivered on subsequent batches.",
|
|
24
|
+
"given": "a message delivered to the registered consumer",
|
|
25
|
+
"when": "the consumer returns success",
|
|
26
|
+
"then": "a messageAcked event fires with messageId and queueName; the message is not redelivered",
|
|
27
|
+
"testable": true,
|
|
28
|
+
"scope": "runtime"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"tacIds": ["TAC-3001-messaging-queue-cloudflare-producer-facade", "TAC-3002-messaging-queue-cloudflare-consumer-registration"],
|
|
32
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
33
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
34
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "messaging-queue-cloudflare-US-29103",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "messaging-queue-cloudflare-REQ-003",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Consumer returning retry re-delivers the same message-id with an incremented attempt counter",
|
|
8
|
+
"asA": "developer whose consumer needs to handle transient failures without human intervention",
|
|
9
|
+
"iWant": "a consumer that returns retry to observe the same message-id on the next delivery with an incremented attempt counter",
|
|
10
|
+
"soThat": "policy decisions like log at attempt 2, alert at attempt N-1 are trivially expressed in code",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-29103-1",
|
|
14
|
+
"description": "Given the SIMULATE_CONSUMER_RETRY=true induced-failure switch on the fixture, when the consumer returns retry on the first delivery, then the second delivery observes the same stable message-id with attempts incremented to 2; the same holds on the third delivery with attempts 3. The retry-and-dlq probe records the full trajectory 1 to 3 attempts on the same message-id.",
|
|
15
|
+
"given": "a message published with the SIMULATE_CONSUMER_RETRY switch true on the fixture",
|
|
16
|
+
"when": "the consumer is invoked across successive deliveries",
|
|
17
|
+
"then": "the message-id is stable across attempts 1 to 3; the attempt counter increments on each delivery; body, headers and producer-supplied trace-id remain byte-equal across attempts",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3002-messaging-queue-cloudflare-consumer-registration"],
|
|
23
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "messaging-queue-cloudflare-US-29104",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "messaging-queue-cloudflare-REQ-004",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "After max-attempts the message lands on the DLQ with messageDeadLettered and the DLQ contents match",
|
|
8
|
+
"asA": "developer whose consumer needs a receipt for permanent failures without blocking the queue",
|
|
9
|
+
"iWant": "a message that fails past max-attempts to land on the configured DLQ with a lifecycle event and inspectable DLQ contents",
|
|
10
|
+
"soThat": "operational triage of a bad message is a matter of reading the DLQ, not of grepping application logs",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-29104-1",
|
|
14
|
+
"description": "Given a message that returns retry across max_retries deliveries (default 3), when the third delivery returns retry, then a messageDeadLettered event fires on the sink with the stable message-id, the queue name, and attempts equal to the terminal attempt count. The message is present on the DLQ; inspecting the DLQ through the fixture DLQ-inspector helper returns the body byte-equal to the original publish.",
|
|
15
|
+
"given": "the fixture with max_retries 3 and SIMULATE_CONSUMER_RETRY=true published once",
|
|
16
|
+
"when": "the delivery loop runs to exhaustion",
|
|
17
|
+
"then": "the retry-and-dlq probe records attempts 1, 2, 3 with the same message-id; messageDeadLettered fires with attempts equal to the terminal count; the DLQ-inspector reads the message body back byte-equal",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3002-messaging-queue-cloudflare-consumer-registration", "TAC-3003-messaging-queue-cloudflare-event-sink"],
|
|
23
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "messaging-queue-cloudflare-US-29105",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "messaging-queue-cloudflare-REQ-005",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Batch consume bounded by Cloudflare limits with per-message ack semantics",
|
|
8
|
+
"asA": "developer publishing bursty traffic",
|
|
9
|
+
"iWant": "the consumer to process a batch of messages in one Worker invocation with the elicited batch size and batch timeout, acking each message individually",
|
|
10
|
+
"soThat": "one bad message in a batch does not roll back the twenty good ones behind it",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-29105-1",
|
|
14
|
+
"description": "Given the fixture consumer configured with max_batch_size 10 and max_batch_timeout 5, when 5 messages are published in rapid succession, then the consumer receives them in one batched invocation and acknowledges each per message; every message is delivered exactly to the count published (barring redeliveries the AC does not test); no batch-level rollback fires on a per-message retry.",
|
|
15
|
+
"given": "the fixture consumer with the elicited batch settings",
|
|
16
|
+
"when": "the publish-to-delivery probe publishes 5 messages",
|
|
17
|
+
"then": "the consumer receives them in one batch; per-message ack fires 5 times; the messageAcked events on the sink number 5",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3002-messaging-queue-cloudflare-consumer-registration"],
|
|
23
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "messaging-queue-cloudflare-US-29106",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "messaging-queue-cloudflare-REQ-006",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Every lifecycle event carries a metadata-only whitelist with no body bytes, no header values and no consumer context",
|
|
8
|
+
"asA": "developer whose queue traffic may carry PII bodies",
|
|
9
|
+
"iWant": "the lifecycle event whitelist to be enforced in code so my consumer logging cannot accidentally leak a body byte, a header value, or a consumer-context field",
|
|
10
|
+
"soThat": "the shipped shape composes with a PII-bearing body without redaction gymnastics at every log site",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-29106-1",
|
|
14
|
+
"description": "Given the SIMULATE_PII_IN_BODY=true induced-failure switch driving publish and ack with a PII fixture body (userId 1234, ssn 123-45-6789), when the event-secrecy probe captures the events, then every event record carries exactly the whitelist fields (event, messageId, queueName, attempts, timestamp) and no more; no body bytes, no header value, no userId string, no ssn string, no consumer-context field appears in any record, verified by JSON-stringify substring grep.",
|
|
15
|
+
"given": "the PII fixture body driven through publish, consume and ack",
|
|
16
|
+
"when": "the event-secrecy probe reads the captured event records",
|
|
17
|
+
"then": "every record carries only the whitelist; JSON-stringify substring grep for the fixture PII strings returns no match on any record",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3003-messaging-queue-cloudflare-event-sink"],
|
|
23
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "messaging-queue-cloudflare-US-29107",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "messaging-queue-cloudflare-REQ-002",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Wrangler-dev round-trip: full publish-to-delivery cycle observable under wrangler dev",
|
|
8
|
+
"asA": "developer running the fixture under wrangler dev on my laptop",
|
|
9
|
+
"iWant": "the producer-facade-ready and publish-to-delivery cycles to complete inside the fixture wrangler-dev seam without a real Cloudflare account",
|
|
10
|
+
"soThat": "I can iterate on the facade contract without a live account and CI can gate a change without live-account access",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-29108-1",
|
|
14
|
+
"description": "Given the fixture booted with the in-memory queue-driver mode (the wrangler-dev-equivalent seam the probes drive), when the producer-facade-ready and publish-to-delivery probes run in sequence, then producerReady fires with the queueName, publish delivers all 5 messages to the consumer with body byte-equal, and the per-blueprint reports at .rcf/reports/blueprints/messaging-queue-cloudflare/producer-facade-ready.json and publish-to-delivery.json each carry aggregateVerdict pass.",
|
|
15
|
+
"given": "the fixture with the in-memory queue-driver mode active (SDR-3-a: wrangler-dev-equivalent seam per the local-development doc's stated limitations)",
|
|
16
|
+
"when": "the producer-facade-ready and publish-to-delivery run-shims execute",
|
|
17
|
+
"then": "each shim exits 0; the per-blueprint reports carry aggregateVerdict pass; the event records observed include producerReady with queueName and 5 messageAcked events with matching messageIds",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3001-messaging-queue-cloudflare-producer-facade", "TAC-3002-messaging-queue-cloudflare-consumer-registration"],
|
|
23
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "messaging-queue-cloudflare-US-29108",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "messaging-queue-cloudflare-REQ-002",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Real-account concurrency smoke: 500 messages processed concurrently up to the 250 cap",
|
|
8
|
+
"asA": "developer verifying the facade works against a real Cloudflare account",
|
|
9
|
+
"iWant": "a smoke test that publishes 500 messages against the shared HQ queue rcf-lite-ci-queue-smoke and asserts the consumer processes them concurrently up to the documented 250-invocation cap",
|
|
10
|
+
"soThat": "the shipped shape's compatibility with the real Cloudflare Queues surface is proven end-to-end at least once per release, without requiring every CI run to be account-bound",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-29108-2",
|
|
14
|
+
"description": "Given CI_HAS_CLOUDFLARE_ACCOUNT=1 and the credentials for the shared HQ queue rcf-lite-ci-queue-smoke wired via security-secrets-management, when the real-account-concurrency-smoke probe runs against the real queue, then 500 messages are published, the consumer processes them concurrently up to the 250 push-based invocation cap Cloudflare documents at https://developers.cloudflare.com/queues/platform/limits/, and the smoke drains the queue on exit. Without CI_HAS_CLOUDFLARE_ACCOUNT the probe records accountBoundSkipped true per spec section 3.5 and the per-blueprint report carries aggregateVerdict pass.",
|
|
15
|
+
"given": "the shared HQ queue rcf-lite-ci-queue-smoke on Baz's account, or the env var unset",
|
|
16
|
+
"when": "the real-account-concurrency-smoke run-shim executes",
|
|
17
|
+
"then": "with the env var set, 500 messages round-trip with observed concurrency below or equal to 250; with the env var unset, the report carries accountBoundSkipped true and aggregateVerdict pass; the shim exits 0 in both paths",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3001-messaging-queue-cloudflare-producer-facade", "TAC-3002-messaging-queue-cloudflare-consumer-registration"],
|
|
23
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# messaging-queue-cloudflare blueprint coordination vocabulary
|
|
2
|
+
|
|
3
|
+
This file is the messaging-queue-cloudflare half of the cross-blueprint contract. The Phase 1 conflict detector matches scope:global ADR topics by EXACT string equality, and AC ids are unnamespaced by the 0.4.4 grammar. Any blueprint intended to compose with this one must reuse these exact strings and respect these bands.
|
|
4
|
+
|
|
5
|
+
## Global ADR topics this blueprint contributes (exact strings)
|
|
6
|
+
|
|
7
|
+
| Topic string | messaging-queue-cloudflare contribution | Origin | Composition note |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `deliverySemantics` | ADR-3002-messaging-queue-cloudflare-delivery-semantics | Minted by this blueprint. Names the wire-shape contract for message-delivery guarantees; the shipped answer is at-least-once (Cloudflare Queues implements at-least-once with a stable message-id the consumer uses for its own dedup per https://developers.cloudflare.com/queues/configuration/javascript-apis/, and WSD-005 makes at-least-once and out-of-order tolerance the shipped default for domain event handoffs). | The one project-wide decision on delivery semantics for the primary worklist queue. A future exactly-once or at-most-once sibling would contribute the same topic string with a different answer, forcing a DELIBERATE conflict the operator resolves with a project-level ADR |
|
|
10
|
+
|
|
11
|
+
The messaging-queue-cloudflare blueprint claims one global topic. Every other contribution is scope-local (ADR-3001 adapter, ADR-3003 max-attempts floor, ADR-3004 batch defaults do not contribute global topics; a composing blueprint that holds an opinion on any of them authors its own project-level ADR).
|
|
12
|
+
|
|
13
|
+
Rules for new topics: lower camel case, one concept per topic, no version suffixes. A topic names the decision area, not the chosen answer.
|
|
14
|
+
|
|
15
|
+
## Id number bands (registry bootstrap)
|
|
16
|
+
|
|
17
|
+
This table is maintained shelf-wide across every blueprint's `docs/topics.md`. Rows are recorded at ship, never predicted.
|
|
18
|
+
|
|
19
|
+
| Blueprint | US band | ADR/TAC suffix block | Status | Global topics |
|
|
20
|
+
|---|---|---|---|---|
|
|
21
|
+
| application-spa | 1101-1899 | 2xx | shipped v1.3.0 | `clientRouting`, `theming`, `clientState`, `errorEnvelope`, `authModel` |
|
|
22
|
+
| application-api-rest | 2101-2899 | 3xx | shipped v1.0.0 | `errorEnvelope`, `authModel`, `apiVersioning`, `logging` |
|
|
23
|
+
| security-auth-magic-link | 3101-3899 | 5xx | shipped v1.0.0 | `authModel` |
|
|
24
|
+
| email-smtp-resend | 4101-4899 | 4xx | shipped v1.0.0 | none |
|
|
25
|
+
| hello-panel (walkthrough exemplar) | 4101-4899 | 4xx | doc-reserved; teaching exemplar in `packages/rcf-lite/docs/blueprint-authoring-walkthrough.md`, not shipped as a blueprint directory | `operatorPanel` |
|
|
26
|
+
| persistence-data-sqlite | 5101-5899 | 6xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
27
|
+
| delivery-ci-workflows | 6101-6899 | 7xx | shipped v2.0.0 (renamed from ci-pipeline) | `ciGates`, `strictCoverageGate`, `releaseArtefacts` |
|
|
28
|
+
| observability-essentials | 7101-7899 | 8xx | shipped v2.0.0 | `statusPageContract` |
|
|
29
|
+
| security-secrets-management | 8101-8899 | 9xx | shipped v1.0.1 | `secretsSource` |
|
|
30
|
+
| security-auth-clerk | 9101-9899 | 10xx | shipped v1.0.0 | `authModel` |
|
|
31
|
+
| security-auth-oauth2 | 10101-10899 | 11xx | shipped v1.0.0 | `authModel` |
|
|
32
|
+
| security-auth-keycloak | 11101-11899 | 12xx | shipped v1.0.0 | `authModel` |
|
|
33
|
+
| deploy-cloudflare-workers | 12101-12899 | 13xx | shipped v1.0.0 | `deploymentTarget` |
|
|
34
|
+
| persistence-data-d1 | 13101-13899 | 14xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
35
|
+
| observability-probe-endpoints | 14101-14899 | 15xx | shipped v1.1.0 | `healthProbes`, `readinessSemantics` |
|
|
36
|
+
| observability-logging | 15101-15899 | 16xx | shipped v1.0.0 | `logging` |
|
|
37
|
+
| application-error-handling | 16101-16899 | 17xx | shipped v1.0.0 | `errorHandling` |
|
|
38
|
+
| application-datatable | 17101-17899 | 18xx | shipped v1.0.0 | none |
|
|
39
|
+
| application-charts | 18101-18899 | 19xx | shipped v1.0.0 | none |
|
|
40
|
+
| application-dashboard | 19101-19899 | 20xx | shipped v1.0.0 | none |
|
|
41
|
+
| application-notifications-in-app | 20101-20899 | 21xx | shipped v1.0.0 | none |
|
|
42
|
+
| application-admin-console | 21101-21899 | 22xx | shipped v1.0.0 | none |
|
|
43
|
+
| application-empty-error-states | 22101-22899 | 23xx | shipped v1.0.0 | none |
|
|
44
|
+
| application-file-upload | 23101-23899 | 24xx | shipped v1.0.0 | none |
|
|
45
|
+
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
46
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.0.0 | `objectStorageContract` |
|
|
47
|
+
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
48
|
+
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
49
|
+
|
|
50
|
+
US 29101-29108 sit at the LOW end of the 29101-29899 band on purpose (watchpost run-4 lesson). A project-side story mechanically derived from a messaging-queue-cloudflare REQ id into the number 29110 would collide against a shipped US-29110; band headroom (29109-29899) leaves that space.
|
|
51
|
+
|
|
52
|
+
ADR/TAC suffixes for this blueprint use the 3001-3099 block, continuing the shelf pattern.
|
|
53
|
+
|
|
54
|
+
## Shared expectations for future composing blueprints
|
|
55
|
+
|
|
56
|
+
- Reuse `deliverySemantics` exactly as spelled here when your blueprint holds an opinion on the delivery-guarantee contract for the primary worklist queue; contribute your own scope:global ADR on that string and let composition surface the pairing.
|
|
57
|
+
- A future exactly-once, at-most-once, or `messaging-queue-postgres` sibling mints on demand; each claims `deliverySemantics` and conflicts by design.
|
|
58
|
+
- `jobs-background` (round-5 track T-4) reads the `queue` capability declared on `blueprint.json` per section 6a of `blueprint-authoring.md` to obtain the producer facade at apply time via the T-5 visual-round capability-declaration mechanism.
|
|
59
|
+
|
|
60
|
+
## Deliberate-conflict statement
|
|
61
|
+
|
|
62
|
+
This blueprint's ADR-3002 on `deliverySemantics` is a first shipped answer to a topic that will conflict, by design, with future exactly-once or at-most-once siblings, and with the reserved `messaging-queue-postgres` sibling (Baz decision 7). Applying `messaging-queue-cloudflare` alongside a hypothetical `messaging-queue-postgres` raises one `globalAdrTopic` conflict the operator resolves via one of the four documented resolutions per topic (adopt one, keep the existing one, author a project-level supersede ADR, or `--resolve <topic>=project:<ADR-id>` on the add).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# messaging-queue-cloudflare guide
|
|
2
|
+
|
|
3
|
+
## What this blueprint gets you
|
|
4
|
+
|
|
5
|
+
A directed producer-to-consumer worklist queue on Cloudflare Queues: publish, consume, ack, retry, DLQ. The producer facade is the sole reader of the Queues binding declared in your wrangler config (a `[[queues.producers]]` block); the consumer registration attaches to the Worker's `queue` handler through the paired `[[queues.consumers]]` block. Consumer code calls typed named domain verbs on the facade (`publish`, `publishBatch`) and never touches the raw binding. Every publish attaches a producer-supplied trace-id header so downstream consumers stitch the enqueue-to-process boundary; every consumer receipt records `messageAcked` on the sink with the stable platform-assigned message-id.
|
|
6
|
+
|
|
7
|
+
Four lifecycle events fire on the injected event sink with a rigid metadata-only whitelist (`event`, `ts`, `messageId`, `queueName`, `attempts`); consumer logging cannot leak object body bytes, header values, consumer-context fields or PII from the message body. The whitelist is enforced in code by the sink adapter (`event-sink.mjs`), not just documented.
|
|
8
|
+
|
|
9
|
+
The reserved sibling `messaging-queue-postgres` (Baz decision 7, deferred v1.0.0 pending demand) mints on demand and conflicts by design on `deliverySemantics` with this blueprint; the operator picks one queue backend per project via a project-level ADR.
|
|
10
|
+
|
|
11
|
+
## Apply this blueprint
|
|
12
|
+
|
|
13
|
+
`deploy-cloudflare-workers` is the composition target this blueprint expects on a Workers project. The v1.0.0 shipped shape does NOT declare a `requiresAppliedCapabilities` block: the queue binding is wired through wrangler config (not through the capability mechanism), and `jobs-background` composes on the `queue` capability this blueprint provides (per spec section 5.6).
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
rcf define blueprint add messaging-queue-cloudflare
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Exits 0 on any Workers-adjacent project. Contributes ADR-3002 on `scope: global` topic `deliverySemantics` (at-least-once as the shipped shape); a project that already declares a different answer on `deliverySemantics` (an exactly-once or at-most-once sibling minted on demand) surfaces a DELIBERATE conflict for operator resolution.
|
|
20
|
+
|
|
21
|
+
## Producer facade shape
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import { createProducer, queueConfigFromEnv } from './producer.mjs';
|
|
25
|
+
|
|
26
|
+
const cfg = queueConfigFromEnv();
|
|
27
|
+
const producer = createProducer({
|
|
28
|
+
binding: env.RCF_TEST_QUEUE, // the wrangler-bound Queues producer
|
|
29
|
+
queueName: cfg.queueName,
|
|
30
|
+
onEvent: (record) => logger.log('messaging-queue', record),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await producer.ready;
|
|
34
|
+
|
|
35
|
+
// publish one message with a producer-supplied trace-id
|
|
36
|
+
const { id } = await producer.publish({ userId: '42', action: 'sendConfirmation' }, {
|
|
37
|
+
headers: { 'x-trace-id': crypto.randomUUID() },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// publish a batch
|
|
41
|
+
await producer.publishBatch([
|
|
42
|
+
{ body: { seq: 1 }, headers: { 'x-trace-id': 't-1' } },
|
|
43
|
+
{ body: { seq: 2 }, headers: { 'x-trace-id': 't-2' } },
|
|
44
|
+
]);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The facade is the sole holder of the queue binding reference per REQ-001. A call site that reaches into `env.<binding>.send()` directly is refused at author-side review.
|
|
48
|
+
|
|
49
|
+
## Consumer registration shape
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { createConsumer } from './consumer.mjs';
|
|
53
|
+
|
|
54
|
+
export default {
|
|
55
|
+
queue: createConsumer({
|
|
56
|
+
handler: async (msg) => {
|
|
57
|
+
// msg.id, msg.body, msg.headers, msg.attempts, msg.timestamp
|
|
58
|
+
try {
|
|
59
|
+
await processBackgroundWork(msg.body);
|
|
60
|
+
return 'ack';
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// Return 'retry' to redeliver with an incremented attempt counter;
|
|
63
|
+
// after max_retries the message lands on the DLQ per REQ-004.
|
|
64
|
+
return 'retry';
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
onEvent: (record) => logger.log('messaging-queue', record),
|
|
68
|
+
env: process.env,
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Wrangler dev section
|
|
74
|
+
|
|
75
|
+
Local iteration on the fixture uses `wrangler dev` (v3.1.0+ per the Cloudflare Queues local-development doc). The wrangler-dev seam brings up producer and consumer in a single command with `--persist-to` for reproducible state:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
cd packages/rcf-lite/test/fixtures/infra-s3-and-queue
|
|
79
|
+
npx wrangler dev --persist-to .wrangler-state
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Consumer concurrency is NOT supported under `wrangler dev` per https://developers.cloudflare.com/queues/configuration/local-development/; the local run proves the facade contract, publish, consume, ack, retry, and DLQ landing, but does not exercise the 250-invocation concurrency cap. That assertion is the accountBound `real-account-concurrency-smoke` probe. Similarly, Cloudflare Queues does not support `wrangler dev --remote` per the same page.
|
|
83
|
+
|
|
84
|
+
The shipped v1.0.0 probes drive the wrangler-dev-equivalent seam (in-memory queue driver realising the same binding shape) so CI stays reproducible without a live Cloudflare account; the boundary at the facade is indistinguishable from a live-Queues run.
|
|
85
|
+
|
|
86
|
+
## Miniflare in CI section
|
|
87
|
+
|
|
88
|
+
Miniflare (`https://developers.cloudflare.com/workers/testing/miniflare/`, fetched 2026-09-06 status 200) documents itself as full-featured and supports KV, Durable Objects, WebSockets, modules, and more, without listing Queues explicitly. The Miniflare Queues sub-page returned 404 during spec ratification, so no citation ships at that URL. In CI, the wrangler-dev-equivalent seam the fixture ships is the reference; a project that needs the full Miniflare surface for a broader integration test wires it separately and defers to the Cloudflare Queues local-development page above for the Queues-specific behaviour.
|
|
89
|
+
|
|
90
|
+
## Real-account smoke section
|
|
91
|
+
|
|
92
|
+
`node ../../../../blueprints/messaging-queue-cloudflare/contributions/probes/run-real-account-concurrency-smoke.mjs` is the accountBound probe. Without `CI_HAS_CLOUDFLARE_ACCOUNT` it exits 0 with `accountBoundSkipped: true` per spec section 3.5 and the per-blueprint report reads `aggregateVerdict: pass`. With `CI_HAS_CLOUDFLARE_ACCOUNT=1` plus credentials for the shared HQ queue `rcf-lite-ci-queue-smoke` (Q2 default) wired via `security-secrets-management`, a live-account run is queued as a v1.0.0 follow-up: the run itself rides `deploy-cloudflare-workers`' surface (not a Node probe module) since Cloudflare Queues does not support `wrangler dev --remote`, and the v1.0.0 probe stops short of driving deployment machinery.
|
|
93
|
+
|
|
94
|
+
## Retry and DLQ discipline
|
|
95
|
+
|
|
96
|
+
ADR-3003 sets `max_retries` to 3 (matching the Cloudflare Queues documented default at https://developers.cloudflare.com/queues/configuration/dead-letter-queues/) with a floor of 1 and a ceiling of 100 (matching the Cloudflare Queues documented per-message max at https://developers.cloudflare.com/queues/platform/limits/). On overflow, the message lands on the elicited DLQ declared via the `dead_letter_queue` field on the wrangler `[[queues.consumers]]` block; the DLQ has its own retention (4 days when no active consumer is attached per the DLQ doc). A DLQ inspector helper (`src/dlq-inspector.mjs` in the fixture; `wrangler queues consumer add --dead-letter-queue` in production per the DLQ doc's CLI form) reads the DLQ contents for triage.
|
|
97
|
+
|
|
98
|
+
## Batch consume discipline
|
|
99
|
+
|
|
100
|
+
ADR-3004 sets `max_batch_size` to 10 (ceiling 100) and `max_batch_timeout` to 5 seconds (ceiling 60 seconds) per the Cloudflare Queues platform limits. Batch settings live on the `[[queues.consumers]]` block in wrangler config; a project running closer to the ceiling changes the config values, not the code. Per-message ack semantics keep one bad message in a batch from rolling back its peers.
|
|
101
|
+
|
|
102
|
+
## Composition with jobs-background
|
|
103
|
+
|
|
104
|
+
`jobs-background` (round-5 track T-4) declares `requiresAppliedCapabilities: ["queue"]`; applying `jobs-background` on a project that already applied `messaging-queue-cloudflare` gets the T-4 apply-time capability check to pass with the `queue` capability discovered on the applied manifest. The T-4 blueprint enqueues jobs through the producer facade this blueprint exposes; the retry-and-DLQ trajectory is what turns transient job failures into eventual success and permanent failures into a DLQ receipt.
|
|
105
|
+
|
|
106
|
+
## What this blueprint does not do
|
|
107
|
+
|
|
108
|
+
- No exactly-once transport (Cloudflare Queues does not implement transport-level dedup); consumer code that needs exactly-once processing dedups on the stable message-id.
|
|
109
|
+
- No fan-out / broadcast pub-sub; that shape lives in the parked `messaging-eventbus` candidate on a different global topic.
|
|
110
|
+
- No schema registry on the message bodies (they are opaque to the blueprint boundary).
|