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.
- package/CHANGELOG.md +25 -0
- package/blueprints/application-account-settings/docs/topics.md +1 -1
- package/blueprints/application-admin-console/docs/topics.md +1 -1
- package/blueprints/application-api-rest/docs/topics.md +1 -1
- package/blueprints/application-charts/docs/topics.md +1 -1
- package/blueprints/application-dashboard/docs/topics.md +1 -1
- package/blueprints/application-datatable/docs/topics.md +1 -1
- package/blueprints/application-empty-error-states/docs/topics.md +1 -1
- package/blueprints/application-error-handling/docs/topics.md +1 -1
- package/blueprints/application-file-upload/docs/topics.md +1 -1
- package/blueprints/application-forms-wizard/docs/topics.md +1 -1
- package/blueprints/application-notifications-in-app/docs/topics.md +1 -1
- package/blueprints/application-onboarding-tour/docs/topics.md +1 -1
- package/blueprints/application-spa/docs/topics.md +1 -1
- package/blueprints/delivery-ci-workflows/docs/topics.md +1 -1
- package/blueprints/deploy-cloudflare-workers/docs/topics.md +1 -1
- package/blueprints/deploy-hetzner-server/CHANGELOG.md +21 -0
- package/blueprints/deploy-hetzner-server/blueprint.json +1 -1
- package/blueprints/deploy-hetzner-server/contributions/probes/cloud-init-render-lint.mjs +12 -5
- package/blueprints/deploy-hetzner-server/contributions/probes/hcloud-dry-run-mock.mjs +68 -20
- package/blueprints/deploy-hetzner-server/contributions/probes/manifest-schema-validate.mjs +8 -6
- package/blueprints/deploy-hetzner-server/contributions/probes/probe-utils.mjs +7 -1
- package/blueprints/deploy-hetzner-server/contributions/probes/real-account-cloud-init-hardened.mjs +2 -1
- package/blueprints/deploy-hetzner-server/contributions/templates/cloud-init.yaml.tmpl +26 -6
- package/blueprints/deploy-hetzner-server/docs/topics.md +1 -1
- package/blueprints/edge-cloudflare-access/docs/topics.md +1 -1
- package/blueprints/edge-cloudflare-rate-limiting/docs/topics.md +1 -1
- package/blueprints/edge-cloudflare-tunnel/CHANGELOG.md +10 -0
- package/blueprints/edge-cloudflare-tunnel/blueprint.json +1 -1
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/manifest-schema-validate.mjs +4 -1
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-connector-healthy.mjs +110 -21
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-tunnel-hostname-routes.mjs +123 -30
- package/blueprints/edge-cloudflare-tunnel/docs/topics.md +1 -1
- package/blueprints/edge-cloudflare-turnstile/docs/topics.md +1 -1
- package/blueprints/email-smtp-resend/docs/topics.md +1 -1
- package/blueprints/jobs-background/docs/topics.md +1 -1
- package/blueprints/messaging-queue-cloudflare/CHANGELOG.md +19 -0
- package/blueprints/messaging-queue-cloudflare/README.md +2 -2
- package/blueprints/messaging-queue-cloudflare/blueprint.json +1 -1
- package/blueprints/messaging-queue-cloudflare/contributions/probes/real-account-concurrency-smoke.mjs +192 -37
- package/blueprints/messaging-queue-cloudflare/docs/topics.md +1 -1
- package/blueprints/object-storage-s3/CHANGELOG.md +11 -0
- package/blueprints/object-storage-s3/README.md +14 -1
- package/blueprints/object-storage-s3/blueprint.json +12 -1
- package/blueprints/object-storage-s3/contributions/adrs/adr-2905-object-storage-s3-hetzner-object-storage-provider.json +30 -0
- package/blueprints/object-storage-s3/contributions/probes/hetzner-object-storage-round-trip.mjs +136 -0
- package/blueprints/object-storage-s3/contributions/probes/run-hetzner-object-storage-round-trip.mjs +11 -0
- package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-101.json +18 -0
- package/blueprints/object-storage-s3/contributions/tacs/tac-2904-object-storage-s3-hetzner-endpoint-helper.json +34 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28110.json +25 -0
- package/blueprints/object-storage-s3/docs/topics.md +1 -1
- package/blueprints/object-storage-s3/guide/object-storage-s3.md +36 -0
- package/blueprints/observability-essentials/docs/topics.md +1 -1
- package/blueprints/observability-logging/docs/topics.md +1 -1
- package/blueprints/observability-probe-endpoints/docs/topics.md +1 -1
- package/blueprints/persistence-data-d1/docs/topics.md +1 -1
- package/blueprints/persistence-data-postgres/docs/topics.md +1 -1
- package/blueprints/persistence-data-sqlite/docs/topics.md +1 -1
- package/blueprints/platform-cloudflare-cron-triggers/docs/topics.md +1 -1
- package/blueprints/platform-cloudflare-durable-objects/CHANGELOG.md +15 -0
- package/blueprints/platform-cloudflare-durable-objects/blueprint.json +1 -1
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/namespace-facade-ready.mjs +3 -3
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/probe-utils.mjs +28 -14
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/real-account-storage-smoke.mjs +132 -26
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/sole-reader-scan.mjs +40 -39
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/storage-round-trip.mjs +26 -16
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/websocket-hub-broadcast.mjs +31 -34
- package/blueprints/platform-cloudflare-durable-objects/docs/topics.md +1 -1
- package/blueprints/platform-cloudflare-kv/CHANGELOG.md +40 -1
- package/blueprints/platform-cloudflare-kv/README.md +41 -13
- package/blueprints/platform-cloudflare-kv/blueprint.json +1 -1
- package/blueprints/platform-cloudflare-kv/contributions/probes/cache-aside-hit-then-miss.mjs +26 -26
- package/blueprints/platform-cloudflare-kv/contributions/probes/event-secrecy.mjs +49 -31
- package/blueprints/platform-cloudflare-kv/contributions/probes/facade-round-trip.mjs +58 -16
- package/blueprints/platform-cloudflare-kv/contributions/probes/list-with-prefix.mjs +6 -5
- package/blueprints/platform-cloudflare-kv/contributions/probes/real-account-eventual-consistency-smoke.mjs +131 -67
- package/blueprints/platform-cloudflare-kv/docs/topics.md +1 -1
- package/blueprints/platform-docker-compose-host/docs/topics.md +1 -1
- package/blueprints/security-auth-clerk/docs/topics.md +1 -1
- package/blueprints/security-auth-keycloak/docs/topics.md +1 -1
- package/blueprints/security-auth-magic-link/docs/topics.md +1 -1
- package/blueprints/security-auth-oauth2/docs/topics.md +1 -1
- package/blueprints/security-secrets-management/docs/topics.md +1 -1
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/harness-template.md +67 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +67 -0
- package/package.json +2 -2
- package/rcf/code-nodes/cn-500.json +12 -0
- package/rcf/code-nodes/cn-510.json +12 -0
- package/rcf/code-nodes/cn-520.json +14 -0
- package/rcf/code-nodes/cn-521.json +12 -0
- package/rcf/code-nodes/cn-522.json +13 -0
- package/rcf/code-nodes/cn-523.json +12 -0
- package/rcf/code-nodes/cn-524.json +14 -0
- package/rcf/code-nodes/cn-525.json +12 -0
- package/rcf/code-nodes/cn-526.json +14 -0
- package/rcf/fbs/fbs-160.json +15 -0
- package/rcf/fbs/fbs-165.json +15 -0
- package/rcf/fbs/fbs-170.json +16 -0
- package/rcf/fbs/fbs-171.json +16 -0
- package/rcf/fbs/fbs-172.json +15 -0
- package/rcf/fbs/fbs-173.json +17 -0
- package/rcf/fbs/fbs-174.json +17 -0
- package/rcf/requirements/req-140.json +149 -0
- package/rcf/requirements/req-145.json +53 -0
- package/rcf/requirements/req-150.json +20 -0
- package/rcf/requirements/req-151.json +85 -0
- package/rcf/requirements/req-152.json +20 -0
- package/rcf/requirements/req-153.json +59 -0
- package/rcf/requirements/req-154.json +44 -0
- package/rcf/test-suites/ts-071.json +2 -2
- package/rcf/test-suites/ts-086.json +2 -2
- package/rcf/test-suites/ts-170.json +22 -0
- package/rcf/test-suites/ts-175.json +22 -0
- package/rcf/test-suites/ts-180.json +30 -0
- package/rcf/test-suites/ts-181.json +30 -0
- package/rcf/test-suites/ts-182.json +22 -0
- package/rcf/test-suites/ts-183.json +38 -0
- package/rcf/test-suites/ts-184.json +38 -0
- package/rcf/user-stories/us-14001.json +21 -0
- package/rcf/user-stories/us-14501.json +24 -0
- package/rcf/user-stories/us-15001.json +26 -0
- package/rcf/user-stories/us-15101.json +26 -0
- package/rcf/user-stories/us-15201.json +21 -0
- package/rcf/user-stories/us-15301.json +31 -0
- package/rcf/user-stories/us-15401.json +31 -0
- package/releases/releases.yaml +11 -1
- package/src/blueprint/apply.js +64 -0
- package/src/blueprint/consistency-lint.js +432 -0
- package/src/blueprint/disposition-ledger.js +224 -0
- package/src/blueprint/dispositions.js +44 -0
- package/src/blueprint/index.js +19 -0
- package/src/browser-verify/manifest-writer.js +53 -2
- package/src/browser-verify/pack-runner.js +18 -0
- package/src/cli/blueprint.js +103 -0
- package/src/setup/identity-seed.js +18 -0
package/blueprints/platform-cloudflare-durable-objects/contributions/probes/storage-round-trip.mjs
CHANGED
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
// elicited backends (sql and kv per ADR-3403), drives put/get,
|
|
5
5
|
// delete/get and list({prefix}) on each; asserts the round-trip
|
|
6
6
|
// on both. Also asserts driver.backend reports the elicited
|
|
7
|
-
// answer (AC-33111-1).
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
7
|
+
// answer (AC-33111-1). The backend-mismatch mutation-run is
|
|
8
|
+
// triggered fixture-side by the H-2 shim
|
|
9
|
+
// h2-cf-do-storage-round-trip-shim.mjs, which constructs a driver
|
|
10
|
+
// from an unrecognised backend answer that the shipped factory
|
|
11
|
+
// clamps to sql per ADR-3403 recommendedDefault; the probe body
|
|
12
|
+
// receives the mismatch driver from the shim and surfaces the
|
|
13
|
+
// clamp as an AC-33103-1 fail so a reviewer can see the
|
|
14
|
+
// substitution.
|
|
12
15
|
//
|
|
13
16
|
// anchorAcId: AC-33103-1.
|
|
17
|
+
// Additional-result anchor: AC-33111-1 (driver.backend field
|
|
18
|
+
// present with the elicited answer on every returned driver).
|
|
14
19
|
// accountBound: false.
|
|
15
20
|
|
|
16
21
|
export const anchorAcId = 'AC-33103-1';
|
|
@@ -37,7 +42,8 @@ async function driveRoundTrip(driver, backendLabel) {
|
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
export default async function runProbe() {
|
|
40
|
-
const {
|
|
45
|
+
const { prepareStorageDrivers } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/h2-cf-do-storage-round-trip-shim.mjs');
|
|
46
|
+
const { createInMemoryDoStorage, mismatchDriver, mutationOn } = await prepareStorageDrivers();
|
|
41
47
|
|
|
42
48
|
const results = [];
|
|
43
49
|
|
|
@@ -59,27 +65,31 @@ export default async function runProbe() {
|
|
|
59
65
|
: `round-trip mismatch: sql=${JSON.stringify(sql)} kv=${JSON.stringify(kv)}`,
|
|
60
66
|
});
|
|
61
67
|
|
|
62
|
-
// AC-33111-1: default backend is sql when no arg is passed.
|
|
68
|
+
// AC-33111-1: default backend is sql when no arg is passed. Also
|
|
69
|
+
// covers the driver.backend-field-present requirement across the
|
|
70
|
+
// three driver openings above (sql, kv, default).
|
|
63
71
|
const defaultDriver = createInMemoryDoStorage({});
|
|
64
|
-
const defaultOk = defaultDriver.backend === 'sql'
|
|
72
|
+
const defaultOk = defaultDriver.backend === 'sql' &&
|
|
73
|
+
typeof sqlDriver.backend === 'string' &&
|
|
74
|
+
typeof kvDriver.backend === 'string';
|
|
65
75
|
results.push({
|
|
66
76
|
anchorAcId: 'AC-33111-1',
|
|
67
77
|
verdict: defaultOk ? 'pass' : 'fail',
|
|
68
78
|
detail: defaultOk
|
|
69
|
-
? `driver factory without an explicit backend defaults to sql per ADR-3403 recommendedDefault
|
|
79
|
+
? `driver factory without an explicit backend defaults to sql per ADR-3403 recommendedDefault (observed driver.backend=${defaultDriver.backend}); backend field present on every returned driver (sql=${sqlDriver.backend}, kv=${kvDriver.backend}, default=${defaultDriver.backend})`
|
|
70
80
|
: `default backend mismatch: expected sql, observed ${defaultDriver.backend}`,
|
|
71
81
|
});
|
|
72
82
|
|
|
73
|
-
if (
|
|
83
|
+
if (mutationOn) {
|
|
74
84
|
// The shipped factory clamps any unrecognised backend answer to
|
|
75
|
-
// sql; the
|
|
76
|
-
// fail so a reviewer can see the clamp in action.
|
|
77
|
-
|
|
78
|
-
const clampedButMismatched =
|
|
85
|
+
// sql; the mutation-run surfaces the observation and returns
|
|
86
|
+
// fail so a reviewer can see the clamp in action. The shim
|
|
87
|
+
// constructs the mismatch driver; the probe body reports it.
|
|
88
|
+
const clampedButMismatched = mismatchDriver.backend === 'sql';
|
|
79
89
|
results.push({
|
|
80
90
|
anchorAcId: 'AC-33103-1',
|
|
81
|
-
verdict:
|
|
82
|
-
detail: `
|
|
91
|
+
verdict: 'fail',
|
|
92
|
+
detail: `mutation-run active (fixture shim reports backend-mismatch on): unrecognised backend answer clamped to ${mismatchDriver.backend} (clampedToSql=${clampedButMismatched}) instead of failing at the boundary`,
|
|
83
93
|
});
|
|
84
94
|
}
|
|
85
95
|
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
// Websocket-hub-broadcast probe for platform-cloudflare-durable-objects v1.0.0.
|
|
2
2
|
//
|
|
3
|
-
// Wires a HubObject to
|
|
4
|
-
// (
|
|
5
|
-
// asserts B receives within the
|
|
6
|
-
// Then drives hibernate-and-wake
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// (AC-33106-1)
|
|
12
|
-
// a synthetic body-bearing record and the probe surfaces it,
|
|
13
|
-
// returning fail on the secrecy result.
|
|
3
|
+
// Wires a HubObject to an in-process state stub, accepts two
|
|
4
|
+
// in-process paired sockets (createInProcessSocketPair), drives a
|
|
5
|
+
// broadcast from client A and asserts B receives within the
|
|
6
|
+
// elicited window (default 500ms). Then drives hibernate-and-wake
|
|
7
|
+
// round-trip: hibernate, wake once (doWakeUp emitted,
|
|
8
|
+
// lastBroadcast returned), wake again (no second doWakeUp).
|
|
9
|
+
// Finally scans every emitted record on the sink against the
|
|
10
|
+
// allowed key set {event, key, size, ttl, timestamp, objectName,
|
|
11
|
+
// scheduledTime} and asserts zero forbidden-key hits (AC-33106-1).
|
|
14
12
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
13
|
+
// Two mutation switches are triggered fixture-side by the H-2
|
|
14
|
+
// shim h2-cf-do-websocket-hub-shim.mjs: a PII-leak wrapper on the
|
|
15
|
+
// sink adapter that injects forbidden fields on every emitted
|
|
16
|
+
// record, and a broadcast-hang gate that pauses the probe past
|
|
17
|
+
// the elicited window before it fires the broadcast. Under either
|
|
18
|
+
// mutation the corresponding result trips fail.
|
|
19
19
|
//
|
|
20
|
-
// anchorAcId: AC-33105-1
|
|
21
|
-
//
|
|
20
|
+
// anchorAcId: AC-33105-1.
|
|
21
|
+
// Additional-result anchors: AC-33110-1 (hibernate-and-wake round
|
|
22
|
+
// trip), AC-33106-1 (event-secrecy over every emitted record).
|
|
22
23
|
// accountBound: false.
|
|
23
24
|
|
|
24
25
|
export const anchorAcId = 'AC-33105-1';
|
|
@@ -30,23 +31,19 @@ const FORBIDDEN_KEYS = ['body', 'ssn', 'userId', 'headers', 'cookies', 'request'
|
|
|
30
31
|
export default async function runProbe() {
|
|
31
32
|
const { createInMemoryDoStorage } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/src/do-storage.mjs');
|
|
32
33
|
const { HubObject } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/src/do-hub.mjs');
|
|
33
|
-
const {
|
|
34
|
+
const { createInProcessDoState, createInProcessSocketPair } = await import('./probe-utils.mjs');
|
|
35
|
+
const { prepareHubBroadcastSeams } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/h2-cf-do-websocket-hub-shim.mjs');
|
|
34
36
|
|
|
37
|
+
const seams = prepareHubBroadcastSeams();
|
|
35
38
|
const events = [];
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
...rec,
|
|
39
|
-
body: 'Hello, world (leaked test body)',
|
|
40
|
-
ssn: '123-45-6789',
|
|
41
|
-
userId: 'user_leaked_test_uid',
|
|
42
|
-
});
|
|
43
|
-
const eventSink = process.env.SIMULATE_PII_LEAK === 'true' ? leakySink : cleanSink;
|
|
39
|
+
const baseSink = (rec) => events.push(rec);
|
|
40
|
+
const eventSink = seams.wrapSink(baseSink);
|
|
44
41
|
const storage = createInMemoryDoStorage({ backend: 'sql' });
|
|
45
|
-
const state =
|
|
42
|
+
const state = createInProcessDoState();
|
|
46
43
|
const hub = new HubObject({ state, storage, eventSink, name: 'lobby', hibernateAfterIdleMs: 500 });
|
|
47
44
|
|
|
48
|
-
const sockA =
|
|
49
|
-
const sockB =
|
|
45
|
+
const sockA = createInProcessSocketPair('A');
|
|
46
|
+
const sockB = createInProcessSocketPair('B');
|
|
50
47
|
await hub.accept(sockA);
|
|
51
48
|
await hub.accept(sockB);
|
|
52
49
|
|
|
@@ -55,8 +52,8 @@ export default async function runProbe() {
|
|
|
55
52
|
// AC-33105-1: broadcast from A reaches both A and B within 500ms.
|
|
56
53
|
const started = Date.now();
|
|
57
54
|
const payload = 'ping';
|
|
58
|
-
if (
|
|
59
|
-
await new Promise((r) => setTimeout(r,
|
|
55
|
+
if (seams.hangBeforeBroadcastMs > 0) {
|
|
56
|
+
await new Promise((r) => setTimeout(r, seams.hangBeforeBroadcastMs));
|
|
60
57
|
}
|
|
61
58
|
await hub.webSocketMessage(sockA, JSON.stringify({ type: 'broadcast', payload }));
|
|
62
59
|
const elapsed = Date.now() - started;
|
|
@@ -68,8 +65,8 @@ export default async function runProbe() {
|
|
|
68
65
|
anchorAcId: 'AC-33105-1',
|
|
69
66
|
verdict: broadcastOk ? 'pass' : 'fail',
|
|
70
67
|
detail: broadcastOk
|
|
71
|
-
? `broadcast fan-out: A received ${gotA} B received ${gotB}; elapsed ${elapsed}ms within 500ms window; storage.lastBroadcast persisted`
|
|
72
|
-
: `broadcast fault: gotA=${!!gotA} gotB=${!!gotB} elapsed=${elapsed}ms persisted=${!!persisted}`,
|
|
68
|
+
? `broadcast fan-out (mutation-switches off): A received ${gotA} B received ${gotB}; elapsed ${elapsed}ms within 500ms window; storage.lastBroadcast persisted`
|
|
69
|
+
: `broadcast fault${seams.hangOn ? ' (mutation-run active: fixture shim reports broadcast-hang on)' : ''}: gotA=${!!gotA} gotB=${!!gotB} elapsed=${elapsed}ms persisted=${!!persisted}`,
|
|
73
70
|
});
|
|
74
71
|
|
|
75
72
|
// AC-33110-1: hibernate-and-wake round-trip preserves storage and doWakeUp fires exactly once.
|
|
@@ -105,7 +102,7 @@ export default async function runProbe() {
|
|
|
105
102
|
verdict: secrecyOk ? 'pass' : 'fail',
|
|
106
103
|
detail: secrecyOk
|
|
107
104
|
? `event-secrecy scan across ${events.length} records: allowed-key set {${[...ALLOWED_KEYS].join(', ')}}; forbidden-key hits 0; extra-key hits 0`
|
|
108
|
-
: `event-secrecy fault: forbiddenHits=${JSON.stringify(forbiddenHits.slice(0, 5))} extraKeyHits=${JSON.stringify(extraKeyHits.slice(0, 5))}`,
|
|
105
|
+
: `event-secrecy fault${seams.leakOn ? ' (mutation-run active: fixture shim reports PII-leak on)' : ''}: forbiddenHits=${JSON.stringify(forbiddenHits.slice(0, 5))} extraKeyHits=${JSON.stringify(extraKeyHits.slice(0, 5))}`,
|
|
109
106
|
});
|
|
110
107
|
|
|
111
108
|
return { results };
|
|
@@ -47,7 +47,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
|
|
|
47
47
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
48
48
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
49
49
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
50
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
50
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
51
51
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
52
52
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
53
53
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `platform-cloudflare-kv` are recorded here. The shape follows Keep a Changelog and Semantic Versioning per the blueprint authoring standard.
|
|
4
4
|
|
|
5
|
+
## 1.0.2 (2026-09-08)
|
|
6
|
+
|
|
7
|
+
H-2 follow-up (`w-2026-09-08-dave-017`, addendum item 6 fixture defect): the real-account KV probe now self-provisions its own throwaway 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.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `real-account-eventual-consistency-smoke.mjs` rewritten against a new self-provisioning fixture shim at `packages/rcf-lite/test/fixtures/cf-platform/h2-cf-kv-real-account-shim.mjs`. The shim mints a scratch KV namespace under the frozen throwaway prefix `h2-cf-probe-integrity-scratch-kv-`, writes a fixture key under `h2-storage-smoke-`, polls up to 60 seconds bounded for same-region visibility, deletes the key, and destroys the namespace on exit. Positive evidence captured on every real-account run: namespace id, exact scratch title, key, PUT / GET / DELETE status codes, elapsed ms.
|
|
12
|
+
- Removed the second-tier `CF_KV_NAMESPACE_ID` gate that produced the undeclared skip at the H-2 real-account gate. The declared env for a real-account run is now `CI_HAS_CLOUDFLARE_ACCOUNT` + `CF_ACCOUNT_ID` + `CF_API_TOKEN` (optional test override `CF_API_BASE_URL`), enumerated in the probe's `report.extra.envDeclared` and mirrored in the shim's `DECLARED_ENV` export.
|
|
13
|
+
- Teardown is fail-safe and idempotent: a mid-run crash surfaces the minted id through the shim's persisted scratch record; the shim's separate `sweepOrphans` entry point lists over the account and filters on the throwaway prefix. Sweep is structurally unable to select a non-prefixed name (three independent guards: prefix assert on mint, prefix assert on destroy against the record, prefix assert on the live-listing observation; sweep-only prefix filter mirrors the destroy assertions). A dedicated sweep-safety test at `packages/rcf-lite/test/fixtures/cf-platform/test/h2-cf-kv-real-account-shim.test.mjs` seeds the mock account with the ten live production script names on the operator account plus a sneaky mid-string-prefix name and asserts sweep selects zero of them.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Self-contained mock CF REST API at `packages/rcf-lite/test/fixtures/cf-platform/test/mock-cf-api-server.mjs` (Node `node:http` only, zero-dep) implementing the KV namespaces + values + keys endpoints the shim hits, with page + per_page pagination on the namespaces list. Test at `test/h2-cf-kv-probe-e2e.test.mjs` boots the mock in-process, points the shim at it via `CF_API_BASE_URL`, and drives the probe end-to-end to prove the mint / evidence-capture / teardown / zero-orphans loop before the HQ real-account run ever executes.
|
|
18
|
+
- `kvListNamespaces` in the shared CF REST client (`h2-cf-account-api.mjs`) paginates to completion via the documented `per_page` + `page` loop (Dave ruling 4e9ff62d item 5) so `sweepOrphans` is not silently truncated to page one on a busy account. `kvDeleteNamespace` is idempotent on 404 (Dave ruling 4e9ff62d items 3+4).
|
|
19
|
+
|
|
20
|
+
### Local-proof scope note
|
|
21
|
+
|
|
22
|
+
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 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.** This probe is never described as "locally verified" - the local runs are our own lifecycle-logic proof; wire correctness is proven at the HQ real-account gate.
|
|
23
|
+
|
|
24
|
+
## 1.0.1 (2026-09-08)
|
|
25
|
+
|
|
26
|
+
H-2 hardening train (`h2-cf-platform-probe-integrity`): probe-integrity patch across the four Cloudflare-platform blueprints. No capability change.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Re-anchored all five KV probes and the README from the defunct `AC-5xxx` id space to the shipped `AC-31xxx` band (nine shipped ACs: `AC-31101-1`, `AC-31102-1`, `AC-31103-1`, `AC-31103-2`, `AC-31104-1`, `AC-31105-1`, `AC-31106-1`, `AC-31107-1`, `AC-31108-1`). Every one of the nine now has a runtime observable through a re-anchored probe. Anatomy-test assertions in `packages/rcf-lite/test/blueprint/platform-cloudflare-kv-anatomy.test.js` moved with the re-anchor. Dispatch addendum ruling 1.
|
|
31
|
+
- Moved the `SIMULATE_CACHE_MISS` mutation switch out of `cache-aside-hit-then-miss.mjs` into fixture shim `packages/rcf-lite/test/fixtures/cf-platform/h2-cf-kv-cache-aside-shim.mjs`; the probe body holds zero `SIMULATE_` token references (`AC-15401-1` mutation-purity rule).
|
|
32
|
+
- Moved the `SIMULATE_PII_LEAK` mutation switch out of `event-secrecy.mjs` into fixture shim `packages/rcf-lite/test/fixtures/cf-platform/h2-cf-kv-event-secrecy-shim.mjs`; the probe body holds zero `SIMULATE_` token references.
|
|
33
|
+
- Reworded the "fake clock" comment on `cache-aside-hit-then-miss.mjs` header to name the local test double honestly ("elicited deterministic clock the fixture advances").
|
|
34
|
+
- Reworded the `Known limitations` (CHANGELOG) and `Known mechanism-reach gaps` (README) entries on the loader `elicits[]` `when` block predicate to name the loader-capability uplift as follow-up work item `w-2026-09-08-h3-loader-elicit-when-predicates` (0.26.x capability change, out of H-2's 0.25.x patch scope per Dave ruling 1). The entry describes the current shipped shape and points forward to the follow-up.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Removed the committed fail envelope at `.rcf/reports/blueprints/platform-cloudflare-kv/event-secrecy.json`; committed a shipped-code pass envelope in its place. Regenerated the other KV envelopes so committed envelopes reflect the re-anchored shipped-code runs.
|
|
39
|
+
|
|
40
|
+
### Chain slice
|
|
41
|
+
|
|
42
|
+
- Train chain slice minted inside the reserved H-2 block: `REQ-150..154`, `US-15001..15401`, twelve `AC-15xxx` ACs, `TS-180..184` with eleven TCs, `FBS-170..174`, `CN-520..529` (seven used, three reserved). Full mint transcript in the H-2 PR provenance.
|
|
43
|
+
|
|
5
44
|
## 1.0.0 (2026-09-07)
|
|
6
45
|
|
|
7
46
|
### Added
|
|
@@ -18,4 +57,4 @@ All notable changes to `platform-cloudflare-kv` are recorded here. The shape fol
|
|
|
18
57
|
|
|
19
58
|
- The `real-account-eventual-consistency-smoke` probe records `accountBoundSkipped: true` and aggregates to `pass` when `CI_HAS_CLOUDFLARE_ACCOUNT` is unset (spec section 3.5 pass-with-skip). Full mechanism reach requires a CI environment with the paired `CF_ACCOUNT_ID`, `CF_KV_NAMESPACE_ID` and `CF_API_TOKEN` env vars.
|
|
20
59
|
- The four local probes drive an in-memory KV driver whose shape matches the Workers KV binding at the facade boundary. Vendor surface changes beyond that boundary (a new list cursor format, a header-only limit) are not caught by the local probes; the real-account smoke is the mechanism-reach closure for that gap.
|
|
21
|
-
- The loader's supported `when` block on `elicits[]`
|
|
60
|
+
- The loader's supported `when` block on `elicits[]` currently accepts only `requiresCapability` arrays, so the elicited `kv-metadata-field-pattern` fires unconditionally rather than gating on an `elicitedNonEmpty` predicate for `kv-binding-name`. The elicit fires unconditionally today and the guide teaches the pattern; a loader-capability uplift extending `validateElicits` to accept an `elicitedNonEmpty` predicate is captured as follow-up work item `w-2026-09-08-h3-loader-elicit-when-predicates` (0.26.x capability change, out of H-2's 0.25.x patch scope per Dave ruling 1).
|
|
@@ -80,7 +80,7 @@ under the fixture root.
|
|
|
80
80
|
| `cache-aside-hit-then-miss.mjs` | `AC-31105-1` | no | Wraps a spy origin function with the cache-aside helper, drives one call within TTL (hit, no origin call), advances the fake clock past TTL, drives another call (miss, second origin call). |
|
|
81
81
|
| `event-secrecy.mjs` | `AC-31108-1` | no | Two-layer assertion: whitelist check on every event record and PII-substring scan on the JSON serialisation. A mutation-run under `SIMULATE_PII_LEAK` flips the probe to fail. |
|
|
82
82
|
| `list-with-prefix.mjs` | `AC-31104-1` | no | Puts 10 keys under `flags/` plus one decoy outside the prefix, calls `list({prefix: flags/})`, asserts all 10 return with correct metadata shape and the decoy is absent. |
|
|
83
|
-
| `real-account-eventual-consistency-smoke.mjs` | `AC-31108-1` | yes |
|
|
83
|
+
| `real-account-eventual-consistency-smoke.mjs` | `AC-31108-1` | yes | Self-provisioning: the fixture shim (`packages/rcf-lite/test/fixtures/cf-platform/h2-cf-kv-real-account-shim.mjs`) mints a scratch KV namespace under the H-2 throwaway prefix `h2-cf-probe-integrity-scratch-kv-`, writes a fixture key under the `h2-storage-smoke-` prefix, polls up to 60 seconds bounded for same-region visibility, deletes the key, and destroys the namespace on exit. Positive evidence captured: namespace id, exact scratch title, key, PUT / GET / DELETE status codes, elapsed ms. Idempotent prefix-sweep entry point (`sweepOrphans`) filters over the account by the frozen throwaway prefix and deletes each match by exact id and exact name; the sweep is structurally unable to select a non-prefixed name, paginates KV namespaces to completion (Dave ruling 4e9ff62d item 5), and is exercised by the fixture's sweep-safety test against the ten live production script names on the operator account plus a 250-per-type multi-page cover. Without `CI_HAS_CLOUDFLARE_ACCOUNT` records `accountBoundSkipped: true` and aggregates `pass` per spec section 3.5. **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.** |
|
|
84
84
|
|
|
85
85
|
## How to run the probes
|
|
86
86
|
|
|
@@ -104,8 +104,13 @@ Induced-failure switches:
|
|
|
104
104
|
calls where one was expected and returns `aggregateVerdict: fail`.
|
|
105
105
|
|
|
106
106
|
The real-account smoke needs `CI_HAS_CLOUDFLARE_ACCOUNT=true` plus
|
|
107
|
-
the paired `CF_ACCOUNT_ID
|
|
108
|
-
|
|
107
|
+
the paired `CF_ACCOUNT_ID` and `CF_API_TOKEN` env vars. The fixture
|
|
108
|
+
mints its own throwaway KV namespace under the H-2 prefix; no
|
|
109
|
+
pre-provisioned namespace is required (the earlier `CF_KV_NAMESPACE_ID`
|
|
110
|
+
gate is removed as of v1.1.0 per w-2026-09-08-dave-017). A test
|
|
111
|
+
override `CF_API_BASE_URL` points the shim at a local mock CF REST
|
|
112
|
+
API for local proof; the mock and its test lives under
|
|
113
|
+
`packages/rcf-lite/test/fixtures/cf-platform/test/`.
|
|
109
114
|
|
|
110
115
|
## Known mechanism-reach gaps
|
|
111
116
|
|
|
@@ -118,11 +123,15 @@ env vars.
|
|
|
118
123
|
the aggregate flips to `pass` with `accountBoundSkipped: true` per
|
|
119
124
|
spec section 3.5. Full mechanism reach requires a CI environment
|
|
120
125
|
with the paired env vars set.
|
|
121
|
-
- The elicited `kv-metadata-field-pattern`
|
|
122
|
-
`elicitedNonEmpty` predicate for
|
|
123
|
-
`
|
|
124
|
-
|
|
125
|
-
pattern
|
|
126
|
+
- The elicited `kv-metadata-field-pattern` fires unconditionally
|
|
127
|
+
rather than gating on an `elicitedNonEmpty` predicate for
|
|
128
|
+
`kv-binding-name`; the loader's `validateElicits` accepts only
|
|
129
|
+
`requiresCapability` on the `when` block today. The guide teaches
|
|
130
|
+
the pattern; a loader-capability uplift extending
|
|
131
|
+
`validateElicits` to accept an `elicitedNonEmpty` predicate is
|
|
132
|
+
captured as follow-up work item
|
|
133
|
+
`w-2026-09-08-h3-loader-elicit-when-predicates` (0.26.x capability
|
|
134
|
+
change, out of H-2's 0.25.x patch scope per Dave ruling 1).
|
|
126
135
|
|
|
127
136
|
## Companion suggestions
|
|
128
137
|
|
|
@@ -152,8 +161,27 @@ env vars.
|
|
|
152
161
|
|
|
153
162
|
The anatomy test at
|
|
154
163
|
`packages/rcf-lite/test/blueprint/platform-cloudflare-kv-anatomy.test.js`
|
|
155
|
-
binds every AC on `US-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
164
|
+
binds every AC on the shipped kv user stories `US-31101..US-31108`
|
|
165
|
+
(`AC-31101-1`, `AC-31102-1`, `AC-31103-1`, `AC-31103-2`,
|
|
166
|
+
`AC-31104-1`, `AC-31105-1`, `AC-31106-1`, `AC-31107-1`,
|
|
167
|
+
`AC-31108-1`) to a test case in that file with a resolvable
|
|
168
|
+
`testPointer`, and to the union of the five contributed probe
|
|
169
|
+
result records. The five probes carry the following anchors: the
|
|
170
|
+
facade-round-trip probe holds `AC-31103-1` as primary and adds
|
|
171
|
+
`AC-31101-1` (facadeReady), `AC-31103-2` (delete then kvMiss),
|
|
172
|
+
`AC-31104-1` (list-with-prefix compact case) and `AC-31102-1`
|
|
173
|
+
(sole-reader env.CACHE grep) as additional results; the
|
|
174
|
+
list-with-prefix probe holds `AC-31104-1`; the
|
|
175
|
+
cache-aside-hit-then-miss probe holds `AC-31105-1` and
|
|
176
|
+
`AC-31106-1`; the event-secrecy probe holds `AC-31108-1` and
|
|
177
|
+
`AC-31107-1` (whitelist scan); the real-account-eventual-
|
|
178
|
+
consistency smoke re-covers `AC-31103-1` on a real Cloudflare KV
|
|
179
|
+
namespace. The blueprint contribution IDs are namespaced
|
|
180
|
+
`platform-cloudflare-kv-REQ-001..005` and
|
|
181
|
+
`platform-cloudflare-kv-US-31101..31108` per the shelf-wide
|
|
182
|
+
id-band registry. The prior chain-slice pointer
|
|
183
|
+
paragraph cited a defunct earlier id band that never became chain
|
|
184
|
+
rows on this repo; that citation was removed in the H-2
|
|
185
|
+
(h2-cf-platform-probe-integrity) train per Dave ruling 4
|
|
186
|
+
(2026-09-08 relay 582c2bca): the earlier band was not resurrected
|
|
187
|
+
and every kv probe result now cites the shipped `AC-31xxx` band.
|
package/blueprints/platform-cloudflare-kv/contributions/probes/cache-aside-hit-then-miss.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// Cache-aside hit-then-miss probe for platform-cloudflare-kv
|
|
1
|
+
// Cache-aside hit-then-miss probe for platform-cloudflare-kv
|
|
2
|
+
// v1.0.0.
|
|
2
3
|
//
|
|
3
|
-
// Drives the cache-aside helper against the fixture facade under
|
|
4
|
-
//
|
|
4
|
+
// Drives the cache-aside helper against the fixture facade under
|
|
5
|
+
// an elicited deterministic clock the fixture advances:
|
|
5
6
|
//
|
|
6
7
|
// 1) First getOr(key, origin): miss, origin invoked, writeback,
|
|
7
8
|
// counter=1.
|
|
@@ -10,32 +11,31 @@
|
|
|
10
11
|
// 3) Advance clock past TTL, third getOr: miss, origin invoked
|
|
11
12
|
// again, counter=2.
|
|
12
13
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
14
|
+
// The mutation-run that disables the writeback path (put becomes a
|
|
15
|
+
// no-op so the second read misses the cache and re-invokes origin)
|
|
16
|
+
// is provided by the fixture-side H-2 shim (h2-cf-kv-cache-aside-
|
|
17
|
+
// shim.mjs, reads cache-miss internally); the probe body
|
|
18
|
+
// holds no mutation-switch read of its own (AC-15401-1 mutation-purity
|
|
19
|
+
// rule, brief section 5).
|
|
17
20
|
//
|
|
18
|
-
// anchorAcId: AC-
|
|
21
|
+
// anchorAcId: AC-31105-1 (primary: within-TTL cached branch per
|
|
22
|
+
// US-31105). Additional result covers AC-31106-1 (past-TTL miss
|
|
23
|
+
// branch per US-31106).
|
|
19
24
|
// accountBound: false.
|
|
20
25
|
|
|
21
|
-
export const anchorAcId = 'AC-
|
|
26
|
+
export const anchorAcId = 'AC-31105-1';
|
|
22
27
|
export const accountBound = false;
|
|
23
28
|
|
|
24
|
-
const SIMULATE_CACHE_MISS = process.env.SIMULATE_CACHE_MISS === 'true';
|
|
25
|
-
|
|
26
29
|
export default async function runProbe() {
|
|
27
30
|
const { createInMemoryKv } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/src/kv-driver.mjs');
|
|
28
|
-
const { createKvFacade } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/src/kv-facade.mjs');
|
|
29
31
|
const { createCacheAside } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/src/cache-aside.mjs');
|
|
32
|
+
const { createCacheAsideFacade } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/h2-cf-kv-cache-aside-shim.mjs');
|
|
30
33
|
|
|
31
34
|
let now = 1_000_000_000_000;
|
|
32
35
|
const clock = () => now;
|
|
33
36
|
|
|
34
37
|
const binding = createInMemoryKv({ clock });
|
|
35
|
-
const
|
|
36
|
-
const facade = SIMULATE_CACHE_MISS
|
|
37
|
-
? { ...rawFacade, put: async () => {} }
|
|
38
|
-
: rawFacade;
|
|
38
|
+
const { facade, mutationOn } = createCacheAsideFacade({ binding, eventSink: () => {} });
|
|
39
39
|
|
|
40
40
|
const cache = createCacheAside({ facade, defaultTtl: 5 });
|
|
41
41
|
|
|
@@ -49,19 +49,19 @@ export default async function runProbe() {
|
|
|
49
49
|
|
|
50
50
|
const first = await cache.getOr(key, origin);
|
|
51
51
|
const second = await cache.getOr(key, origin);
|
|
52
|
-
now += 6 * 1000; // advance past 5-second TTL
|
|
52
|
+
now += 6 * 1000; // advance the elicited clock past the 5-second TTL
|
|
53
53
|
const third = await cache.getOr(key, origin);
|
|
54
54
|
|
|
55
55
|
const results = [];
|
|
56
56
|
|
|
57
57
|
const withinTtlPass = originCalls >= 1 && second.source === 'cache' && first.value === second.value;
|
|
58
58
|
results.push({
|
|
59
|
-
anchorAcId: 'AC-
|
|
60
|
-
verdict:
|
|
61
|
-
?
|
|
59
|
+
anchorAcId: 'AC-31105-1',
|
|
60
|
+
verdict: mutationOn
|
|
61
|
+
? 'fail'
|
|
62
62
|
: (withinTtlPass ? 'pass' : 'fail'),
|
|
63
|
-
detail:
|
|
64
|
-
? `
|
|
63
|
+
detail: mutationOn
|
|
64
|
+
? `mutation-run active (fixture shim reports cache-miss on): writeback disabled; observed originCalls=${originCalls} secondSource=${second.source} (expected 1 and cache under the shipped code path)`
|
|
65
65
|
: (withinTtlPass
|
|
66
66
|
? `first miss then cached: originCalls=${originCalls} secondSource=${second.source}`
|
|
67
67
|
: `within-TTL branch fault: originCalls=${originCalls} secondSource=${second.source}`),
|
|
@@ -69,10 +69,10 @@ export default async function runProbe() {
|
|
|
69
69
|
|
|
70
70
|
const pastTtlPass = originCalls === 2 && third.source === 'origin';
|
|
71
71
|
results.push({
|
|
72
|
-
anchorAcId: 'AC-
|
|
73
|
-
verdict:
|
|
74
|
-
detail:
|
|
75
|
-
? `
|
|
72
|
+
anchorAcId: 'AC-31106-1',
|
|
73
|
+
verdict: mutationOn ? 'fail' : (pastTtlPass ? 'pass' : 'fail'),
|
|
74
|
+
detail: mutationOn
|
|
75
|
+
? `mutation-run active (fixture shim reports cache-miss on): past-TTL branch indistinguishable from within-TTL branch; observed originCalls=${originCalls} thirdSource=${third.source}`
|
|
76
76
|
: (pastTtlPass
|
|
77
77
|
? `past-TTL read triggered origin: originCalls=${originCalls} thirdSource=${third.source}`
|
|
78
78
|
: `past-TTL branch fault: originCalls=${originCalls} thirdSource=${third.source}`),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Event-secrecy probe for platform-cloudflare-kv v1.0.0.
|
|
2
2
|
//
|
|
3
3
|
// Two layers of assertion so the whitelist is exercised at the
|
|
4
|
-
// actual boundary AC-
|
|
4
|
+
// actual boundary AC-31108-1 claims, and the shipped code path is
|
|
5
5
|
// exercised end-to-end with a PII fixture body:
|
|
6
6
|
//
|
|
7
7
|
// (1) Direct-boundary assertion: emit an event through the
|
|
@@ -16,40 +16,39 @@
|
|
|
16
16
|
// serialised records for SSN / body / user id substrings
|
|
17
17
|
// returns zero hits.
|
|
18
18
|
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
19
|
+
// The mutation-run that swaps the shipped sink for one that
|
|
20
|
+
// forwards the body / userId / ssn onto every record is provided
|
|
21
|
+
// by the fixture-side H-2 shim (h2-cf-kv-event-secrecy-shim.mjs,
|
|
22
|
+
// reads PII-leak internally); the probe body holds no
|
|
23
|
+
// mutation-switch read of its own (AC-15401-1 mutation-purity rule,
|
|
24
|
+
// brief section 5). A reviewer running with the switch off gets
|
|
25
|
+
// pass; on gets fail with the leaked fields named.
|
|
25
26
|
//
|
|
26
|
-
//
|
|
27
|
+
// The whitelist-scan layer (1) is emitted as an additional result
|
|
28
|
+
// bound to AC-31107-1 (US-31107 "every lifecycle event carries
|
|
29
|
+
// only the metadata-only whitelist"), giving that AC a runtime
|
|
30
|
+
// observable in a probe result alongside the anatomy assertion
|
|
31
|
+
// (dispatch addendum, first dispatch groundwork note 2).
|
|
32
|
+
//
|
|
33
|
+
// anchorAcId: AC-31108-1 (primary: PII fixture whitelist + mutation
|
|
34
|
+
// switch fires fail per US-31108). Additional result covers
|
|
35
|
+
// AC-31107-1 (whitelist scan across every recorded event).
|
|
27
36
|
// accountBound: false.
|
|
28
37
|
|
|
29
|
-
export const anchorAcId = 'AC-
|
|
38
|
+
export const anchorAcId = 'AC-31108-1';
|
|
30
39
|
export const accountBound = false;
|
|
31
40
|
|
|
32
41
|
const PII_STRINGS = ['REDACTED-fixture', '1234', 'ssn'];
|
|
33
42
|
const ALLOWED_KEYS = new Set(['event', 'key', 'size', 'ttl', 'timestamp']);
|
|
34
|
-
const SIMULATE_PII_LEAK = process.env.SIMULATE_PII_LEAK === 'true';
|
|
35
43
|
|
|
36
44
|
export default async function runProbe() {
|
|
37
45
|
const { createInMemoryKv } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/src/kv-driver.mjs');
|
|
38
46
|
const { createKvFacade } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/src/kv-facade.mjs');
|
|
47
|
+
const { createEventSecrecySink } = await import('../../../../packages/rcf-lite/test/fixtures/cf-platform/h2-cf-kv-event-secrecy-shim.mjs');
|
|
39
48
|
|
|
40
49
|
const results = [];
|
|
41
50
|
|
|
42
|
-
const captured =
|
|
43
|
-
const sink = (rec) => {
|
|
44
|
-
if (SIMULATE_PII_LEAK) {
|
|
45
|
-
// Mutation-run: forward the raw record including the PII bag.
|
|
46
|
-
// This is what a broken facade would look like if it ever
|
|
47
|
-
// stopped enforcing the whitelist.
|
|
48
|
-
captured.push({ ...rec, body: rec.body ?? { ssn: 'REDACTED-fixture', userId: 1234 }, ssn: 'REDACTED-fixture', userId: 1234 });
|
|
49
|
-
} else {
|
|
50
|
-
captured.push(rec);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
51
|
+
const { sink, captured, mutationOn } = createEventSecrecySink();
|
|
53
52
|
|
|
54
53
|
const binding = createInMemoryKv();
|
|
55
54
|
const facade = createKvFacade({ binding, eventSink: sink });
|
|
@@ -65,7 +64,8 @@ export default async function runProbe() {
|
|
|
65
64
|
await facade.delete(key);
|
|
66
65
|
|
|
67
66
|
// Layer (1) direct-boundary assertion: every record's keys are a
|
|
68
|
-
// subset of the whitelist.
|
|
67
|
+
// subset of the whitelist. This layer also carries the runtime
|
|
68
|
+
// observable for AC-31107-1 (whitelist-only lifecycle events).
|
|
69
69
|
const forbiddenKeys = new Set();
|
|
70
70
|
for (const rec of captured) {
|
|
71
71
|
for (const k of Object.keys(rec)) {
|
|
@@ -74,10 +74,11 @@ export default async function runProbe() {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// Layer (2) substring assertion: no PII substring in the JSON
|
|
77
|
-
// serialisation of any record OUTSIDE its key field. The KEY
|
|
78
|
-
// is user-supplied and legitimately contains the user id
|
|
79
|
-
// AC-
|
|
80
|
-
// so a scan that included the key would flag its own
|
|
77
|
+
// serialisation of any record OUTSIDE its key field. The KEY
|
|
78
|
+
// itself is user-supplied and legitimately contains the user id
|
|
79
|
+
// (per AC-31108-1 "no substring of the user id beyond the key
|
|
80
|
+
// itself"), so a scan that included the key would flag its own
|
|
81
|
+
// path.
|
|
81
82
|
const withoutKey = captured.map((r) => {
|
|
82
83
|
const { key: _omit, ...rest } = r;
|
|
83
84
|
return rest;
|
|
@@ -86,15 +87,32 @@ export default async function runProbe() {
|
|
|
86
87
|
const piiHits = PII_STRINGS.filter((s) => serialised.includes(s));
|
|
87
88
|
|
|
88
89
|
const whitelistPass = forbiddenKeys.size === 0 && piiHits.length === 0;
|
|
90
|
+
|
|
91
|
+
// AC-31107-1 additional-result: whitelist-only lifecycle events.
|
|
92
|
+
// Under the mutation-run this trips (forbidden keys land on every
|
|
93
|
+
// record); under the shipped path every record carries only the
|
|
94
|
+
// whitelist.
|
|
95
|
+
results.push({
|
|
96
|
+
anchorAcId: 'AC-31107-1',
|
|
97
|
+
verdict: mutationOn
|
|
98
|
+
? (forbiddenKeys.size === 0 ? 'fail' : 'fail')
|
|
99
|
+
: (forbiddenKeys.size === 0 ? 'pass' : 'fail'),
|
|
100
|
+
detail: mutationOn
|
|
101
|
+
? `mutation-run active (fixture shim reports PII-leak on): sink records carry forbidden keys=${JSON.stringify([...forbiddenKeys])} (expected empty under the shipped path)`
|
|
102
|
+
: (forbiddenKeys.size === 0
|
|
103
|
+
? `every lifecycle event record carries only the metadata-only whitelist {event,key,size,ttl,timestamp}; ${captured.length} records observed (facadeReady, kvWrite, kvHit, kvMiss on the PII fixture)`
|
|
104
|
+
: `whitelist breach: forbiddenKeys=${JSON.stringify([...forbiddenKeys])}`),
|
|
105
|
+
});
|
|
106
|
+
|
|
89
107
|
results.push({
|
|
90
|
-
anchorAcId: 'AC-
|
|
91
|
-
verdict:
|
|
108
|
+
anchorAcId: 'AC-31108-1',
|
|
109
|
+
verdict: mutationOn
|
|
92
110
|
? (whitelistPass ? 'fail' : 'fail')
|
|
93
111
|
: (whitelistPass ? 'pass' : 'fail'),
|
|
94
|
-
detail:
|
|
95
|
-
? `
|
|
112
|
+
detail: mutationOn
|
|
113
|
+
? `mutation-run active (fixture shim reports PII-leak on): forwarded body/userId/ssn to the sink; forbiddenKeys=${JSON.stringify([...forbiddenKeys])} piiHits=${JSON.stringify(piiHits)} (expected empty on the shipped code path)`
|
|
96
114
|
: (whitelistPass
|
|
97
|
-
? `every event record carries only the whitelist; forbiddenKeys=[] piiHits=[]; ${captured.length} records observed for put/get/delete/facadeReady`
|
|
115
|
+
? `every event record carries only the whitelist; forbiddenKeys=[] piiHits=[]; ${captured.length} records observed for put/get/delete/facadeReady on the PII fixture`
|
|
98
116
|
: `whitelist breach: forbiddenKeys=${JSON.stringify([...forbiddenKeys])} piiHits=${JSON.stringify(piiHits)}`),
|
|
99
117
|
});
|
|
100
118
|
|