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,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "jobs-background-US-30106",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "jobs-background-REQ-006",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Operator-facing surface elicited between cli, httpEndpoint and none",
|
|
8
|
+
"asA": "operator picking how ad-hoc runs and re-drives are invoked",
|
|
9
|
+
"iWant": "the elicited operator surface (cli, httpEndpoint or none) recorded at apply time",
|
|
10
|
+
"soThat": "the applying project builds one thin shim against the jobs-runtime contract without the blueprint prescribing a CLI framework or an HTTP router",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-30106-1",
|
|
14
|
+
"description": "The blueprint declares an elicited parameter operatorSurface with the three enum values cli, httpEndpoint, none in the ADR contribution set (implicit through ADR-3102 elicits section not shipped as a separate ADR because the operator-surface choice does not conflict on a shipped topic); the guide names all three values and the default posture (none for a fresh scaffold; the applying project promotes to cli or httpEndpoint under its own decision). The blueprint does NOT ship the CLI or the HTTP endpoint itself.",
|
|
15
|
+
"given": "the shipped blueprint files after this train lands",
|
|
16
|
+
"when": "grep queries walk the guide and README",
|
|
17
|
+
"then": "guide/jobs-background.md contains the substring operator-facing surface and names all three values cli, httpEndpoint, none; README.md names the same three values; neither ships a CLI binary nor an HTTP router",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3103-jobs-background-run-log"],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "jobs-background-US-30107",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "jobs-background-REQ-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Cross-cutting: --allow-no-queue-yet override records a sidecar note",
|
|
8
|
+
"asA": "operator scaffolding a project that will apply the queue later",
|
|
9
|
+
"iWant": "the --allow-no-queue-yet override to write a sidecar note under the queue family so rcf define validate later flags surfaces that never activated",
|
|
10
|
+
"soThat": "a scaffolding pass is possible without silently shipping an ungated jobs surface",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-jobs-overrideRecorded",
|
|
14
|
+
"description": "On a fresh init scratch project with NO queue provider applied, rcf define blueprint add ./blueprints/jobs-background --allow-no-queue-yet exits 0 and writes the sidecar rcf/blueprints/jobs-background.applied.json with slug jobs-background, allowNoAuthYet true, appliedCapabilities [], and a notes field containing the substring 'no queue yet' and the substring '--allow-no-queue-yet' and the substring 'queue'. The notes must NOT contain the substring 'no auth yet' nor 'no secrets-management yet' (the family derivation on the flag name must resolve to queue, not the auth or secrets families).",
|
|
15
|
+
"given": "a fresh rcf init scratch project with no applied blueprints",
|
|
16
|
+
"when": "rcf define blueprint add ./blueprints/jobs-background --allow-no-queue-yet runs",
|
|
17
|
+
"then": "exit code is 0; rcf/blueprints/jobs-background.applied.json exists; slug is jobs-background; allowNoAuthYet is true; appliedCapabilities is []; notes contains 'no queue yet', '--allow-no-queue-yet' and 'queue'; notes does NOT contain 'no auth yet' nor 'no secrets-management yet'",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3101-jobs-background-job-definition"],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "jobs-background-US-30108",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "jobs-background-REQ-003",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Cross-cutting: scheduled fire on cron drives jobStarted through the elicited fire tolerance window",
|
|
8
|
+
"asA": "developer relying on a cron-scheduled job",
|
|
9
|
+
"iWant": "the scheduler to fire the message and the runtime to dispatch it within the elicited fire tolerance window (default 30 seconds), producing jobStarted and jobCompleted on the sink",
|
|
10
|
+
"soThat": "cron drift is bounded and observable",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-jobs-scheduledRunsOnCron",
|
|
14
|
+
"description": "With messaging-queue-cloudflare and jobs-background both applied on the shared sample-app fixture, the fake-clock-cron probe advances the fixture's fake clock forward through one POSIX cron minute for the refresh-cache job. Within the elicited fireToleranceMs window (default 30000 ms), a jobStarted event fires on the sink with jobName 'refresh-cache' and attempts 1; within the elicited timeoutMs (10000 ms for refresh-cache), a jobCompleted event fires.",
|
|
15
|
+
"given": "the fixture with the queue and jobs applied and the fake clock at second 0 of an every-minute POSIX cron",
|
|
16
|
+
"when": "the fake clock advances 60 seconds",
|
|
17
|
+
"then": "one jobStarted event fires within fireToleranceMs; one jobCompleted event fires within timeoutMs; both records match the whitelist",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3102-jobs-background-scheduler", "TAC-3103-jobs-background-run-log"],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"usId": "jobs-background-US-30109",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"reqId": "jobs-background-REQ-004",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "approved",
|
|
7
|
+
"title": "Cross-cutting: retry-then-DLQ handoff when maxAttempts is exhausted",
|
|
8
|
+
"asA": "operator observing a terminally failing job",
|
|
9
|
+
"iWant": "the applied queue's DLQ to receive the message after maxAttempts is exhausted, and jobFailed to fire on the sink with a terminal error code",
|
|
10
|
+
"soThat": "a terminally failing job lands on the queue's DLQ (as ratified by the T-3 messaging-queue-cloudflare DLQ contract) and the run log records the terminal event",
|
|
11
|
+
"acceptanceCriteria": [
|
|
12
|
+
{
|
|
13
|
+
"id": "AC-30109-1",
|
|
14
|
+
"description": "The retry-and-fail probe's report, after the three failing jobStarted events and the terminal jobFailed event, records that the underlying in-memory queue driver invoked the DLQ producer path (dlqOverflow flag on the driver's introspection API) for the failing message id. The DLQ handoff is a T-3 concern; jobs-background only asserts the terminal jobFailed event fires and the DLQ path is exercised, not the DLQ delivery contents (T-3 covers that).",
|
|
15
|
+
"given": "the fixture with SIMULATE_HANDLER_THROW=true and the T-3 in-memory queue seam active",
|
|
16
|
+
"when": "the retry-and-fail probe runs to completion",
|
|
17
|
+
"then": "the probe report carries dlqInvoked true; the sink shows jobFailed with terminalErrorCode; the retry-and-fail run report at .rcf/reports/blueprints/jobs-background/retry-and-fail.json carries aggregateVerdict pass",
|
|
18
|
+
"testable": true,
|
|
19
|
+
"scope": "runtime"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"tacIds": ["TAC-3103-jobs-background-run-log"],
|
|
23
|
+
"createdAt": "2026-09-06T20:30:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T20:30:00.000Z"
|
|
25
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# jobs-background blueprint coordination vocabulary
|
|
2
|
+
|
|
3
|
+
This file is the jobs-background 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 | jobs-background contribution | Origin | Composition note |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `backgroundJobModel` | ADR-3101-jobs-background-model | Minted by this blueprint. Names the shipped background-job model; the shipped answer is retry-and-schedule (per-message re-delivery on failure inheriting the applied queue's max-attempts ceiling; POSIX cron plus one-shot delayed schedules; metadata-only run-log at four moments). | The one project-wide decision on what background-job discipline the project ships. A future stateful-workflow sibling (Temporal-shape, Airflow-shape) would contribute the same topic string with a different answer, forcing a DELIBERATE conflict the operator resolves with a project-level ADR. The round-6 Workflows adapter (per Baz section 5.7 and round-6 decision (b)) lives INSIDE this blueprint as a v1.1.0 minor scheduler-mode value at ADR-3102 and does NOT re-claim this topic. |
|
|
10
|
+
|
|
11
|
+
The jobs-background blueprint claims one global topic. Every other contribution is scope-local (ADR-3102 scheduler mode, ADR-3103 requires-applied-capabilities, ADR-3104 timeout floor 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 30101-30109 sit at the LOW end of the 30101-30899 band on purpose (watchpost run-4 lesson). A project-side story mechanically derived from a jobs-background REQ id into the number 30110 would collide against a shipped US-30110; band headroom (30110-30899) leaves that space.
|
|
51
|
+
|
|
52
|
+
ADR/TAC suffixes for this blueprint use the 3101-3199 block, continuing the shelf pattern.
|
|
53
|
+
|
|
54
|
+
## Shared expectations for future composing blueprints
|
|
55
|
+
|
|
56
|
+
- Reuse `backgroundJobModel` exactly as spelled here when your blueprint holds an opinion on the shipped background-job discipline; contribute your own scope:global ADR on that string and let composition surface the pairing.
|
|
57
|
+
- A future stateful-workflow sibling (Temporal-shape, Airflow-shape) mints on demand; each claims `backgroundJobModel` and conflicts by design.
|
|
58
|
+
- A consumer blueprint that surfaces "run this later" or "run this on a schedule" reads the `backgroundJobs` capability declared on `blueprint.json` per section 6a of `blueprint-authoring.md` to obtain the job-definition and scheduler surfaces at apply time via the T-5 capability-declaration mechanism.
|
|
59
|
+
|
|
60
|
+
## Deliberate-conflict statement
|
|
61
|
+
|
|
62
|
+
This blueprint's ADR-3101 on `backgroundJobModel` is a first shipped answer to a topic that will conflict, by design, with future stateful-workflow siblings. Applying `jobs-background` alongside a hypothetical Temporal-shape or Airflow-shape sibling 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). The reserved v1.1.0 `workflows` scheduler mode inside this blueprint does NOT trigger this conflict because it lives at ADR-3102 (scope-local) and preserves ADR-3101's answer on `backgroundJobModel`.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# jobs-background guide
|
|
2
|
+
|
|
3
|
+
The v1.0.0 background-jobs discipline. Composes on any applied blueprint declaring `capabilities: ["queue"]`; the shipped provider today is `messaging-queue-cloudflare` v1.0.0. Refuses apply on a bare project.
|
|
4
|
+
|
|
5
|
+
## When to apply this
|
|
6
|
+
|
|
7
|
+
You have background work that runs asynchronously from a request boundary, in-process on a Node server or in a Cloudflare Worker: welcome emails, cache refreshes, reindexing, receipt PDF generation. You want:
|
|
8
|
+
|
|
9
|
+
- A uniform job-definition shape so every module in `./jobs/` follows one contract.
|
|
10
|
+
- A scheduler that ships POSIX cron plus one-shot-delayed shapes and elicits the runtime placement (`inProcess`, `workerCron`, `external`).
|
|
11
|
+
- Retry that inherits the applied queue's max-attempts ceiling; no hand-rolled retry loops.
|
|
12
|
+
- Metadata-only observability by default; the domain PII on the job body cannot leak through the run log even by accident.
|
|
13
|
+
|
|
14
|
+
You do NOT want:
|
|
15
|
+
|
|
16
|
+
- A stateful multi-step workflow orchestration with durable execution and rollbacks. That is either a future Temporal-shape or Airflow-shape sibling (conflicts on `backgroundJobModel`), or the reserved v1.1.0 `workflows` scheduler mode inside this blueprint.
|
|
17
|
+
- A pub-sub broadcast surface. That is a queue with fan-out semantics; this blueprint composes on a directed producer-to-consumer worklist queue.
|
|
18
|
+
|
|
19
|
+
## Apply
|
|
20
|
+
|
|
21
|
+
On a fresh project with `messaging-queue-cloudflare` already applied:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
rcf define blueprint add jobs-background
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Applies 22 contributions and writes the sidecar `rcf/blueprints/jobs-background.applied.json` with `appliedCapabilities: ["queue"]` (discovered through the T-5 capability mechanism).
|
|
28
|
+
|
|
29
|
+
On a bare project (no queue provider applied), apply refuses:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
rcf define blueprint add jobs-background
|
|
33
|
+
# exit 3
|
|
34
|
+
# [jobs-background-no-queue] jobs-background requires an applied blueprint declaring
|
|
35
|
+
# at least one of: queue.
|
|
36
|
+
# ...
|
|
37
|
+
# Or override for a scaffolding pass (records a note on source.notes so
|
|
38
|
+
# later validation flags the surface as not-yet-activated):
|
|
39
|
+
# rcf define blueprint add jobs-background --allow-no-queue-yet
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Override:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
rcf define blueprint add jobs-background --allow-no-queue-yet
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Exits 0 and writes the sidecar with `allowNoAuthYet: true`, `appliedCapabilities: []`, `notes: "no queue yet: applied under --allow-no-queue-yet; surfaces gated on queue will refuse at runtime until a queue blueprint is applied."`.
|
|
49
|
+
|
|
50
|
+
## Writing a job
|
|
51
|
+
|
|
52
|
+
Every job module in `./jobs/` exports a plain object:
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
export default {
|
|
56
|
+
name: 'send-welcome-email',
|
|
57
|
+
inputSchema: mySchemaLibrary.object({ userId: mySchemaLibrary.number(), email: mySchemaLibrary.string() }),
|
|
58
|
+
retryPolicy: { maxAttempts: 3, backoff: 'exponential' },
|
|
59
|
+
timeoutMs: 60000,
|
|
60
|
+
async handler(input, ctx) {
|
|
61
|
+
await ctx.logger.info('sending welcome email', { userId: input.userId });
|
|
62
|
+
await ctx.http.post('/api/mail', input);
|
|
63
|
+
return { sent: true };
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Backoff shapes:
|
|
69
|
+
|
|
70
|
+
- `exponential`: base * 2^(attempts - 1), capped at 10 minutes.
|
|
71
|
+
- `constant`: base seconds between every attempt.
|
|
72
|
+
- `linear`: base * attempts.
|
|
73
|
+
|
|
74
|
+
The `retryPolicy.maxAttempts` must not exceed the applied queue's ceiling; Cloudflare Queues caps at 100 per ADR-3003 in the messaging-queue-cloudflare blueprint.
|
|
75
|
+
|
|
76
|
+
## Scheduling
|
|
77
|
+
|
|
78
|
+
Two shipping shapes at REQ-003:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
// POSIX cron, every minute:
|
|
82
|
+
export default {
|
|
83
|
+
name: 'refresh-cache',
|
|
84
|
+
cron: '* * * * *',
|
|
85
|
+
// ...
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// One-shot delayed, invoked from application code:
|
|
89
|
+
await scheduler.delayed({ jobName: 'send-welcome-email', delayMs: 5 * 60 * 1000, input: { userId, email } });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Running with Cloudflare Queues
|
|
93
|
+
|
|
94
|
+
The shipped composition target is a Cloudflare Worker deploy with `messaging-queue-cloudflare` applied. The `workerCron` scheduler mode wires Cloudflare Cron Triggers to a Worker handler that publishes to the applied queue; the same Worker consumes the queue and dispatches to registered job handlers.
|
|
95
|
+
|
|
96
|
+
Wrangler config (extract):
|
|
97
|
+
|
|
98
|
+
```toml
|
|
99
|
+
[triggers]
|
|
100
|
+
crons = ["* * * * *"]
|
|
101
|
+
|
|
102
|
+
[[queues.producers]]
|
|
103
|
+
binding = "RCF_TEST_QUEUE"
|
|
104
|
+
queue = "rcf-test-queue"
|
|
105
|
+
|
|
106
|
+
[[queues.consumers]]
|
|
107
|
+
queue = "rcf-test-queue"
|
|
108
|
+
dead_letter_queue = "rcf-test-dlq"
|
|
109
|
+
max_retries = 3
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Boot line for local development against the T-3 in-memory queue-driver seam:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
node ../../../../blueprints/jobs-background/contributions/probes/run-fake-clock-cron.mjs
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Operator-facing surface (REQ-006)
|
|
119
|
+
|
|
120
|
+
Elicited between `cli`, `httpEndpoint`, or `none` at apply time.
|
|
121
|
+
|
|
122
|
+
- `cli`: the applying project builds a thin Node CLI shim against the jobs-runtime that supports `jobs list`, `jobs run <name>`, `jobs status <jobId>`.
|
|
123
|
+
- `httpEndpoint`: the applying project mounts a small HTTP surface through its own HTTP shape (`GET /jobs`, `POST /jobs/:name/run`, `GET /jobs/status/:jobId`).
|
|
124
|
+
- `none`: no operator surface; the run log is the only observability. Default for fresh scaffolds.
|
|
125
|
+
|
|
126
|
+
The blueprint ships the contract these shims build against; the shim itself is the applying project's concern.
|
|
127
|
+
|
|
128
|
+
## Reserved v1.1.0 Workflows adapter
|
|
129
|
+
|
|
130
|
+
The `workflows` scheduler mode arrives at v1.1.0 per Baz section 5.7 and round-6 decision (b). It wraps the same job-definition contract with Cloudflare Workflows `step.do` orchestration, adding durable multi-step execution without changing the outward job-definition shape. What changes:
|
|
131
|
+
|
|
132
|
+
- One new enum value at ADR-3102: `scheduler: "workflows"`.
|
|
133
|
+
- One new section in this guide covering how to declare durable steps through the workflow adapter.
|
|
134
|
+
- The `fake-clock-cron.mjs` probe gains a `workflows-scheduler` variant.
|
|
135
|
+
|
|
136
|
+
What does NOT change:
|
|
137
|
+
|
|
138
|
+
- The `backgroundJobModel` topic answer (still retry-and-schedule at ADR-3101).
|
|
139
|
+
- The job-definition module shape (`name`, `handler`, `inputSchema`, `retryPolicy`, `timeoutMs`).
|
|
140
|
+
- The run-log envelope whitelist (`event`, `jobId`, `jobName`, `attempts`, `duration`, `timestamp`, optional `terminalErrorCode`).
|
|
141
|
+
- The `requiresAppliedCapabilities` block on the applying blueprint.
|
|
142
|
+
|
|
143
|
+
## Composition with other blueprints
|
|
144
|
+
|
|
145
|
+
- `observability-logging` (companion): supplies the run-log upstream so the four lifecycle events flow through the applied logger's channel policy.
|
|
146
|
+
- `application-error-handling` (companion): supplies the error record factory a `jobFailed` constructs on the terminal path.
|
|
147
|
+
- `messaging-queue-cloudflare`: the shipped `queue` capability provider today. The `messaging-queue-postgres` sibling (reserved slug per Baz decision 7 in the T-3 documentation) mints on demand and composes here without any change to `jobs-background`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# messaging-queue-cloudflare changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 - 2026-09-06
|
|
4
|
+
|
|
5
|
+
Initial release. Directed producer-to-consumer worklist queue on Cloudflare Queues; first shipped adapter with the reserved `messaging-queue-postgres` sibling (Baz decision 7) named beside it in the section 6a capability table. Accessed through a producer facade that is the sole reader of the Cloudflare Queues binding (`env.RCF_TEST_QUEUE` in the fixture). Landed via the infra round 5 spec (ratified 2026-09-06) as track T-3.
|
|
6
|
+
|
|
7
|
+
- 21 contributions: 6 REQs on producer facade sole-reader / publish-consume-ack / retry / dead-letter / batch / lifecycle events; 8 USs at 29101-29108; 3 TACs (3001 producer facade, 3002 consumer registration, 3003 event sink); 4 ADRs (3001 adapter Cloudflare Queues shipped, 3002 `deliverySemantics` scope global at-least-once, 3003 max_retries default 3 floor 1 ceiling 100, 3004 batch size default 10 batch timeout default 5 seconds).
|
|
8
|
+
- Five Node-only probes under `contributions/probes/` proven against the wrangler-dev-equivalent seam (in-memory queue driver realising the Cloudflare Queues binding shape per SDR-3-a on US-29107): `producer-facade-ready`, `publish-to-delivery`, `retry-and-dlq`, `event-secrecy`, and the accountBound `real-account-concurrency-smoke`. The real-account smoke records `accountBoundSkipped: true` when `CI_HAS_CLOUDFLARE_ACCOUNT` is unset per spec section 3.5.
|
|
9
|
+
- T-3 slice of the shared sample-app fixture at `packages/rcf-lite/test/fixtures/infra-s3-and-queue/` adds a `wrangler.toml` declaring the queue binding (`RCF_TEST_QUEUE`), DLQ binding (`RCF_TEST_DLQ`), `dead_letter_queue = "rcf-test-dlq"`, `max_retries = 3`, `max_batch_size = 10`, `max_batch_timeout = 5`; plus `src/queue-driver.mjs`, `src/producer.mjs`, `src/consumer.mjs`, `src/dlq-inspector.mjs`, `src/event-sink.mjs`. Three wired induced-failure switches (`SIMULATE_CONSUMER_RETRY`, `SIMULATE_DLQ_OVERFLOW`, `SIMULATE_PII_IN_BODY`) drive the negative-run paths.
|
|
10
|
+
- Declares `capabilities: ["queue"]`, `suggestedCompanions: [{role: "logging"}, {role: "errorHandling"}]`, `providesRoles` absent, `requiresAppliedCapabilities` absent. Composes on `deploy-cloudflare-workers` transitively via the wrangler binding (not through the capability mechanism), so `jobs-background` reads the `queue` capability at apply time via the T-5 mechanism from the visual round.
|
|
11
|
+
- Contributes ADR-3002 as `scope: global` on new topic `deliverySemantics`. Future exactly-once, at-most-once or `messaging-queue-postgres` siblings conflict here by design.
|
|
12
|
+
- Every ADR contribution entry on `blueprint.json` carries a `standardsTraceClause` per section 8a.2 (mandatory per HQ mid-flight ruling after PR #154 gate).
|
|
13
|
+
- Extends `packages/rcf-lite/docs/blueprint-authoring.md` section 6a capability-declaration table with one row for `queue` naming the reserved `messaging-queue-postgres` sibling per Baz decision 7 and spec section 5.6.
|
|
14
|
+
- The Miniflare Queues sub-page under the workers testing miniflare storage tree is NOT cited: the ratified round-5 spec's status block records it returning 404 on 2026-09-06. The Cloudflare Queues local-development page (`https://developers.cloudflare.com/queues/configuration/local-development/`) is the sole citation for the local seam.
|
|
15
|
+
- Lifecycle event whitelist reuses T-2 object-storage-s3's `{event, ts, ...}` common envelope prefix (per PR #157 gate reviewer note 2026-09-06) so a future v1.1 minor can promote one shared shape to schema enforcement.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# messaging-queue-cloudflare
|
|
2
|
+
|
|
3
|
+
A directed producer-to-consumer worklist queue on Cloudflare Queues, accessed through a producer facade that is the sole reader of the Queues binding. Cloudflare Queues is the first adapter (queue binding under wrangler config, consumer registered on the Worker `queue` handler); the reserved `messaging-queue-postgres` sibling (Baz decision 7) mints on demand. Ships publish, consume, ack, retry, and DLQ; four lifecycle events (`producerReady`, `messagePublished`, `messageAcked`, `messageDeadLettered`) with a metadata-only field discipline. Composes on `deploy-cloudflare-workers` transitively via the wrangler binding, not through the capability mechanism, so `capabilities: ["queue"]` is what `jobs-background` reads to obtain the producer facade at apply time; no `requiresAppliedCapabilities` block ships at v1.0.0.
|
|
4
|
+
|
|
5
|
+
## What this blueprint gives you
|
|
6
|
+
|
|
7
|
+
- **A producer facade** (TAC-3001) that is the sole reader of the queue binding (`env.RCF_TEST_QUEUE` in the fixture; the elicited binding name in a real project) in your source tree, opens on boot, and exposes typed named domain verbs (`publish`, `publishBatch`). Emits `producerReady` on the first successful ready-check.
|
|
8
|
+
- **A consumer registration** (TAC-3002) on the Worker's `queue` handler; reads a batch, dispatches per-message to the registered handler, acks per message, returns `retry` on retryable failure, drives the retry-to-DLQ trajectory at the elicited `max_retries` per ADR-3003.
|
|
9
|
+
- **An event sink** (TAC-3003) with a metadata-only field discipline: `producerReady`, `messagePublished`, `messageAcked`, `messageDeadLettered`; no body bytes, no header value, no consumer context, no producer context.
|
|
10
|
+
- **Six Node-only probes** proving every runtime observable: four against the wrangler-dev-equivalent seam (`producer-facade-ready`, `publish-to-delivery`, `retry-and-dlq`, `event-secrecy`) plus one accountBound `real-account-concurrency-smoke` that skips cleanly without `CI_HAS_CLOUDFLARE_ACCOUNT` per spec section 3.5, and a shared `probe-utils.mjs` helper module.
|
|
11
|
+
|
|
12
|
+
## The six REQs
|
|
13
|
+
|
|
14
|
+
| REQ | What it commits |
|
|
15
|
+
|---|---|
|
|
16
|
+
| REQ-001 | Producer facade sole reader of the Queues binding; opens on boot; emits `producerReady`. |
|
|
17
|
+
| REQ-002 | Publish / consume / ack contract with stable message-id and producer-supplied trace-id. |
|
|
18
|
+
| REQ-003 | Retry contract: consumer returning `retry` re-delivers with an incremented attempt counter. |
|
|
19
|
+
| REQ-004 | Dead-letter contract: elicited `max_retries` (default 3 per Cloudflare Queues), DLQ per-consumer via wrangler `dead_letter_queue` field, DLQ retention 4 days per https://developers.cloudflare.com/queues/configuration/dead-letter-queues/. |
|
|
20
|
+
| REQ-005 | Batch consume: elicited `max_batch_size` (default 10, ceiling 100) and `max_batch_timeout` (default 5 seconds, ceiling 60 seconds) per https://developers.cloudflare.com/queues/platform/limits/. |
|
|
21
|
+
| REQ-006 | Four lifecycle events with metadata-only fields; event-secrecy probe asserts against a PII fixture body. |
|
|
22
|
+
|
|
23
|
+
## The three TACs
|
|
24
|
+
|
|
25
|
+
- **TAC-3001 producer facade**: sole reader of the queue binding; `publish` and `publishBatch` are the outward surface.
|
|
26
|
+
- **TAC-3002 consumer registration**: the Worker `queue` handler surface; batch envelope; per-message ack / retry semantics.
|
|
27
|
+
- **TAC-3003 event sink**: metadata-only lifecycle event contract; whitelist enforced in code.
|
|
28
|
+
|
|
29
|
+
## The four ADRs
|
|
30
|
+
|
|
31
|
+
- **ADR-3001 adapter**: Cloudflare Queues v1.0.0 shipped first adapter. `recommendedDefault: true`. Standards trace clause: Cloudflare Queues documented per-message and per-batch limits.
|
|
32
|
+
- **ADR-3002 delivery semantics** (`scope: global`, topic `deliverySemantics`): at-least-once as the shipped shape; conflicts by design with future exactly-once or at-most-once siblings. Standards trace clause: WSD-005 clauses on at-least-once and out-of-order tolerance.
|
|
33
|
+
- **ADR-3003 max-attempts floor**: `max_retries` default 3, floor 1, ceiling 100 per Cloudflare Queues platform limits. `elicited: true`. Standards trace clause: Cloudflare Queues default retry limit 3 and documented ceiling 100.
|
|
34
|
+
- **ADR-3004 batch defaults**: `max_batch_size` default 10 (ceiling 100), `max_batch_timeout` default 5 seconds (ceiling 60 seconds). `elicited: true`. Standards trace clause: Cloudflare Queues per-consumer batch settings.
|
|
35
|
+
|
|
36
|
+
## Elicited parameters
|
|
37
|
+
|
|
38
|
+
Queue name (or binding name in a Workers deploy); `max_retries` before DLQ (default 3, floor 1, ceiling 100); DLQ queue name (a second Cloudflare Queue declared via `dead_letter_queue` in wrangler config); `max_batch_size` (default 10, ceiling 100); `max_batch_timeout` (default 5 seconds, ceiling 60 seconds); producer-supplied trace-id header key (default `x-trace-id`).
|
|
39
|
+
|
|
40
|
+
## Companions
|
|
41
|
+
|
|
42
|
+
- **logging**: every publish, every ack, and every DLQ landing writes through the applied logger; a logging companion supplies the factory.
|
|
43
|
+
- **errorHandling**: a publish failure, a DLQ overflow constructs an internal error record; an error-handling companion supplies the record factory and the boundary.
|
|
44
|
+
|
|
45
|
+
## Composition and conflicts
|
|
46
|
+
|
|
47
|
+
Composes on `deploy-cloudflare-workers` transitively via the wrangler binding (not through the capability mechanism), so no `requiresAppliedCapabilities` block ships at v1.0.0. Contributes ADR-3002 as `scope: global` on new topic `deliverySemantics`; a future `messaging-queue-postgres` sibling (Baz decision 7, deferred v1.0.0 pending demand) or an exactly-once / at-most-once sibling would conflict here by design. `capabilities: ["queue"]`; `providesRoles: []`.
|
|
48
|
+
|
|
49
|
+
## Reserved sibling
|
|
50
|
+
|
|
51
|
+
`messaging-queue-postgres` (Baz decision 7). Reserved slug beside the `queue` capability row in `packages/rcf-lite/docs/blueprint-authoring.md` section 6a. When demand mints it, the sibling composes on `persistence-data-postgres` as its transport and conflicts by design on `deliverySemantics` with this blueprint; the operator picks one queue backend per project via a project-level ADR.
|
|
52
|
+
|
|
53
|
+
## The six probes
|
|
54
|
+
|
|
55
|
+
Each probe is a Node module under `contributions/probes/` exporting the round-5 spec section 3.2 verdict envelope, with a matching `run-<probe-name>.mjs` shim that drives the probe against the shared fixture's T-3 slice and writes the per-blueprint report at `.rcf/reports/blueprints/messaging-queue-cloudflare/<probe-name>.json`.
|
|
56
|
+
|
|
57
|
+
| Probe | Anchor AC | What it proves | accountBound |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `producer-facade-ready` | AC-29101-1 | Producer facade opens against the queue binding; `producerReady` fires with `queueName`. | false |
|
|
60
|
+
| `publish-to-delivery` | AC-29102-1, AC-29102-2, AC-29105-1, AC-29108-1 | 5 messages round-trip body byte-equal with stable message-ids and producer-supplied trace-ids preserved; per-message ack; wrangler-dev-equivalent seam. | false |
|
|
61
|
+
| `retry-and-dlq` | AC-29103-1, AC-29104-1 | Consumer returning `retry` observes same stable message-id with attempts 1..max_retries; DLQ landing fires `messageDeadLettered` at `max_retries + 1`; DLQ contents body byte-equal via the DLQ inspector helper. | false |
|
|
62
|
+
| `event-secrecy` | AC-29106-1 | Every event record carries only the whitelist (event, ts, messageId, queueName, attempts); no PII fixture text on JSON grep. | false |
|
|
63
|
+
| `real-account-concurrency-smoke` | AC-29108-2 | Real 500-message round-trip against `rcf-lite-ci-queue-smoke` when `CI_HAS_CLOUDFLARE_ACCOUNT` set; records `accountBoundSkipped: true` when not. | true |
|
|
64
|
+
|
|
65
|
+
## Anatomy
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
blueprints/messaging-queue-cloudflare/
|
|
69
|
+
blueprint.json
|
|
70
|
+
README.md
|
|
71
|
+
CHANGELOG.md
|
|
72
|
+
guide/
|
|
73
|
+
messaging-queue-cloudflare.md
|
|
74
|
+
docs/
|
|
75
|
+
topics.md
|
|
76
|
+
contributions/
|
|
77
|
+
requirements/
|
|
78
|
+
messaging-queue-cloudflare-req-001.json ... req-006.json
|
|
79
|
+
user-stories/
|
|
80
|
+
messaging-queue-cloudflare-us-29101.json ... us-29108.json
|
|
81
|
+
tacs/
|
|
82
|
+
tac-3001-messaging-queue-cloudflare-producer-facade.json
|
|
83
|
+
tac-3002-messaging-queue-cloudflare-consumer-registration.json
|
|
84
|
+
tac-3003-messaging-queue-cloudflare-event-sink.json
|
|
85
|
+
adrs/
|
|
86
|
+
adr-3001-messaging-queue-cloudflare-adapter.json
|
|
87
|
+
adr-3002-messaging-queue-cloudflare-delivery-semantics.json
|
|
88
|
+
adr-3003-messaging-queue-cloudflare-max-attempts-floor.json
|
|
89
|
+
adr-3004-messaging-queue-cloudflare-batch-defaults.json
|
|
90
|
+
probes/
|
|
91
|
+
probe-utils.mjs
|
|
92
|
+
producer-facade-ready.mjs (+ run-producer-facade-ready.mjs)
|
|
93
|
+
publish-to-delivery.mjs (+ run-publish-to-delivery.mjs)
|
|
94
|
+
retry-and-dlq.mjs (+ run-retry-and-dlq.mjs)
|
|
95
|
+
event-secrecy.mjs (+ run-event-secrecy.mjs)
|
|
96
|
+
real-account-concurrency-smoke.mjs (+ run-real-account-concurrency-smoke.mjs)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Known limitations and mechanism-reach gaps
|
|
100
|
+
|
|
101
|
+
The five local probes drive the fixture's in-memory queue-driver (`src/queue-driver.mjs`), which realises the Cloudflare Queues binding shape (`send` / `sendBatch` on the producer side, batch envelope with per-message `ack` / `retry` on the consumer side) so the producer facade module and consumer registration are indistinguishable from a live Queues run at the facade boundary. The wrangler-dev-equivalent seam (SDR-3-a on US-29107) is a deliberate v1.0.0 posture: rcf-lite gains no runtime dependency, CI stays reproducible without a live Cloudflare account, and Cloudflare Queues does not support consumer concurrency under `wrangler dev` per https://developers.cloudflare.com/queues/configuration/local-development/ (that assertion is what `real-account-concurrency-smoke` carries as `accountBound: true`).
|
|
102
|
+
|
|
103
|
+
Two live paths are unproven at v1.0.0 and are recorded per AC below so the gate reviewer and any downstream composing blueprint see exactly what is and is not proven for each AC. The gate reviewer's own single `wrangler dev` run and the `accountBound` smoke are what promote these gaps to proven.
|
|
104
|
+
|
|
105
|
+
- **AC-29101-1** (producer facade opens on boot; `producerReady` fires with `queueName`): proven against the in-memory realisation of the Cloudflare Queues binding shape (probe `producer-facade-ready`, verdict pass); live Queues delivery unproven pending a reviewer `wrangler dev` run or the accountBound smoke.
|
|
106
|
+
- **AC-29101-2** (sole-reader grep on the applied fixture): proven against the in-memory realisation (source-tree scan of `src/producer.mjs` against `src/consumer.mjs` peers); live-Queues source-tree posture is identical (the grep target is the fixture tree, not the vendor surface), so this AC has no live-only gap.
|
|
107
|
+
- **AC-29102-1** (5 messages round-trip body byte-equal with stable message-id and producer-supplied trace-id): proven against the in-memory realisation of the binding shape (probe `publish-to-delivery`, verdict pass); live Queues delivery of the same 5-message batch unproven pending a reviewer `wrangler dev` run or the accountBound smoke.
|
|
108
|
+
- **AC-29102-2** (`messageAcked` fires per successful consumer return; message not redelivered): proven against the in-memory realisation of the binding shape (probe `publish-to-delivery`, verdict pass); live Queues per-message ack behaviour unproven pending a reviewer `wrangler dev` run or the accountBound smoke.
|
|
109
|
+
- **AC-29103-1** (retry trajectory: same stable message-id across attempts 1..max_retries with incrementing attempt counter): proven against the in-memory realisation of the binding shape (probe `retry-and-dlq`, verdict pass with attempts 1,2,3 on the same message-id); live Queues retry counter observation unproven pending a reviewer `wrangler dev` run or the accountBound smoke.
|
|
110
|
+
- **AC-29104-1** (DLQ landing at `max_retries + 1` with `messageDeadLettered` event and body byte-equal on DLQ inspection): proven against the in-memory realisation of the binding shape (probe `retry-and-dlq`, verdict pass with DLQ landing at attempts=4 and byte-equal body); live Queues DLQ landing via wrangler `dead_letter_queue` field and inspection via `wrangler queues consumer add --dead-letter-queue` unproven pending a reviewer `wrangler dev` run or the accountBound smoke.
|
|
111
|
+
- **AC-29105-1** (batch consume: 5 messages in one batch, per-message ack semantics under `max_batch_size = 10`): proven against the in-memory realisation of the binding shape (probe `publish-to-delivery`, verdict pass); live Queues `max_batch_size` / `max_batch_timeout` behaviour on the [[queues.consumers]] block unproven pending a reviewer `wrangler dev` run or the accountBound smoke.
|
|
112
|
+
- **AC-29106-1** (metadata-only event whitelist; no PII body / header value / consumer context leaks): proven end-to-end against the in-memory realisation (probe `event-secrecy`, verdict pass; the whitelist is code-enforced in `src/event-sink.mjs`, independent of transport); live-Queues run does not change the sink code path, so this AC has no live-only gap.
|
|
113
|
+
- **AC-29108-1** (wrangler-dev-equivalent seam round-trip): proven end-to-end against the in-memory realisation of the binding shape (probe `publish-to-delivery`, verdict pass); a live `wrangler dev` process was not driven in this pass. Rationale: SDR-3-a on US-29107 makes the wrangler-dev-equivalent seam the shipped local seam because Cloudflare Queues does not support consumer concurrency under `wrangler dev` and does not support `wrangler dev --remote` per the local-development doc, so a live-wrangler run adds observability (a real process on port 8787) but does not add facade-contract certainty over the in-memory realisation. The gate reviewer drives one live `wrangler dev` run in their own scratch to close the observability gap.
|
|
114
|
+
- **AC-29108-2** (real-account concurrency smoke: 500 messages, consumer concurrency up to 250): NOT proven at v1.0.0 without `CI_HAS_CLOUDFLARE_ACCOUNT`. The probe records `accountBoundSkipped: true` per spec section 3.5 and the per-blueprint report carries `aggregateVerdict: pass` on the skipped shape. The live 500-message run rides `deploy-cloudflare-workers`' surface (Cloudflare Queues does not support `wrangler dev --remote` per the local-development doc, so this cannot ride a local wrangler process even with the env var set); the live run is a v1.0.0 follow-up when the shared HQ queue `rcf-lite-ci-queue-smoke` (Q2 default) is provisioned on Baz's account.
|
|
115
|
+
|
|
116
|
+
## Other limitations (not mechanism-reach gaps)
|
|
117
|
+
|
|
118
|
+
- Cloudflare Queues per-message size cap is 128 KB per https://developers.cloudflare.com/queues/platform/limits/. A project sending larger payloads either chunks or moves the body out-of-band to `object-storage-s3`. This is a boundary the shipped facade does not police; it is a project-level concern the guide names.
|
|
119
|
+
- The Miniflare Queues sub-page returned 404 during spec ratification (2026-09-06); the Cloudflare Queues local-development page above is the authoritative reference and the only URL the guide cites for the local seam.
|
|
120
|
+
|
|
121
|
+
## What this blueprint does not do
|
|
122
|
+
|
|
123
|
+
- No fan-out / broadcast pub-sub semantics; that lives in the parked `messaging-eventbus` candidate on a different global topic.
|
|
124
|
+
- No exactly-once transport (Cloudflare Queues implements at-least-once with a stable message-id for consumer-side dedup per ADR-3002); consumer code that needs exactly-once processing dedups on the stable message-id.
|
|
125
|
+
- No schema registry; message bodies are opaque to this blueprint.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"slug": "messaging-queue-cloudflare",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"category": "messaging",
|
|
5
|
+
"capabilities": ["queue"],
|
|
6
|
+
"suggestedCompanions": [
|
|
7
|
+
{
|
|
8
|
+
"role": "logging",
|
|
9
|
+
"reason": "Every publish and every ack writes through the applied logger; a logging companion supplies the factory."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"role": "errorHandling",
|
|
13
|
+
"reason": "A publish failure, a DLQ overflow constructs an internal error record; an error-handling companion supplies the record factory and the boundary."
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"contributions": [
|
|
17
|
+
{ "id": "messaging-queue-cloudflare-REQ-001", "kind": "req", "path": "requirements/messaging-queue-cloudflare-req-001.json" },
|
|
18
|
+
{ "id": "messaging-queue-cloudflare-REQ-002", "kind": "req", "path": "requirements/messaging-queue-cloudflare-req-002.json" },
|
|
19
|
+
{ "id": "messaging-queue-cloudflare-REQ-003", "kind": "req", "path": "requirements/messaging-queue-cloudflare-req-003.json" },
|
|
20
|
+
{ "id": "messaging-queue-cloudflare-REQ-004", "kind": "req", "path": "requirements/messaging-queue-cloudflare-req-004.json" },
|
|
21
|
+
{ "id": "messaging-queue-cloudflare-REQ-005", "kind": "req", "path": "requirements/messaging-queue-cloudflare-req-005.json" },
|
|
22
|
+
{ "id": "messaging-queue-cloudflare-REQ-006", "kind": "req", "path": "requirements/messaging-queue-cloudflare-req-006.json" },
|
|
23
|
+
{ "id": "messaging-queue-cloudflare-US-29101", "kind": "us", "path": "user-stories/messaging-queue-cloudflare-us-29101.json" },
|
|
24
|
+
{ "id": "messaging-queue-cloudflare-US-29102", "kind": "us", "path": "user-stories/messaging-queue-cloudflare-us-29102.json" },
|
|
25
|
+
{ "id": "messaging-queue-cloudflare-US-29103", "kind": "us", "path": "user-stories/messaging-queue-cloudflare-us-29103.json" },
|
|
26
|
+
{ "id": "messaging-queue-cloudflare-US-29104", "kind": "us", "path": "user-stories/messaging-queue-cloudflare-us-29104.json" },
|
|
27
|
+
{ "id": "messaging-queue-cloudflare-US-29105", "kind": "us", "path": "user-stories/messaging-queue-cloudflare-us-29105.json" },
|
|
28
|
+
{ "id": "messaging-queue-cloudflare-US-29106", "kind": "us", "path": "user-stories/messaging-queue-cloudflare-us-29106.json" },
|
|
29
|
+
{ "id": "messaging-queue-cloudflare-US-29107", "kind": "us", "path": "user-stories/messaging-queue-cloudflare-us-29107.json" },
|
|
30
|
+
{ "id": "messaging-queue-cloudflare-US-29108", "kind": "us", "path": "user-stories/messaging-queue-cloudflare-us-29108.json" },
|
|
31
|
+
{ "id": "TAC-3001-messaging-queue-cloudflare-producer-facade", "kind": "tac", "path": "tacs/tac-3001-messaging-queue-cloudflare-producer-facade.json" },
|
|
32
|
+
{ "id": "TAC-3002-messaging-queue-cloudflare-consumer-registration", "kind": "tac", "path": "tacs/tac-3002-messaging-queue-cloudflare-consumer-registration.json" },
|
|
33
|
+
{ "id": "TAC-3003-messaging-queue-cloudflare-event-sink", "kind": "tac", "path": "tacs/tac-3003-messaging-queue-cloudflare-event-sink.json" },
|
|
34
|
+
{
|
|
35
|
+
"id": "ADR-3001-messaging-queue-cloudflare-adapter",
|
|
36
|
+
"kind": "adr",
|
|
37
|
+
"path": "adrs/adr-3001-messaging-queue-cloudflare-adapter.json",
|
|
38
|
+
"recommendedDefault": true,
|
|
39
|
+
"elicited": false,
|
|
40
|
+
"standardsTraceClause": "Cloudflare Queues documented per-message and per-batch limits"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "ADR-3002-messaging-queue-cloudflare-delivery-semantics",
|
|
44
|
+
"kind": "adr",
|
|
45
|
+
"path": "adrs/adr-3002-messaging-queue-cloudflare-delivery-semantics.json",
|
|
46
|
+
"scope": "global",
|
|
47
|
+
"topic": "deliverySemantics",
|
|
48
|
+
"standardsTraceClause": "WSD-005 clauses on at-least-once and out-of-order tolerance"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "ADR-3003-messaging-queue-cloudflare-max-attempts-floor",
|
|
52
|
+
"kind": "adr",
|
|
53
|
+
"path": "adrs/adr-3003-messaging-queue-cloudflare-max-attempts-floor.json",
|
|
54
|
+
"recommendedDefault": true,
|
|
55
|
+
"elicited": true,
|
|
56
|
+
"standardsTraceClause": "Cloudflare Queues default retry limit 3 and documented ceiling 100"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "ADR-3004-messaging-queue-cloudflare-batch-defaults",
|
|
60
|
+
"kind": "adr",
|
|
61
|
+
"path": "adrs/adr-3004-messaging-queue-cloudflare-batch-defaults.json",
|
|
62
|
+
"recommendedDefault": true,
|
|
63
|
+
"elicited": true,
|
|
64
|
+
"standardsTraceClause": "Cloudflare Queues per-consumer batch settings"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adrId": "ADR-3001-messaging-queue-cloudflare-adapter",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "accepted",
|
|
7
|
+
"title": "Adapter: Cloudflare Queues as the shipped v1.0.0 first adapter",
|
|
8
|
+
"context": "A project deploying a directed producer-to-consumer worklist queue on Cloudflare Workers has one native shipped answer today: Cloudflare Queues. The Queues surface is bound through wrangler configuration (a [[queues.producers]] block for the producer facade to publish through, a [[queues.consumers]] block for the Worker queue handler to consume from), and the shipped Worker runtime binds env.<binding> to a Queues object with send / sendBatch on the producer side and a batch envelope with ack / retry on the consumer side per https://developers.cloudflare.com/queues/configuration/javascript-apis/. The v1.0.0 facade must hold the adapter reference opaque at the facade boundary so a future non-Cloudflare adapter (an SQS shim, the reserved messaging-queue-postgres sibling per Baz decision 7) slots in without a facade re-shape, and so a fixture-time in-memory adapter is testable without live Cloudflare Queues access.",
|
|
9
|
+
"decision": "The shipped v1.0.0 default is Cloudflare Queues, wired through wrangler config (producer binding on [[queues.producers]], consumer on [[queues.consumers]] with dead_letter_queue and max_retries per https://developers.cloudflare.com/queues/configuration/dead-letter-queues/). The producer facade module is the sole holder of the Queues binding reference per REQ-001; the consumer registration module is the sole reader of the batch envelope per REQ-002. The adapter reference is OPAQUE at this ADR: the facade dereferences the elicited binding at the top of the module and any consumer wanting a different adapter (an SQS shim, the messaging-queue-postgres sibling minted on demand) replaces the binding reference here without touching the outward interface. A fixture-time in-memory adapter (used by the local probes) implements the same binding shape (send / sendBatch on the producer side, batch with ack / retry on the consumer side) so the facade and consumer surfaces are indistinguishable from the real Queues binding at the facade boundary. The 128 KB per-message cap and 100 messages per batch cap Cloudflare documents at https://developers.cloudflare.com/queues/platform/limits/ apply to every real-Queues run.",
|
|
10
|
+
"consequences": "The project inherits the Cloudflare Queues wire-format, the platform-assigned stable message-id, and the batch envelope's per-message ack semantics. Standards trace clause: Cloudflare Queues documented per-message and per-batch limits (per https://developers.cloudflare.com/queues/platform/limits/), Cloudflare Queues Worker queue-handler contract (per https://developers.cloudflare.com/queues/configuration/javascript-apis/), and Cloudflare Queues dead-letter documentation (per https://developers.cloudflare.com/queues/configuration/dead-letter-queues/). A future non-Cloudflare adapter mints under a sibling blueprint (reserved slug messaging-queue-postgres, Baz decision 7) and conflicts by design on deliverySemantics (ADR-3002) with this blueprint; the operator picks one queue backend per project via a project-level ADR.",
|
|
11
|
+
"alternativesConsidered": [
|
|
12
|
+
{
|
|
13
|
+
"name": "External SQS or SNS as the shipped v1.0.0 default",
|
|
14
|
+
"summary": "Ship AWS SQS as the primary v1.0.0 queue backend and provide Cloudflare Queues as an elicited alternative.",
|
|
15
|
+
"reasonNotChosen": "The shelf composition target this round is a Cloudflare Workers project (deploy-cloudflare-workers is the applied deploy target on the shared fixture). Shipping SQS as the default would leave a Workers project reaching into a non-Cloudflare vendor for its main worklist, which is not the shipped-shape target of the round-5 spec section 5.3."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Kafka or NATS as the shipped v1.0.0 default",
|
|
19
|
+
"summary": "Ship a self-hosted Kafka or NATS backend as the primary v1.0.0 queue.",
|
|
20
|
+
"reasonNotChosen": "A self-hosted Kafka or NATS backend adds a persistent operational surface every applying project has to run; the round-5 spec section 5.3 targets a directed producer-to-consumer worklist as a shipped shape whose applying project need not host a broker. The reserved messaging-queue-postgres sibling (Baz decision 7) covers the self-hosted-transport family separately when demand mints it."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "Ship a hand-rolled fetch client against the Queues REST API",
|
|
24
|
+
"summary": "Skip the Worker Queues binding and roll a fetch client against the Queues REST-shape endpoints.",
|
|
25
|
+
"reasonNotChosen": "The Worker binding is the shipped Cloudflare surface and the one every Queues doc references; a REST client would ship a second surface with its own auth and its own reliability envelope. The mature binding-based surface is what makes the v1.0.0 facade portable at the boundary."
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
29
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adrId": "ADR-3002-messaging-queue-cloudflare-delivery-semantics",
|
|
3
|
+
"prdId": "PRD-001",
|
|
4
|
+
"tadId": "TAD-001",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"status": "accepted",
|
|
7
|
+
"title": "Delivery semantics: at-least-once as the shipped shape (scope global, new topic deliverySemantics)",
|
|
8
|
+
"context": "A directed producer-to-consumer worklist ships one of three delivery shapes at the transport contract: at-most-once (fire and forget, dedup at the producer), at-least-once (guaranteed delivery with duplicates possible, dedup at the consumer), or exactly-once (guaranteed delivery with dedup at the transport, expensive). Cloudflare Queues implements at-least-once with a stable message-id the consumer uses for its own dedup; the WSD-005 messaging standard (operator/knowledge/wsd/wsd-standards/wsd-005-messaging.md) makes at-least-once and out-of-order tolerance the shipped default for domain event handoffs; the messaging-queue-cloudflare blueprint claims the deliverySemantics topic on the shelf so future exactly-once or at-most-once siblings surface as DELIBERATE conflicts and not silent divergences.",
|
|
9
|
+
"decision": "The shipped v1.0.0 shape is at-least-once. Every publish reaches every consumer at least once; duplicates are possible under retry-and-DLQ redelivery paths; consumer code that needs exactly-once semantics maintains its own dedup keyed on the stable message-id per REQ-002. The deliverySemantics topic scope is global on this blueprint; the topic string is deliverySemantics (lower camel case, one concept per topic, no version suffix, per the round-2 rules-for-new-topics clause on the shelf registry). Future exactly-once or at-most-once siblings would contribute the same topic string with a different answer, forcing a DELIBERATE conflict the operator resolves with a project-level ADR per the round-2 conflict pattern.",
|
|
10
|
+
"consequences": "The applying project inherits an at-least-once transport with a stable message-id it uses for consumer-side dedup when the domain calls for exactly-once processing. Standards trace clause: WSD-005 clauses on at-least-once and out-of-order tolerance (satisfied by the message-id and trace-id header contract). The reserved messaging-queue-postgres sibling (Baz decision 7) will claim deliverySemantics with an at-least-once answer too; two shipped answers on one topic surface a DELIBERATE conflict the operator resolves by picking one queue backend per project via a project-level ADR (the same shape as errorEnvelope and authModel conflicts the SPA and API-REST blueprints already carry).",
|
|
11
|
+
"alternativesConsidered": [
|
|
12
|
+
{
|
|
13
|
+
"name": "At-most-once as the shipped v1.0.0 default",
|
|
14
|
+
"summary": "Ship fire-and-forget publish semantics with producer-side dedup and no DLQ.",
|
|
15
|
+
"reasonNotChosen": "At-most-once loses the DLQ safety net and does not compose with the retry contract REQ-003 requires. The shipped shape's whole reason for existing is the guaranteed handoff for background work; at-most-once is the wrong contract for that use case."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "Exactly-once as the shipped v1.0.0 default",
|
|
19
|
+
"summary": "Ship transport-level dedup so the consumer never sees the same message-id twice.",
|
|
20
|
+
"reasonNotChosen": "Cloudflare Queues does not implement transport-level exactly-once; shipping the label would misrepresent the transport. Consumer-side dedup keyed on the stable message-id is the shipped path when exactly-once is the domain requirement."
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"createdAt": "2026-09-06T21:00:00.000Z",
|
|
24
|
+
"updatedAt": "2026-09-06T21:00:00.000Z"
|
|
25
|
+
}
|