rcf-lite 0.25.0 → 0.26.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.
Files changed (137) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/blueprints/application-account-settings/docs/topics.md +1 -1
  3. package/blueprints/application-admin-console/docs/topics.md +1 -1
  4. package/blueprints/application-api-rest/docs/topics.md +1 -1
  5. package/blueprints/application-charts/docs/topics.md +1 -1
  6. package/blueprints/application-dashboard/docs/topics.md +1 -1
  7. package/blueprints/application-datatable/docs/topics.md +1 -1
  8. package/blueprints/application-empty-error-states/docs/topics.md +1 -1
  9. package/blueprints/application-error-handling/docs/topics.md +1 -1
  10. package/blueprints/application-file-upload/docs/topics.md +1 -1
  11. package/blueprints/application-forms-wizard/docs/topics.md +1 -1
  12. package/blueprints/application-notifications-in-app/docs/topics.md +1 -1
  13. package/blueprints/application-onboarding-tour/docs/topics.md +1 -1
  14. package/blueprints/application-spa/docs/topics.md +1 -1
  15. package/blueprints/delivery-ci-workflows/docs/topics.md +1 -1
  16. package/blueprints/deploy-cloudflare-workers/docs/topics.md +1 -1
  17. package/blueprints/deploy-hetzner-server/CHANGELOG.md +21 -0
  18. package/blueprints/deploy-hetzner-server/blueprint.json +1 -1
  19. package/blueprints/deploy-hetzner-server/contributions/probes/cloud-init-render-lint.mjs +12 -5
  20. package/blueprints/deploy-hetzner-server/contributions/probes/hcloud-dry-run-mock.mjs +68 -20
  21. package/blueprints/deploy-hetzner-server/contributions/probes/manifest-schema-validate.mjs +8 -6
  22. package/blueprints/deploy-hetzner-server/contributions/probes/probe-utils.mjs +7 -1
  23. package/blueprints/deploy-hetzner-server/contributions/probes/real-account-cloud-init-hardened.mjs +2 -1
  24. package/blueprints/deploy-hetzner-server/contributions/templates/cloud-init.yaml.tmpl +26 -6
  25. package/blueprints/deploy-hetzner-server/docs/topics.md +1 -1
  26. package/blueprints/edge-cloudflare-access/docs/topics.md +1 -1
  27. package/blueprints/edge-cloudflare-rate-limiting/docs/topics.md +1 -1
  28. package/blueprints/edge-cloudflare-tunnel/CHANGELOG.md +10 -0
  29. package/blueprints/edge-cloudflare-tunnel/blueprint.json +1 -1
  30. package/blueprints/edge-cloudflare-tunnel/contributions/probes/manifest-schema-validate.mjs +4 -1
  31. package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-connector-healthy.mjs +110 -21
  32. package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-tunnel-hostname-routes.mjs +123 -30
  33. package/blueprints/edge-cloudflare-tunnel/docs/topics.md +1 -1
  34. package/blueprints/edge-cloudflare-turnstile/docs/topics.md +1 -1
  35. package/blueprints/email-smtp-resend/docs/topics.md +1 -1
  36. package/blueprints/jobs-background/docs/topics.md +1 -1
  37. package/blueprints/messaging-queue-cloudflare/CHANGELOG.md +19 -0
  38. package/blueprints/messaging-queue-cloudflare/README.md +2 -2
  39. package/blueprints/messaging-queue-cloudflare/blueprint.json +1 -1
  40. package/blueprints/messaging-queue-cloudflare/contributions/probes/real-account-concurrency-smoke.mjs +192 -37
  41. package/blueprints/messaging-queue-cloudflare/docs/topics.md +1 -1
  42. package/blueprints/object-storage-s3/CHANGELOG.md +11 -0
  43. package/blueprints/object-storage-s3/README.md +14 -1
  44. package/blueprints/object-storage-s3/blueprint.json +12 -1
  45. package/blueprints/object-storage-s3/contributions/adrs/adr-2905-object-storage-s3-hetzner-object-storage-provider.json +30 -0
  46. package/blueprints/object-storage-s3/contributions/probes/hetzner-object-storage-round-trip.mjs +136 -0
  47. package/blueprints/object-storage-s3/contributions/probes/run-hetzner-object-storage-round-trip.mjs +11 -0
  48. package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-101.json +18 -0
  49. package/blueprints/object-storage-s3/contributions/tacs/tac-2904-object-storage-s3-hetzner-endpoint-helper.json +34 -0
  50. package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28110.json +25 -0
  51. package/blueprints/object-storage-s3/docs/topics.md +1 -1
  52. package/blueprints/object-storage-s3/guide/object-storage-s3.md +36 -0
  53. package/blueprints/observability-essentials/docs/topics.md +1 -1
  54. package/blueprints/observability-logging/docs/topics.md +1 -1
  55. package/blueprints/observability-probe-endpoints/docs/topics.md +1 -1
  56. package/blueprints/persistence-data-d1/docs/topics.md +1 -1
  57. package/blueprints/persistence-data-postgres/docs/topics.md +1 -1
  58. package/blueprints/persistence-data-sqlite/docs/topics.md +1 -1
  59. package/blueprints/platform-cloudflare-cron-triggers/docs/topics.md +1 -1
  60. package/blueprints/platform-cloudflare-durable-objects/CHANGELOG.md +15 -0
  61. package/blueprints/platform-cloudflare-durable-objects/blueprint.json +1 -1
  62. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/namespace-facade-ready.mjs +3 -3
  63. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/probe-utils.mjs +28 -14
  64. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/real-account-storage-smoke.mjs +132 -26
  65. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/sole-reader-scan.mjs +40 -39
  66. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/storage-round-trip.mjs +26 -16
  67. package/blueprints/platform-cloudflare-durable-objects/contributions/probes/websocket-hub-broadcast.mjs +31 -34
  68. package/blueprints/platform-cloudflare-durable-objects/docs/topics.md +1 -1
  69. package/blueprints/platform-cloudflare-kv/CHANGELOG.md +40 -1
  70. package/blueprints/platform-cloudflare-kv/README.md +41 -13
  71. package/blueprints/platform-cloudflare-kv/blueprint.json +1 -1
  72. package/blueprints/platform-cloudflare-kv/contributions/probes/cache-aside-hit-then-miss.mjs +26 -26
  73. package/blueprints/platform-cloudflare-kv/contributions/probes/event-secrecy.mjs +49 -31
  74. package/blueprints/platform-cloudflare-kv/contributions/probes/facade-round-trip.mjs +58 -16
  75. package/blueprints/platform-cloudflare-kv/contributions/probes/list-with-prefix.mjs +6 -5
  76. package/blueprints/platform-cloudflare-kv/contributions/probes/real-account-eventual-consistency-smoke.mjs +131 -67
  77. package/blueprints/platform-cloudflare-kv/docs/topics.md +1 -1
  78. package/blueprints/platform-docker-compose-host/docs/topics.md +1 -1
  79. package/blueprints/security-auth-clerk/docs/topics.md +1 -1
  80. package/blueprints/security-auth-keycloak/docs/topics.md +1 -1
  81. package/blueprints/security-auth-magic-link/docs/topics.md +1 -1
  82. package/blueprints/security-auth-oauth2/docs/topics.md +1 -1
  83. package/blueprints/security-secrets-management/docs/topics.md +1 -1
  84. package/fixtures/canary-manifest.json +9 -9
  85. package/guidance/harness-template.md +67 -0
  86. package/guidance/managed/agent-instructions-block.hash +1 -1
  87. package/guidance/managed/agent-instructions-block.md +67 -0
  88. package/package.json +2 -2
  89. package/rcf/code-nodes/cn-500.json +12 -0
  90. package/rcf/code-nodes/cn-510.json +12 -0
  91. package/rcf/code-nodes/cn-520.json +14 -0
  92. package/rcf/code-nodes/cn-521.json +12 -0
  93. package/rcf/code-nodes/cn-522.json +13 -0
  94. package/rcf/code-nodes/cn-523.json +12 -0
  95. package/rcf/code-nodes/cn-524.json +14 -0
  96. package/rcf/code-nodes/cn-525.json +12 -0
  97. package/rcf/code-nodes/cn-526.json +14 -0
  98. package/rcf/fbs/fbs-160.json +15 -0
  99. package/rcf/fbs/fbs-165.json +15 -0
  100. package/rcf/fbs/fbs-170.json +16 -0
  101. package/rcf/fbs/fbs-171.json +16 -0
  102. package/rcf/fbs/fbs-172.json +15 -0
  103. package/rcf/fbs/fbs-173.json +17 -0
  104. package/rcf/fbs/fbs-174.json +17 -0
  105. package/rcf/requirements/req-140.json +149 -0
  106. package/rcf/requirements/req-145.json +53 -0
  107. package/rcf/requirements/req-150.json +20 -0
  108. package/rcf/requirements/req-151.json +85 -0
  109. package/rcf/requirements/req-152.json +20 -0
  110. package/rcf/requirements/req-153.json +59 -0
  111. package/rcf/requirements/req-154.json +44 -0
  112. package/rcf/test-suites/ts-071.json +2 -2
  113. package/rcf/test-suites/ts-086.json +2 -2
  114. package/rcf/test-suites/ts-170.json +22 -0
  115. package/rcf/test-suites/ts-175.json +22 -0
  116. package/rcf/test-suites/ts-180.json +30 -0
  117. package/rcf/test-suites/ts-181.json +30 -0
  118. package/rcf/test-suites/ts-182.json +22 -0
  119. package/rcf/test-suites/ts-183.json +38 -0
  120. package/rcf/test-suites/ts-184.json +38 -0
  121. package/rcf/user-stories/us-14001.json +21 -0
  122. package/rcf/user-stories/us-14501.json +24 -0
  123. package/rcf/user-stories/us-15001.json +26 -0
  124. package/rcf/user-stories/us-15101.json +26 -0
  125. package/rcf/user-stories/us-15201.json +21 -0
  126. package/rcf/user-stories/us-15301.json +31 -0
  127. package/rcf/user-stories/us-15401.json +31 -0
  128. package/releases/releases.yaml +11 -1
  129. package/src/blueprint/apply.js +64 -0
  130. package/src/blueprint/consistency-lint.js +432 -0
  131. package/src/blueprint/disposition-ledger.js +224 -0
  132. package/src/blueprint/dispositions.js +44 -0
  133. package/src/blueprint/index.js +19 -0
  134. package/src/browser-verify/manifest-writer.js +53 -2
  135. package/src/browser-verify/pack-runner.js +18 -0
  136. package/src/cli/blueprint.js +103 -0
  137. package/src/setup/identity-seed.js +18 -0
@@ -112,7 +112,10 @@ async function eventSecrecyScan(credentialsPath) {
112
112
  try {
113
113
  creds = JSON.parse(await readFile(credentialsPath, 'utf8'));
114
114
  } catch (err) {
115
- return { verdict: 'warn', detail: `credentials placeholder read failed: ${err.message}` };
115
+ return {
116
+ verdict: 'fail',
117
+ detail: `credentials placeholder read failed at ${credentialsPath}: ${err.message}. Fix the fixture setup step (packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/*/credentials/probe.json.example must exist and parse as JSON) before re-running the probe. A missing placeholder is a fixture-setup defect, not an event-secrecy warning.`,
118
+ };
116
119
  }
117
120
  const eventBody = {
118
121
  kind: 'cloudflaredReady',
@@ -1,27 +1,44 @@
1
1
  // Probe: real-account-connector-healthy.
2
2
  //
3
- // anchorAcId: AC-tunnel-connectorHealthy.
4
- // accountBound: true (gates on CI_HAS_CLOUDFLARE_ACCOUNT AND CI_HAS_HETZNER_ACCOUNT).
3
+ // anchorAcId: AC-tunnel-connectorHealthy (US-39102 contribution;
4
+ // real-account-connector-healthy declared with tunnelConnectorUp).
5
+ // C4 ruling: the AC-tunnel-* symbolic anchors on the tunnel blueprint
6
+ // ARE the AC ids in the shipped US-39101..US-39108 contribution band
7
+ // (see blueprints/edge-cloudflare-tunnel/contributions/user-stories/
8
+ // edge-cloudflare-tunnel-us-3910N.json); the anchors already resolve
9
+ // to the shipped band, so no re-anchor is performed by H-2. The kv
10
+ // treatment differed because those probes anchored AC-5xxx ids that
11
+ // existed nowhere on the shelf; here the ids exist and match by
12
+ // design.
5
13
  //
6
- // Without both env vars the probe records accountBoundSkipped: true and
7
- // the aggregate flips to pass per hetzner-round-7-spec section 3.5.
14
+ // accountBound: true (gates on CI_HAS_CLOUDFLARE_ACCOUNT AND
15
+ // CI_HAS_HETZNER_ACCOUNT; the connector runs on a throwaway Hetzner
16
+ // server).
8
17
  //
9
- // With both env vars the probe (not exercised in this dispatch; the
10
- // blueprint author has no account tokens per the dispatch envelope):
11
- // - Provisions a throwaway Hetzner server via the shared fixture
12
- // provision.mjs (T-1 verb).
13
- // - Stands up cloudflared against a scratch Cloudflare subdomain and asks
14
- // the api for `cloudflared tunnel info <name>`; asserts at least one
15
- // healthy connector.
16
- // - Captures a tunnelConnectorUp event with connector id, region and
17
- // version metadata (metadata only).
18
- // - Tears down via destroy.mjs in an always() block.
18
+ // Env-absent branch: pass-with-skip (spec section 3.5).
19
+ // Env-present branch: provisions a throwaway Hetzner server through
20
+ // the shared T-1 fixture provisionThrowawayServer, drives the
21
+ // cloudflared control surface through the cf-edge shim's
22
+ // cloudflaredTunnelInfo({ tunnelName }) call, asserts at least one
23
+ // HEALTHY connector, and captures a tunnelConnectorUp event
24
+ // (metadata-only: connector id, region, version). If the fixture
25
+ // provisioning throws (missing HCLOUD_TOKEN, quota, etc.), the
26
+ // probe FAILS with a pointer naming the fixture step: the tunnel
27
+ // connector cannot report healthy on a server that never provisioned.
28
+ //
29
+ // The shim seams (cloudflaredTunnelInfo, event capture) allow the
30
+ // driver path and mutation switches to be exercised locally without
31
+ // a real Cloudflare account; the real-account run happens at HQ
32
+ // gate time.
19
33
 
20
34
  import { runShim, accountBoundSkippedResult } from './probe-utils.mjs';
21
35
 
22
36
  export const anchorAcIds = ['AC-tunnel-connectorHealthy'];
23
37
  export const accountBound = true;
24
38
 
39
+ const TUNNEL_NAME_ENV = 'CF_TUNNEL_NAME';
40
+ const DEFAULT_TUNNEL_NAME = 'h2-cf-probe-integrity-scratch';
41
+
25
42
  export default async function runProbe() {
26
43
  const results = [];
27
44
  const extra = {
@@ -30,6 +47,7 @@ export default async function runProbe() {
30
47
  CI_HAS_CLOUDFLARE_ACCOUNT: process.env.CI_HAS_CLOUDFLARE_ACCOUNT === 'true',
31
48
  CI_HAS_HETZNER_ACCOUNT: process.env.CI_HAS_HETZNER_ACCOUNT === 'true',
32
49
  },
50
+ events: [],
33
51
  };
34
52
  const hasAll = extra.envPresent.CI_HAS_CLOUDFLARE_ACCOUNT && extra.envPresent.CI_HAS_HETZNER_ACCOUNT;
35
53
  if (!hasAll) {
@@ -40,15 +58,86 @@ export default async function runProbe() {
40
58
  ));
41
59
  return { results, extra };
42
60
  }
43
- results.push({
44
- anchorAcId: 'AC-tunnel-connectorHealthy',
45
- verdict: 'warn',
46
- detail: 'account-bound path present in this dispatch environment but not exercised by the blueprint-author role; run under the delivery-ci-workflows lane with the account tokens loaded.',
47
- });
48
- return { results, extra };
61
+
62
+ // Env-present path: real driver. All account-bound work runs through
63
+ // the cf-edge shim, which itself delegates to the hetzner fixture in
64
+ // production and returns synthetic responses under the fixture-side
65
+ // H2_CF_TUNNEL_SHIM_MODE token. Probe body reads no mutation-switch
66
+ // env var and imports no fixture module directly.
67
+ const {
68
+ provisionScratchServer, destroyScratchServer, cloudflaredTunnelInfo,
69
+ } = await import(
70
+ '../../../../packages/rcf-lite/test/fixtures/cf-edge/h2-cf-tunnel-shim.mjs'
71
+ );
72
+
73
+ const runId = process.env.GITHUB_RUN_ID || `local-${Date.now()}`;
74
+ let provisioned = null;
75
+ try {
76
+ provisioned = await provisionScratchServer({ runId });
77
+ extra.provisioned = {
78
+ id: provisioned.id, name: provisioned.name, location: provisioned.location, shimMode: provisioned.shimMode,
79
+ };
80
+ } catch (err) {
81
+ results.push({
82
+ anchorAcId: 'AC-tunnel-connectorHealthy',
83
+ verdict: 'fail',
84
+ detail: `fixture provisionScratchServer failed: ${err.message}. The tunnel connector cannot report healthy on a server that never provisioned. Fix the fixture step (packages/rcf-lite/test/fixtures/hetzner-throwaway-server/provision.mjs) or the account tokens (HCLOUD_TOKEN) before re-running.`,
85
+ });
86
+ return { results, extra };
87
+ }
88
+
89
+ try {
90
+ const tunnelName = process.env[TUNNEL_NAME_ENV] || DEFAULT_TUNNEL_NAME;
91
+ extra.tunnelName = tunnelName;
92
+ let info;
93
+ try {
94
+ info = await cloudflaredTunnelInfo({ tunnelName });
95
+ } catch (err) {
96
+ results.push({
97
+ anchorAcId: 'AC-tunnel-connectorHealthy',
98
+ verdict: 'fail',
99
+ detail: `cloudflared tunnel info shim call failed: ${err.message}. Fix the cf-edge shim (packages/rcf-lite/test/fixtures/cf-edge/h2-cf-tunnel-shim.mjs) or the cloudflared control surface before re-running.`,
100
+ });
101
+ return { results, extra };
102
+ }
103
+ extra.shimMode = info.shimMode;
104
+ extra.connectorCount = info.connectors.length;
105
+ const healthy = info.connectors.filter((c) => String(c.status || '').toUpperCase() === 'HEALTHY');
106
+ if (healthy.length === 0) {
107
+ results.push({
108
+ anchorAcId: 'AC-tunnel-connectorHealthy',
109
+ verdict: 'fail',
110
+ detail: `cloudflared tunnel info returned ${info.connectors.length} connector(s), none HEALTHY. Statuses observed: ${info.connectors.map((c) => String(c.status || 'MISSING')).join(', ') || '(no connectors)'}.`,
111
+ });
112
+ return { results, extra };
113
+ }
114
+ // Capture tunnelConnectorUp event, metadata only (id, region, version).
115
+ for (const c of healthy) {
116
+ extra.events.push({
117
+ kind: 'tunnelConnectorUp',
118
+ connectorId: String(c.id ?? ''),
119
+ region: String(c.region ?? ''),
120
+ version: String(c.version ?? ''),
121
+ });
122
+ }
123
+ results.push({
124
+ anchorAcId: 'AC-tunnel-connectorHealthy',
125
+ verdict: 'pass',
126
+ detail: `cloudflared tunnel info returned ${info.connectors.length} connector(s), ${healthy.length} HEALTHY (shim mode ${info.shimMode}); tunnelConnectorUp event captured for each healthy connector with metadata-only payload (connectorId, region, version).`,
127
+ });
128
+ return { results, extra };
129
+ } finally {
130
+ // Always attempt teardown; a failed teardown is a fixture concern
131
+ // and rides sweep-orphans, not this probe.
132
+ try {
133
+ await destroyScratchServer(provisioned);
134
+ } catch (_err) {
135
+ // Best-effort; the nightly sweep-orphans job collects any leak.
136
+ }
137
+ }
49
138
  }
50
139
 
51
- const engine = { kind: 'real-account-cloudflared-info', image: 'cloudflared + hcloud (throwaway server)', healthy: true };
140
+ const engine = { kind: 'real-account-cloudflared-info', image: 'cloudflared + hcloud (throwaway server) via cf-edge shim', healthy: true };
52
141
  if (import.meta.url === `file://${process.argv[1]}`) {
53
142
  await runShim('real-account-connector-healthy', engine, runProbe);
54
143
  }
@@ -1,28 +1,45 @@
1
1
  // Probe: real-account-tunnel-hostname-routes.
2
2
  //
3
- // anchorAcId list:
4
- // - AC-tunnel-hostnameRoutes (public-hostname sub-case)
5
- // - AC-tunnel-accessGated (access-gated sub-case, gated on CI_HAS_CLOUDFLARE_ACCESS)
6
- // accountBound: true (CI_HAS_CLOUDFLARE_ACCOUNT AND CI_HAS_HETZNER_ACCOUNT for
7
- // the public-hostname sub-case; add CI_HAS_CLOUDFLARE_ACCESS for the
8
- // access-gated sub-case per Q4 ratification).
3
+ // anchorAcId list (unchanged per H-2 C4 ruling; the AC-tunnel-* ids
4
+ // ARE the shipped AC ids in the US-39104 / US-39106 contribution
5
+ // bodies):
6
+ // - AC-tunnel-hostnameRoutes (public-hostname sub-case: ingress rule
7
+ // routes public hostname to internal service URL and returns 200).
8
+ // - AC-tunnel-accessGated (access-gated sub-case: Access-gated
9
+ // composition; unauthenticated request refused, JWT-carrying
10
+ // request accepted; per US-39106).
9
11
  //
10
- // Without the env vars the sub-cases skip independently and each records
11
- // accountBoundSkipped: true. The aggregate flips to pass per hetzner-
12
- // round-7-spec section 3.5.
12
+ // accountBound: true.
13
+ // Gating (amendment 4 verbatim):
14
+ // - Public-hostname sub-case activated on CI_HAS_CLOUDFLARE_ACCOUNT
15
+ // AND CI_HAS_HETZNER_ACCOUNT.
16
+ // - Access-gated sub-case activated only when CI_HAS_CLOUDFLARE_ACCESS
17
+ // is ALSO set. Env-absent branch keeps pass-with-skip; a pass must
18
+ // never be reachable from CI_HAS_CLOUDFLARE_ACCOUNT presence alone.
19
+ // - HQ note: Cloudflare Access is not enabled on the HQ account, so
20
+ // at gate time the AUD sub-case stays pass-with-skip; expected,
21
+ // not a driver defect.
13
22
  //
14
- // With the env vars (not exercised in this dispatch; the blueprint
15
- // author has no account tokens per the dispatch envelope):
16
- // - Public-hostname sub-case: undici curl against the scratch subdomain,
17
- // asserts 200 and the internal service payload.
18
- // - Access-gated sub-case: two fixture identities; the JWT-less request
19
- // is refused at the edge; the JWT-carrying request proceeds.
23
+ // Env-present, sub-case active: undici-based fetch driver against the
24
+ // scratch subdomain via the cf-edge shim (production path uses Node's
25
+ // built-in fetch; shim-synthetic path lets local runs exercise the
26
+ // driver body without account access). For the access-gated
27
+ // sub-case: send an unauthenticated request (must be refused with
28
+ // 4xx) AND a JWT-carrying request (must be accepted with 2xx). A
29
+ // pass requires BOTH results.
20
30
 
21
31
  import { runShim, accountBoundSkippedResult } from './probe-utils.mjs';
22
32
 
23
33
  export const anchorAcIds = ['AC-tunnel-hostnameRoutes', 'AC-tunnel-accessGated'];
24
34
  export const accountBound = true;
25
35
 
36
+ const PUBLIC_URL_ENV = 'CF_TUNNEL_PUBLIC_URL';
37
+ const ACCESS_URL_ENV = 'CF_TUNNEL_ACCESS_URL';
38
+ const ACCESS_AUDIENCE_ENV = 'CF_ACCESS_AUDIENCE';
39
+ const ACCESS_TEAM_SECRET_ENV = 'CF_ACCESS_TEAM_SECRET';
40
+ const DEFAULT_PUBLIC_URL = 'https://h2-cf-public.scratch.example.invalid/health';
41
+ const DEFAULT_ACCESS_URL = 'https://h2-cf-gated.scratch.example.invalid/protected';
42
+
26
43
  export default async function runProbe() {
27
44
  const results = [];
28
45
  const extra = {
@@ -38,36 +55,112 @@ export default async function runProbe() {
38
55
  };
39
56
  const hasPublic = extra.envPresent.CI_HAS_CLOUDFLARE_ACCOUNT && extra.envPresent.CI_HAS_HETZNER_ACCOUNT;
40
57
  const hasGated = hasPublic && extra.envPresent.CI_HAS_CLOUDFLARE_ACCESS;
58
+
41
59
  if (!hasPublic) {
42
60
  results.push(accountBoundSkippedResult(
43
61
  'AC-tunnel-hostnameRoutes',
44
62
  ['CI_HAS_CLOUDFLARE_ACCOUNT', 'CI_HAS_HETZNER_ACCOUNT'],
45
- 'public-hostname sub-case: undici curl against the scratch subdomain not exercised. Set CI_HAS_CLOUDFLARE_ACCOUNT=true AND CI_HAS_HETZNER_ACCOUNT=true to run this path.',
63
+ 'public-hostname sub-case: undici fetch against the scratch subdomain not exercised. Set CI_HAS_CLOUDFLARE_ACCOUNT=true AND CI_HAS_HETZNER_ACCOUNT=true to run this path.',
46
64
  ));
47
- } else {
48
- results.push({
49
- anchorAcId: 'AC-tunnel-hostnameRoutes',
50
- verdict: 'warn',
51
- detail: 'public-hostname sub-case: account-bound path present but not exercised by the blueprint-author role; run under the delivery-ci-workflows lane.',
52
- });
53
65
  }
54
66
  if (!hasGated) {
55
67
  results.push(accountBoundSkippedResult(
56
68
  'AC-tunnel-accessGated',
57
69
  ['CI_HAS_CLOUDFLARE_ACCOUNT', 'CI_HAS_HETZNER_ACCOUNT', 'CI_HAS_CLOUDFLARE_ACCESS'],
58
- 'access-gated sub-case: two-identity JWT check against the scratch subdomain not exercised. Requires all three env vars (CI_HAS_CLOUDFLARE_ACCESS added per Q4 ratification).',
70
+ 'access-gated sub-case: two-identity JWT check against the scratch subdomain not exercised. Requires all three env vars; a pass here is unreachable from CI_HAS_CLOUDFLARE_ACCOUNT presence alone (amendment 4 verbatim).',
59
71
  ));
60
- } else {
61
- results.push({
62
- anchorAcId: 'AC-tunnel-accessGated',
63
- verdict: 'warn',
64
- detail: 'access-gated sub-case: account-bound path present but not exercised by the blueprint-author role; run under the delivery-ci-workflows lane.',
65
- });
66
72
  }
73
+ if (!hasPublic && !hasGated) {
74
+ return { results, extra };
75
+ }
76
+
77
+ // Sub-case active: load the shim seams.
78
+ const { fetchTunnelHostname, mintScratchIdentity } = await import(
79
+ '../../../../packages/rcf-lite/test/fixtures/cf-edge/h2-cf-tunnel-shim.mjs'
80
+ );
81
+
82
+ if (hasPublic) {
83
+ const url = process.env[PUBLIC_URL_ENV] || DEFAULT_PUBLIC_URL;
84
+ extra.publicHostnameUrl = url;
85
+ try {
86
+ const resp = await fetchTunnelHostname(url, { method: 'GET' });
87
+ extra.publicHostnameResponse = { status: resp.status, shimMode: resp.shimMode, bodyLength: resp.textBody.length };
88
+ if (resp.status >= 200 && resp.status < 300) {
89
+ results.push({
90
+ anchorAcId: 'AC-tunnel-hostnameRoutes',
91
+ verdict: 'pass',
92
+ detail: `public-hostname sub-case: fetch(${url}) returned ${resp.status} (shim mode ${resp.shimMode}), body length ${resp.textBody.length}. Ingress rule routes the public hostname to the internal service URL.`,
93
+ });
94
+ } else {
95
+ results.push({
96
+ anchorAcId: 'AC-tunnel-hostnameRoutes',
97
+ verdict: 'fail',
98
+ detail: `public-hostname sub-case: fetch(${url}) returned ${resp.status} (shim mode ${resp.shimMode}). Expected 2xx; the ingress rule did not route the public hostname to a healthy internal service.`,
99
+ });
100
+ }
101
+ } catch (err) {
102
+ results.push({
103
+ anchorAcId: 'AC-tunnel-hostnameRoutes',
104
+ verdict: 'fail',
105
+ detail: `public-hostname sub-case: fetch(${url}) threw: ${err.message}. Check the scratch subdomain, the tunnel manifest ingress rules, or the cf-edge shim.`,
106
+ });
107
+ }
108
+ }
109
+
110
+ if (hasGated) {
111
+ const url = process.env[ACCESS_URL_ENV] || DEFAULT_ACCESS_URL;
112
+ const audience = process.env[ACCESS_AUDIENCE_ENV] || 'h2-cf-scratch-audience';
113
+ const secret = process.env[ACCESS_TEAM_SECRET_ENV] || 'h2-cf-scratch-team-secret';
114
+ extra.accessGatedUrl = url;
115
+ let refusalResp;
116
+ let acceptResp;
117
+ try {
118
+ refusalResp = await fetchTunnelHostname(url, { method: 'GET' });
119
+ } catch (err) {
120
+ results.push({
121
+ anchorAcId: 'AC-tunnel-accessGated',
122
+ verdict: 'fail',
123
+ detail: `access-gated sub-case: unauthenticated fetch(${url}) threw: ${err.message}.`,
124
+ });
125
+ return { results, extra };
126
+ }
127
+ try {
128
+ const jwt = mintScratchIdentity({ audience, subject: 'h2-cf-scratch-identity', secret });
129
+ acceptResp = await fetchTunnelHostname(url, {
130
+ method: 'GET',
131
+ headers: { authorization: `Bearer ${jwt}` },
132
+ });
133
+ } catch (err) {
134
+ results.push({
135
+ anchorAcId: 'AC-tunnel-accessGated',
136
+ verdict: 'fail',
137
+ detail: `access-gated sub-case: JWT-carrying fetch(${url}) threw: ${err.message}.`,
138
+ });
139
+ return { results, extra };
140
+ }
141
+ extra.accessGatedRefusal = { status: refusalResp.status, shimMode: refusalResp.shimMode };
142
+ extra.accessGatedAccept = { status: acceptResp.status, shimMode: acceptResp.shimMode };
143
+ const refused = refusalResp.status >= 400 && refusalResp.status < 500;
144
+ const accepted = acceptResp.status >= 200 && acceptResp.status < 300;
145
+ if (refused && accepted) {
146
+ results.push({
147
+ anchorAcId: 'AC-tunnel-accessGated',
148
+ verdict: 'pass',
149
+ detail: `access-gated sub-case: unauthenticated request refused with ${refusalResp.status} and JWT-carrying request accepted with ${acceptResp.status} (shim mode ${refusalResp.shimMode}). Access gate composed correctly.`,
150
+ });
151
+ } else {
152
+ results.push({
153
+ anchorAcId: 'AC-tunnel-accessGated',
154
+ verdict: 'fail',
155
+ detail: `access-gated sub-case: expected 4xx-then-2xx; observed refusal status ${refusalResp.status} and JWT-accept status ${acceptResp.status} (shim mode ${refusalResp.shimMode}). Gate is drifted: unauthenticated request should be refused and JWT request accepted.`,
156
+ });
157
+ }
158
+ }
159
+
67
160
  return { results, extra };
68
161
  }
69
162
 
70
- const engine = { kind: 'real-account-tunnel-curl', image: 'undici + cloudflared (scratch subdomain)', healthy: true };
163
+ const engine = { kind: 'real-account-tunnel-fetch', image: 'undici fetch + cf-edge shim (scratch subdomain)', healthy: true };
71
164
  if (import.meta.url === `file://${process.argv[1]}`) {
72
165
  await runShim('real-account-tunnel-hostname-routes', engine, runProbe);
73
166
  }
@@ -46,7 +46,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
46
46
  | application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
47
47
  | application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
48
48
  | persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
49
- | object-storage-s3 | 28101-28899 | 29xx | shipped v1.0.0 | `objectStorageContract` |
49
+ | object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
50
50
  | messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
51
51
  | jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
52
52
  | platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
@@ -46,7 +46,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
46
46
  | application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
47
47
  | application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
48
48
  | persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
49
- | object-storage-s3 | 28101-28899 | 29xx | shipped v1.0.0 | `objectStorageContract` |
49
+ | object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
50
50
  | messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
51
51
  | jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
52
52
  | platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
@@ -42,7 +42,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
42
42
  | application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
43
43
  | application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
44
44
  | persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
45
- | object-storage-s3 | 28101-28899 | 29xx | shipped v1.0.0 | `objectStorageContract` |
45
+ | object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
46
46
  | messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
47
47
  | jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
48
48
  | platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
@@ -43,7 +43,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
43
43
  | application-empty-error-states | 22101-22899 | 23xx | shipped v1.0.0 | none |
44
44
  | application-file-upload | 23101-23899 | 24xx | shipped v1.0.0 | none |
45
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` |
46
+ | object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
47
47
  | messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
48
48
  | jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
49
49
  | platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
@@ -1,5 +1,24 @@
1
1
  # messaging-queue-cloudflare changelog
2
2
 
3
+ ## 1.0.2 - 2026-09-08
4
+
5
+ H-2 follow-up (`w-2026-09-08-dave-017`, addendum item 6 fixture defect): the real-account queue probe now self-provisions its own throwaway Queue + consumer Worker + telemetry KV namespace, closing the second-tier undeclared-env skip HQ hit at the real-account gate on 2026-09-08 (`REAL RUN: PARTIAL`, `MERGE DECISION: HOLD`). No shipped-code capability change.
6
+
7
+ - `real-account-concurrency-smoke.mjs` rewritten against a new self-provisioning fixture shim at `packages/rcf-lite/test/fixtures/cf-platform/h2-cf-queue-real-account-shim.mjs`. AC-29108-2 requires a real consumer to process the published messages ("the consumer processes them concurrently up to the documented 250-invocation cap"), so the shim mints a scratch Cloudflare Queue under `h2-cf-probe-integrity-scratch-q-`, a scratch KV namespace for consumer telemetry under `h2-cf-probe-integrity-scratch-kv-tel-`, and a throwaway consumer Worker under `h2-cf-probe-integrity-scratch-w-` bound to the queue (consumer) with `RCF_TEST_QUEUE` (shipped queue producer binding name matching `packages/rcf-lite/test/fixtures/infra-s3-and-queue/wrangler.toml`) + `RCF_TEST_TELEMETRY_KV` bindings. NO workers.dev subdomain is enabled (Dave ruling 376b4f30): the consumer is invoked BY THE QUEUE, not over HTTP; the driver publishes via the Cloudflare Queues REST publish endpoint and reads consumer telemetry via the KV REST list + get endpoints. The consumer Worker's source is a plain module (`h2-cf-queue-consumer-worker.mjs`); its `queue()` handler records one telemetry entry per invocation (start / end timestamps + batchSize) to the bound telemetry KV namespace; the driver computes `maxConcurrent` from an interval-overlap analysis on those timestamps.
8
+ - Declared env for a real-account run is now `CI_HAS_CLOUDFLARE_ACCOUNT` + `CF_ACCOUNT_ID` + `CF_API_TOKEN` (optional `CF_API_BASE_URL` test override; optional `CF_QUEUE_MESSAGE_COUNT` per section 3.5). The `CF_QUEUE_WORKER_URL` and `CF_QUEUE_NAME` second-tier gates are removed; the fixture provides them itself. Every skip-causing env var enumerated in `report.extra.envDeclared` on the probe result and mirrored in the shim's `DECLARED_ENV` export.
9
+ - Teardown deletes the consumer Worker first (so the queue has no active consumer at delete time), then the Queue, then the telemetry KV namespace, all by EXACT id / EXACT name against the mint record with three independent prefix guards per resource. `workerDelete` and `queueDelete` are idempotent on 404 (Dave ruling 4e9ff62d items 3+4): a botched-retry teardown treats an already-gone resource as success rather than stranding siblings.
10
+ - Separate crash-recovery `sweepOrphans` code path filters over the account by the frozen throwaway prefix constants and deletes each match by exact identity; the sweep is structurally unable to select a non-prefixed name. All account listings paginate to completion (Dave ruling 4e9ff62d item 5): KV namespaces + Queues use the documented `per_page` + `page` loop, Workers scripts use the cursor loop, so `sweepOrphans` is not silently truncated to page one on a busy account. Dedicated sweep-safety test at `packages/rcf-lite/test/fixtures/cf-platform/test/h2-cf-sweep-safety.test.mjs` seeds the mock account with the ten live production Worker script names on the operator account plus mid-string-prefix decoys AND a 250-per-type multi-page cover, and asserts sweep selects zero of them.
11
+ - Queue consumer-attach body sends `type: "worker"` (Dave ruling 4e9ff62d item 1: survives CF schema tightening on the discriminated attach union) and omits `dead_letter_queue` entirely rather than sending an explicit `null` (item 2).
12
+ - Local proof harness (`packages/rcf-lite/test/fixtures/cf-platform/test/mock-cf-api-server.mjs`) is a zero-dep in-process mock of the Cloudflare REST contract, not the wire. **The local run exercises OUR lifecycle logic against a mock of Cloudflare's contract; the real-account gate is the only surface that proves the wire format.** The probe is therefore never described as "locally verified" - the local runs are our own lifecycle-logic proof; wire correctness is proven at the HQ real-account gate.
13
+
14
+ ## 1.0.1 - 2026-09-08
15
+
16
+ H-2 hardening train (`h2-cf-platform-probe-integrity`): probe-integrity patch. No capability change.
17
+
18
+ - Replaced the `accountBoundSkipped: true` pass-stub in `real-account-concurrency-smoke.mjs` with a real 500-message concurrency driver against a live Queues binding on a deployed Worker. When `CI_HAS_CLOUDFLARE_ACCOUNT` is set with `CF_QUEUE_WORKER_URL` and `CF_QUEUE_NAME`, the driver publishes 500 messages through the producer facade in 10 concurrent 50-message batches, polls consumer telemetry via `/stats`, and asserts `drained`, `withinCap` and `observedConcurrency > 1` against Cloudflare's documented push-invocation cap (250). The env-absent branch keeps the pass-with-skip shape. Locally proven against `wrangler dev --local` end-to-end (500/500 messages in 583 ms; local queue serialises so the concurrency assertion FAIL tail is the expected local-proof outcome; on real Cloudflare push consumers the assertion passes at HQ gate time).
19
+ - Re-verified the Cloudflare Queues limits URL (`https://developers.cloudflare.com/queues/platform/limits/`) returns `HTTP/2 200`; recorded in the H-2 PR provenance (2026-09-08T13:38:07Z).
20
+ - Extended the fixture Worker at `packages/rcf-lite/test/fixtures/infra-s3-and-queue/src/worker.mjs` with consumer telemetry and `/publish-batch`, `/stats` and `/reset` endpoints; added `wrangler ^4.0.0` as devDep on the fixture package.
21
+
3
22
  ## 1.0.0 - 2026-09-06
4
23
 
5
24
  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.
@@ -60,7 +60,7 @@ Each probe is a Node module under `contributions/probes/` exporting the round-5
60
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
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
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 |
63
+ | `real-account-concurrency-smoke` | AC-29108-2 | Self-provisioning (v1.0.2): the fixture shim (`packages/rcf-lite/test/fixtures/cf-platform/h2-cf-queue-real-account-shim.mjs`) mints a scratch Cloudflare Queue under the H-2 prefix `h2-cf-probe-integrity-scratch-q-`, a scratch KV namespace for consumer telemetry under `h2-cf-probe-integrity-scratch-kv-tel-`, and a throwaway consumer Worker under `h2-cf-probe-integrity-scratch-w-` bound to the queue (consumer) with `RCF_TEST_QUEUE` (shipped queue binding name) + `RCF_TEST_TELEMETRY_KV` bindings. The Worker has NO fetch surface and NO workers.dev subdomain is enabled (Dave ruling 376b4f30): the consumer is invoked BY THE QUEUE, not over HTTP. Driver publishes 500 messages in 10 concurrent 50-message batches via the Cloudflare Queues REST publish endpoint, polls the telemetry KV namespace via the KV REST list + get endpoints until drained, and asserts `totalConsumed === published` and `1 < maxConcurrent <= 250` per https://developers.cloudflare.com/queues/platform/limits/. Teardown deletes Worker, Queue and telemetry KV namespace on exit; the shim's separate `sweepOrphans` path lists over the account with full pagination (Dave ruling 4e9ff62d item 5) and deletes any prefixed residue after a mid-run crash. **The local proof exercises OUR lifecycle logic against a mock of Cloudflare's contract; the real-account gate is the only surface that proves the wire format.** Without `CI_HAS_CLOUDFLARE_ACCOUNT` records `accountBoundSkipped: true`. | true |
64
64
 
65
65
  ## Anatomy
66
66
 
@@ -111,7 +111,7 @@ Two live paths are unproven at v1.0.0 and are recorded per AC below so the gate
111
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
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
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.
114
+ - **AC-29108-2** (real-account concurrency smoke: 500 messages, consumer concurrency up to 250): as of v1.0.2 the probe SELF-PROVISIONS the throwaway Queue + consumer Worker + telemetry KV namespace under the H-2 prefixes and requires no hand-provisioned CI infrastructure (`w-2026-09-08-dave-017`). Declared env for a live run: `CI_HAS_CLOUDFLARE_ACCOUNT` + `CF_ACCOUNT_ID` + `CF_API_TOKEN`. 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 local proof harness under `packages/rcf-lite/test/fixtures/cf-platform/test/` is a mock of Cloudflare's REST contract, not the wire; the local runs exercise OUR lifecycle logic against that mock, and the real-account gate is the only surface that proves the wire format.**
115
115
 
116
116
  ## Other limitations (not mechanism-reach gaps)
117
117
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "slug": "messaging-queue-cloudflare",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "category": "messaging",
5
5
  "capabilities": ["queue"],
6
6
  "suggestedCompanions": [