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
@@ -7,15 +7,33 @@
7
7
  // events fire on the paired operations, then deletes the key and
8
8
  // asserts kvMiss fires on a subsequent read.
9
9
  //
10
- // Also covers AC-5102-1 by extending the same run with a list-with-
11
- // prefix pass over 10 keys under flags/ so the round-trip probe
12
- // carries the compact case for the list mechanism.
10
+ // Also covers AC-31104-1 by extending the same run with a list-
11
+ // with-prefix pass over 10 keys under flags/ so the round-trip
12
+ // probe carries the compact case for the list mechanism.
13
13
  //
14
- // anchorAcId: AC-5101-1 (primary; AC-5101-2 and AC-5102-1 covered
15
- // as additional results).
14
+ // AC-31102-1 (env.CACHE dereferenced in exactly one fixture source
15
+ // file) is carried here as a runtime observable: the probe reads
16
+ // the fixture src/ tree at run time and asserts exactly one file
17
+ // (src/kv-facade.mjs) contains the env.CACHE substring. This gives
18
+ // the sole-reader-grep AC a runtime observable in a probe result
19
+ // alongside the anatomy-test assertion (dispatch addendum, first
20
+ // dispatch groundwork note 2).
21
+ //
22
+ // anchorAcId: AC-31103-1 (primary: put/get round-trip and paired
23
+ // events per US-31103). Additional results cover AC-31101-1
24
+ // (facadeReady on boot), AC-31103-2 (delete then kvMiss),
25
+ // AC-31104-1 (list-with-prefix compact case) and AC-31102-1
26
+ // (sole-reader-grep runtime observable).
16
27
  // accountBound: false.
17
28
 
18
- export const anchorAcId = 'AC-5101-1';
29
+ import { readdir, readFile } from 'node:fs/promises';
30
+ import { dirname, resolve } from 'node:path';
31
+ import { fileURLToPath } from 'node:url';
32
+
33
+ const HERE = dirname(fileURLToPath(import.meta.url));
34
+ const FIXTURE_SRC = resolve(HERE, '..', '..', '..', '..', 'packages', 'rcf-lite', 'test', 'fixtures', 'cf-platform', 'src');
35
+
36
+ export const anchorAcId = 'AC-31103-1';
19
37
  export const accountBound = false;
20
38
 
21
39
  export default async function runProbe() {
@@ -29,20 +47,21 @@ export default async function runProbe() {
29
47
 
30
48
  const results = [];
31
49
 
32
- // AC-5001-1 seam: ready() fires facadeReady with metadata-only
33
- // payload. Not the anchor here (that's TS-080 anatomy), but the
34
- // signal is worth surfacing.
50
+ // AC-31101-1 seam: ready() fires facadeReady with metadata-only
51
+ // payload. Not the primary anchor here, but the signal is worth
52
+ // surfacing on this probe as an additional result.
35
53
  await facade.ready();
36
54
  const ready = events.filter((e) => e.event === 'facadeReady');
37
55
  results.push({
38
- anchorAcId: 'AC-5001-1',
56
+ anchorAcId: 'AC-31101-1',
39
57
  verdict: ready.length === 1 && ready[0].key === null && ready[0].size === 0 ? 'pass' : 'fail',
40
58
  detail: ready.length === 1
41
59
  ? `facadeReady fired once with key=${ready[0].key} size=${ready[0].size} ttl=${ready[0].ttl} timestamp=${ready[0].timestamp}`
42
60
  : `facadeReady expected once, observed ${ready.length}`,
43
61
  });
44
62
 
45
- // AC-5101-1: put/get round-trip returns same bytes and pairs events.
63
+ // AC-31103-1: put/get round-trip returns same bytes and pairs
64
+ // paired kvWrite / kvHit events.
46
65
  const key = 'kv-facade-round-trip/hello';
47
66
  const value = 'hello workers-kv';
48
67
  await facade.put(key, value, { metadata: { v: 1 } });
@@ -51,27 +70,29 @@ export default async function runProbe() {
51
70
  const hitEvents = events.filter((e) => e.event === 'kvHit');
52
71
  const roundTripPass = got === value && writeEvents.length === 1 && hitEvents.length === 1;
53
72
  results.push({
54
- anchorAcId: 'AC-5101-1',
73
+ anchorAcId: 'AC-31103-1',
55
74
  verdict: roundTripPass ? 'pass' : 'fail',
56
75
  detail: roundTripPass
57
76
  ? `put "${key}" then get returned same bytes; events: kvWrite=1 kvHit=1; write size=${writeEvents[0].size}`
58
77
  : `put/get mismatch: got=${JSON.stringify(got)} kvWrite=${writeEvents.length} kvHit=${hitEvents.length}`,
59
78
  });
60
79
 
61
- // AC-5101-2: delete then get returns null and emits kvMiss.
80
+ // AC-31103-2: delete then get returns null and emits kvMiss.
62
81
  await facade.delete(key);
63
82
  const afterDelete = await facade.get(key);
64
83
  const missEvents = events.filter((e) => e.event === 'kvMiss');
65
84
  const deletePass = afterDelete === null && missEvents.length >= 1;
66
85
  results.push({
67
- anchorAcId: 'AC-5101-2',
86
+ anchorAcId: 'AC-31103-2',
68
87
  verdict: deletePass ? 'pass' : 'fail',
69
88
  detail: deletePass
70
89
  ? `delete then get returned null; kvMiss events observed=${missEvents.length}`
71
90
  : `delete branch fault: afterDelete=${JSON.stringify(afterDelete)} kvMiss=${missEvents.length}`,
72
91
  });
73
92
 
74
- // AC-5102-1: list with prefix returns all 10 keys with metadata shape.
93
+ // AC-31104-1: list with prefix returns all 10 keys with metadata
94
+ // shape. Extended compact case on this probe; list-with-prefix
95
+ // probe holds the primary anchor.
75
96
  const listBinding = createInMemoryKv();
76
97
  const listEvents = [];
77
98
  const listFacade = createKvFacade({ binding: listBinding, eventSink: (rec) => listEvents.push(rec) });
@@ -81,12 +102,33 @@ export default async function runProbe() {
81
102
  const listed = await listFacade.list({ prefix: 'flags/' });
82
103
  const listOk = listed.keys.length === 10 && listed.keys.every((k) => typeof k.name === 'string' && k.name.startsWith('flags/') && k.metadata && typeof k.metadata === 'object');
83
104
  results.push({
84
- anchorAcId: 'AC-5102-1',
105
+ anchorAcId: 'AC-31104-1',
85
106
  verdict: listOk ? 'pass' : 'fail',
86
107
  detail: listOk
87
108
  ? `list({prefix: flags/}) returned 10 keys with metadata shape; listComplete=${listed.listComplete}`
88
109
  : `list-with-prefix fault: keyCount=${listed.keys.length} sample=${JSON.stringify(listed.keys[0])}`,
89
110
  });
90
111
 
112
+ // AC-31102-1 runtime observable: grep the fixture src/ tree for
113
+ // env.CACHE. The dereference must appear in exactly one file
114
+ // (src/kv-facade.mjs); the raw KV binding must not leak into any
115
+ // other module (or the future adapter swap would need to move
116
+ // multiple call sites).
117
+ const entries = await readdir(FIXTURE_SRC);
118
+ const hits = [];
119
+ for (const name of entries) {
120
+ if (!name.endsWith('.mjs')) continue;
121
+ const text = await readFile(resolve(FIXTURE_SRC, name), 'utf8');
122
+ if (text.includes('env.CACHE')) hits.push(name);
123
+ }
124
+ const soleReaderPass = hits.length === 1 && hits[0] === 'kv-facade.mjs';
125
+ results.push({
126
+ anchorAcId: 'AC-31102-1',
127
+ verdict: soleReaderPass ? 'pass' : 'fail',
128
+ detail: soleReaderPass
129
+ ? `env.CACHE dereferenced in exactly one fixture src/ module: ${hits[0]}`
130
+ : `sole-reader-grep fault: expected exactly kv-facade.mjs to dereference env.CACHE; observed ${JSON.stringify(hits)}`,
131
+ });
132
+
91
133
  return { results };
92
134
  }
@@ -3,13 +3,14 @@
3
3
  // Puts 10 keys under the shared prefix flags/ through the facade,
4
4
  // calls facade.list({prefix: flags/}), asserts all 10 return with
5
5
  // correct metadata shape. Independent of the round-trip probe; the
6
- // round-trip probe also covers this AC but this probe carries the
7
- // mechanism as a first-class case for T-1 REQ-002.
6
+ // round-trip probe also covers this AC as an additional result but
7
+ // this probe carries the mechanism as a first-class case for
8
+ // US-31104.
8
9
  //
9
- // anchorAcId: AC-5102-1.
10
+ // anchorAcId: AC-31104-1.
10
11
  // accountBound: false.
11
12
 
12
- export const anchorAcId = 'AC-5102-1';
13
+ export const anchorAcId = 'AC-31104-1';
13
14
  export const accountBound = false;
14
15
 
15
16
  export default async function runProbe() {
@@ -42,7 +43,7 @@ export default async function runProbe() {
42
43
 
43
44
  return {
44
45
  results: [{
45
- anchorAcId: 'AC-5102-1',
46
+ anchorAcId: 'AC-31104-1',
46
47
  verdict: pass ? 'pass' : 'fail',
47
48
  detail: pass
48
49
  ? `list({prefix: flags/}) returned 10 keys under the prefix; every entry carries {name, metadata} with metadata.v=1; decoy key outside prefix was NOT included`
@@ -1,102 +1,166 @@
1
1
  // Real-account eventual-consistency smoke for platform-cloudflare-kv
2
- // v1.0.0.
2
+ // v1.1.0.
3
3
  //
4
- // Opens the facade against a real Cloudflare KV namespace via the
5
- // Workers KV REST API (per https://developers.cloudflare.com/api/
6
- // operations/workers-kv-namespace-write-key-value-pair). Writes a
7
- // fixture key, waits up to 60 seconds bounded (poll every 2s), then
8
- // reads and asserts the write eventually appears. The probe does
9
- // NOT bind a specific propagation time; only that a write eventually
10
- // becomes visible under the 60-second cap. Cleans up the written
11
- // key on exit.
4
+ // Self-provisioning: the probe mints its own scratch KV namespace via
5
+ // the fixture shim (packages/rcf-lite/test/fixtures/cf-platform/
6
+ // h2-cf-kv-real-account-shim.mjs), writes a fixture key, waits up to
7
+ // 60 seconds bounded (poll every 2s) for the same-region read to see
8
+ // the write, then deletes the key AND destroys the scratch namespace.
9
+ // Positive evidence captured: the CF-assigned namespace id, the exact
10
+ // scratch title used (H-2 prefix "h2-cf-probe-integrity-scratch-kv-"),
11
+ // the scratch key (H-2 prefix "h2-storage-smoke-"), elapsed milli-
12
+ // seconds to first same-region visibility, and the PUT status code.
13
+ // The teardown is fail-safe: a mid-run crash surfaces the scratch id
14
+ // through the shim's persisted record; the shim's sweepOrphans entry
15
+ // point cleans any residue by listing over the account and filtering
16
+ // on the throwaway prefix.
12
17
  //
13
- // accountBound: true. Without CI_HAS_CLOUDFLARE_ACCOUNT (and the
14
- // paired CF_ACCOUNT_ID, CF_KV_NAMESPACE_ID, CF_API_TOKEN env vars),
15
- // the probe records accountBoundSkipped: true and the aggregate
16
- // flips to pass per spec section 3.5.
18
+ // accountBound: true. Without CI_HAS_CLOUDFLARE_ACCOUNT the probe
19
+ // records accountBoundSkipped: true and the aggregate flips to pass
20
+ // per spec section 3.5. With CI_HAS_CLOUDFLARE_ACCOUNT=true the probe
21
+ // either produces positive evidence of execution or fails; no third
22
+ // outcome (dispatch requirement 5).
17
23
  //
18
- // anchorAcId: AC-5401-1.
24
+ // Declared env (dispatch requirement 4): every env var that can cause
25
+ // a skip or an early return is enumerated in report.extra.envDeclared
26
+ // and mirrored in the fixture shim's DECLARED_ENV export. If a new
27
+ // gate is added and does not appear in DECLARED_ENV the anatomy test
28
+ // fails.
29
+ //
30
+ // anchorAcId: AC-31103-1. The eventual-consistency round-trip proves
31
+ // the shipped put/get shape on a real Cloudflare KV namespace,
32
+ // re-covering AC-31103-1 (facade put then get returns the same bytes
33
+ // with paired kvWrite / kvHit events on the shipped binding contract).
34
+ //
35
+ // Local-proof scope (Dave ruling 376b4f30): the local test harness
36
+ // under packages/rcf-lite/test/fixtures/cf-platform/test/ is a mock
37
+ // of Cloudflare's REST contract, not the wire. The local run
38
+ // exercises OUR lifecycle logic against a mock of Cloudflare's
39
+ // contract; the real-account gate is the only surface that proves
40
+ // the wire format. This probe is never described as "locally
41
+ // verified" - the local runs are our own lifecycle-logic proof;
42
+ // wire correctness is proven at the HQ real-account gate.
43
+
44
+ import {
45
+ mintScratchNamespace, destroyScratchNamespace,
46
+ putScratchKey, getScratchKey, deleteScratchKey,
47
+ generateKey, DECLARED_ENV, NAMESPACE_PREFIX, KEY_PREFIX,
48
+ } from '../../../../packages/rcf-lite/test/fixtures/cf-platform/h2-cf-kv-real-account-shim.mjs';
19
49
 
20
- export const anchorAcId = 'AC-5401-1';
50
+ export const anchorAcId = 'AC-31103-1';
21
51
  export const accountBound = true;
22
52
 
23
53
  const CAP_MS = 60_000;
24
54
  const POLL_INTERVAL_MS = 2_000;
25
- const API_BASE = 'https://api.cloudflare.com/client/v4';
55
+
56
+ function skipResult(detail) {
57
+ return {
58
+ results: [{
59
+ anchorAcId: 'AC-31103-1',
60
+ verdict: 'pass',
61
+ detail,
62
+ accountBoundSkipped: true,
63
+ }],
64
+ extra: {
65
+ accountBoundSkipped: true,
66
+ envDeclared: Array.from(DECLARED_ENV),
67
+ throwawayPrefixes: { namespaceTitle: NAMESPACE_PREFIX, kvKey: KEY_PREFIX },
68
+ },
69
+ };
70
+ }
26
71
 
27
72
  export default async function runProbe() {
28
73
  if (process.env.CI_HAS_CLOUDFLARE_ACCOUNT !== 'true') {
74
+ return skipResult('accountBoundSkipped: CI_HAS_CLOUDFLARE_ACCOUNT is not set to true; spec section 3.5 pass-with-skip. Declared env for a live run: CI_HAS_CLOUDFLARE_ACCOUNT + CF_ACCOUNT_ID + CF_API_TOKEN (CF_API_BASE_URL optional test override).');
75
+ }
76
+ if (!process.env.CF_ACCOUNT_ID || !process.env.CF_API_TOKEN) {
29
77
  return {
30
78
  results: [{
31
- anchorAcId: 'AC-5401-1',
32
- verdict: 'pass',
33
- detail: 'accountBoundSkipped: CI_HAS_CLOUDFLARE_ACCOUNT is not set to true; spec section 3.5 pass-with-skip.',
79
+ anchorAcId: 'AC-31103-1',
80
+ verdict: 'fail',
81
+ detail: `CI_HAS_CLOUDFLARE_ACCOUNT=true but one of CF_ACCOUNT_ID / CF_API_TOKEN is missing: accountIdPresent=${!!process.env.CF_ACCOUNT_ID} tokenPresent=${!!process.env.CF_API_TOKEN}.`,
34
82
  }],
35
- extra: { accountBoundSkipped: true },
83
+ extra: {
84
+ envDeclared: Array.from(DECLARED_ENV),
85
+ throwawayPrefixes: { namespaceTitle: NAMESPACE_PREFIX, kvKey: KEY_PREFIX },
86
+ },
36
87
  };
37
88
  }
38
89
 
39
- const accountId = process.env.CF_ACCOUNT_ID;
40
- const namespaceId = process.env.CF_KV_NAMESPACE_ID;
41
- const token = process.env.CF_API_TOKEN;
42
- if (!accountId || !namespaceId || !token) {
90
+ const runId = process.env.GITHUB_RUN_ID || `local-${Date.now()}`;
91
+ let namespace = null;
92
+ const evidence = {
93
+ envDeclared: Array.from(DECLARED_ENV),
94
+ throwawayPrefixes: { namespaceTitle: NAMESPACE_PREFIX, kvKey: KEY_PREFIX },
95
+ runId,
96
+ };
97
+ try {
98
+ namespace = await mintScratchNamespace({ runId });
99
+ evidence.namespaceId = namespace.id;
100
+ evidence.namespaceTitle = namespace.title;
101
+ } catch (err) {
43
102
  return {
44
103
  results: [{
45
- anchorAcId: 'AC-5401-1',
104
+ anchorAcId: 'AC-31103-1',
46
105
  verdict: 'fail',
47
- detail: `CI_HAS_CLOUDFLARE_ACCOUNT=true but one of CF_ACCOUNT_ID/CF_KV_NAMESPACE_ID/CF_API_TOKEN is missing: accountIdPresent=${!!accountId} namespaceIdPresent=${!!namespaceId} tokenPresent=${!!token}`,
106
+ detail: `mintScratchNamespace failed: ${err.message}`,
48
107
  }],
108
+ extra: evidence,
49
109
  };
50
110
  }
51
111
 
52
- const key = `platform-cloudflare-kv/real-account-smoke/${Date.now()}`;
112
+ const key = generateKey({ runId });
53
113
  const value = `smoke-${Math.random().toString(36).slice(2)}`;
54
- const putUrl = `${API_BASE}/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${encodeURIComponent(key)}`;
55
- const authHeaders = { authorization: `Bearer ${token}`, 'content-type': 'text/plain' };
114
+ evidence.key = key;
115
+ evidence.valueSample = value;
56
116
 
57
- const putResp = await fetch(putUrl, { method: 'PUT', headers: authHeaders, body: value });
58
- if (!putResp.ok) {
59
- return {
60
- results: [{
61
- anchorAcId: 'AC-5401-1',
62
- verdict: 'fail',
63
- detail: `real-account smoke: PUT returned status ${putResp.status}`,
64
- }],
65
- };
66
- }
117
+ try {
118
+ const put = await putScratchKey({ namespaceId: namespace.id, key, value });
119
+ evidence.putStatus = put.status;
67
120
 
68
- const start = Date.now();
69
- let observed = null;
70
- while (Date.now() - start < CAP_MS) {
71
- const getResp = await fetch(putUrl, { method: 'GET', headers: { authorization: `Bearer ${token}` } });
72
- if (getResp.ok) {
73
- const body = await getResp.text();
74
- if (body === value) {
75
- observed = body;
121
+ const start = Date.now();
122
+ let observed = null;
123
+ while (Date.now() - start < CAP_MS) {
124
+ const got = await getScratchKey({ namespaceId: namespace.id, key });
125
+ if (got.ok && got.text === value) {
126
+ observed = got.text;
127
+ evidence.getStatusOnHit = got.status;
76
128
  break;
77
129
  }
130
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
78
131
  }
79
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
80
- }
132
+ const elapsed = Date.now() - start;
133
+ evidence.elapsedMs = elapsed;
134
+ evidence.capMs = CAP_MS;
81
135
 
82
- const elapsed = Date.now() - start;
136
+ // Delete the scratch key regardless of outcome; the namespace
137
+ // destroy in the finally block would remove it anyway, but the
138
+ // per-key delete proves the delete verb on the shipped path.
139
+ try {
140
+ const del = await deleteScratchKey({ namespaceId: namespace.id, key });
141
+ evidence.deleteStatus = del.status;
142
+ } catch (err) {
143
+ evidence.deleteError = err.message;
144
+ }
83
145
 
84
- // Clean up the written key regardless of outcome.
85
- try {
86
- await fetch(putUrl, { method: 'DELETE', headers: { authorization: `Bearer ${token}` } });
87
- } catch (_err) {
88
- // Best effort; do not fail the probe on cleanup.
146
+ const pass = observed === value;
147
+ return {
148
+ results: [{
149
+ anchorAcId: 'AC-31103-1',
150
+ verdict: pass ? 'pass' : 'fail',
151
+ detail: pass
152
+ ? `real-account KV eventual-consistency: mint namespace ${namespace.id} (${namespace.title}); PUT key ${key} (status ${evidence.putStatus}); write eventually appeared on same-region GET within elapsed=${elapsed}ms (CAP=${CAP_MS}ms); DELETE key (status ${evidence.deleteStatus ?? 'unset'}); namespace destroyed on teardown.`
153
+ : `real-account KV eventual-consistency: mint namespace ${namespace.id} (${namespace.title}); PUT key ${key} (status ${evidence.putStatus}); write did NOT appear within CAP=${CAP_MS}ms; elapsed=${elapsed}ms.`,
154
+ }],
155
+ extra: evidence,
156
+ };
157
+ } finally {
158
+ try {
159
+ const teardown = await destroyScratchNamespace(namespace);
160
+ evidence.namespaceDestroyed = teardown.destroyed;
161
+ } catch (err) {
162
+ evidence.teardownError = err.message;
163
+ process.stderr.write(`h2-cf-kv probe: teardown failed for namespace ${namespace && namespace.id}: ${err.message}; sweepOrphans will collect on next run.\n`);
164
+ }
89
165
  }
90
-
91
- const pass = observed === value;
92
- return {
93
- results: [{
94
- anchorAcId: 'AC-5401-1',
95
- verdict: pass ? 'pass' : 'fail',
96
- detail: pass
97
- ? `write eventually appeared on the same-region read within elapsed=${elapsed}ms (CAP=${CAP_MS}ms)`
98
- : `write did not appear within CAP=${CAP_MS}ms; elapsed=${elapsed}ms`,
99
- }],
100
- extra: { elapsedMs: elapsed, cap: CAP_MS },
101
- };
102
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` |
@@ -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` |
@@ -57,7 +57,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
57
57
  | application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
58
58
  | application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
59
59
  | persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
60
- | object-storage-s3 | 28101-28899 | 29xx | shipped v1.0.0 | `objectStorageContract` |
60
+ | object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
61
61
  | messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
62
62
  | jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
63
63
  | 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` |
@@ -57,7 +57,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
57
57
  | application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
58
58
  | application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
59
59
  | persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
60
- | object-storage-s3 | 28101-28899 | 29xx | shipped v1.0.0 | `objectStorageContract` |
60
+ | object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
61
61
  | messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
62
62
  | jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
63
63
  | 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` |
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "registerCanary": [
3
3
  {
4
- "id": "rc-2026-09-08-001",
5
- "createdAt": "2026-09-08T05:23:05.136Z",
6
- "buildVersion": "0.25.0-mockdriver",
4
+ "id": "rc-2026-09-09-001",
5
+ "createdAt": "2026-09-09T16:11:26.221Z",
6
+ "buildVersion": "0.26.0-mockdriver",
7
7
  "fixturePromptId": "canary-prompt-01",
8
8
  "responseWordCount": 55,
9
9
  "grades": {
@@ -34,9 +34,9 @@
34
34
  "shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
35
35
  },
36
36
  {
37
- "id": "rc-2026-09-08-002",
38
- "createdAt": "2026-09-08T05:23:05.137Z",
39
- "buildVersion": "0.25.0-mockdriver",
37
+ "id": "rc-2026-09-09-002",
38
+ "createdAt": "2026-09-09T16:11:26.222Z",
39
+ "buildVersion": "0.26.0-mockdriver",
40
40
  "fixturePromptId": "canary-prompt-02",
41
41
  "responseWordCount": 55,
42
42
  "grades": {
@@ -67,9 +67,9 @@
67
67
  "shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
68
68
  },
69
69
  {
70
- "id": "rc-2026-09-08-003",
71
- "createdAt": "2026-09-08T05:23:05.137Z",
72
- "buildVersion": "0.25.0-mockdriver",
70
+ "id": "rc-2026-09-09-003",
71
+ "createdAt": "2026-09-09T16:11:26.222Z",
72
+ "buildVersion": "0.26.0-mockdriver",
73
73
  "fixturePromptId": "canary-prompt-03",
74
74
  "responseWordCount": 55,
75
75
  "grades": {
@@ -252,6 +252,73 @@ miss, feed unreachable) say nothing to the operator; freshness is a
252
252
  convenience, not a gate. Never run the upgrade without the operator's
253
253
  explicit go.
254
254
 
255
+ ### RULE 15: On blueprint apply, dispose every AC.
256
+
257
+ A blueprint's acceptance criteria come in two shapes: `fixed`
258
+ (mechanism-invariant, true for every applying project) and `template`
259
+ (shape given, values project-specific). When you apply a blueprint
260
+ (`rcf define blueprint add ...`), walk every AC the blueprint
261
+ contributed. A `fixed` AC stands as authored: do not weaken or drop
262
+ one, and if one appears wrong for the project, that is a defect against
263
+ the blueprint, escalated on the blueprint's repo, not edited locally. A
264
+ `template` AC is a starting shape: actively dispose of it by accepting
265
+ as written, adjusting with the project's values, or dropping with a
266
+ stated reason recorded against the story. Silent inheritance of a
267
+ `template` AC is a defect. After you have disposed of every `template`
268
+ AC, re-sweep the applied AC set against the scenario-class prompt list
269
+ (credential missing, non-2xx, rate limit, timeout, partial write,
270
+ permission denied, malformed input, already exists, gone, concurrent
271
+ access, quota exhausted, dependency not ready, first-vs-repeat run,
272
+ idempotency, empty and maximal collections, boundary values) so the
273
+ story still covers the mechanism's documented failure paths. Any AC
274
+ that rests on a third-party platform fact must show its vendor
275
+ documentation URL and the ISO-8601 date the fact was verified; a
276
+ citation older than the operator's tolerance is re-verified before the
277
+ AC is treated as ground truth.
278
+
279
+ A blueprint's seed docs are a minimal representational seed set, not a
280
+ project spec. You have full scope of the project you are driving, and
281
+ you are the party best placed to integrate the seed with the project's
282
+ own chain. After you have disposed of every AC, walk the seed's REQs,
283
+ TACs and ADRs against the project chain (existing REQs, TACs, ADRs and
284
+ the PRD/TAD): resolve integration contradictions you can (parameter
285
+ substitution against a settled project value, naming reconciliation
286
+ against a settled project convention, composition-order reconciliation
287
+ against another applied blueprint), record the disposition in the
288
+ tree, and escalate to the operator only the ones that need a
289
+ product-shape decision or a two-way credential or account decision. An
290
+ internal contradiction (two of the seed's own layers disagreeing about
291
+ the same contract) is not yours to resolve; escalate it as a defect
292
+ against the blueprint's repo and refuse to derive downstream work from
293
+ the ambiguity.
294
+
295
+ When you escalate, pitch the question at the operator's declared
296
+ register. On your first exchange after `rcf init`, if
297
+ `rcf/.identity/profile.md` carries no `operatorRegister` value, ask one
298
+ question and record the answer to the profile: "One quick set-up
299
+ question. When we need to escalate a decision to you, do you want it
300
+ framed as a product-owner question (plain English, outcome-shaped) or
301
+ an engineer question (with the AC id, the seed's assertion, the
302
+ project's convention)? Answer 'product owner', 'engineer', or 'skip'
303
+ and I will hold the default." A `skip` answer writes `unstated`, which
304
+ reads as `productOwner` on the first escalation; if the operator
305
+ answers in method vocabulary, silently promote to `engineer` for the
306
+ rest of the session and offer to persist the change to the profile.
307
+
308
+ ### RULE 16: Verify with positive evidence.
309
+
310
+ Every "verified" claim (in the Test and Finalise stages, in the PR body's
311
+ verification section, in any status update) names the runtime it was
312
+ checked against AND cites the positive evidence of the property (a
313
+ request id, a response body excerpt, a created-then-deleted resource id
314
+ in an inventory diff, a real deploy record). "The check did not show the
315
+ bad signature" is not evidence; "the request returned id 8f7a-... and
316
+ the response body carried the created resource id 12345" is. Where the
317
+ account credentials are not present, record the skip honestly (name the
318
+ env var that was unset), never fabricate a pass on the strength of
319
+ absence. This rule mirrors RULE 15's active-disposition posture: an
320
+ absent check is a skip you name, an actual check is a claim you cite.
321
+
255
322
  ### Session start
256
323
 
257
324
  Run `rcf define validate`. A broken tree is fixed or reported before anything
@@ -1 +1 @@
1
- 129f4018f526ececaef7ae5bea19ecd72be03a957e2cf2ee3323e85f117f6cba
1
+ d35bd2dfec14db5567c2bcfa2934ce58ac5d7c23fd24e47776501660e06063eb