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/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.26.0] - 2026-09-09
|
|
10
|
+
|
|
11
|
+
rcf-lite 0.26.0 is a hardening and authoring release. The Cloudflare-platform blueprints on the shelf pick up real-account probe drivers with positive evidence and one shared account-token env matrix, the blueprint authoring standard gains three new sections (7c REQ-layer sufficiency, 7d positive-evidence verification, 7e single-definition ownership) plus RULE 16 for the managed agent block, and `rcf define blueprint` gains a `lint-consistency` verb, a per-slug disposition ledger, an apply-time disposition prompt and an identity-register field on the seeded profile. The schema floor moves to `@stravica-ai/rcf-schemas` 0.6.2. Nothing here is breaking.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Blueprint chain-consistency lint verb: `rcf define blueprint lint-consistency <source>` (PR #183).** Two passes over one blueprint's own JSON and markdown. Pass 1 checks single-definition ownership: HTTP-header and env-var casing drift between an owning TAC's interfaces, internal structure, responsibilities or purpose and any other artefact, plus an `undeclaredRestatement` finding when an AC restates a token owned by a TAC interface without an `ownerRef.tacId` reference. Pass 2 checks REQ delivery: `reqNoDelivery` when a REQ description carries a promise phrase (must, requires, guarantees, ceiling, never, invariant, on boot, at boot, event) without a `deliveredBy` link into a TAC or ADR, and `reqDeliveryNotCarried` when the named TAC or field is absent or does not appear in the TAC's responsibilities or interfaces. Exit 0 on clean, exit 3 on unsuppressed findings, exit 2 on usage error. `--json` emits the envelope `{ blueprint, findings[], verdict, passCounts }`. Pass-1 findings are suppressible via a `## Known chain-consistency-lint suppressions` section on the blueprint README (one row per finding id with a one-sentence reason); pass-2 findings are not suppressible. A soft-mode CI lint step runs the verb across the shelf so authoring drift surfaces on every PR without blocking the merge yet.
|
|
16
|
+
- **Per-slug disposition ledger and `rcf define blueprint dispositions <slug> [--json]` verb (PR #183).** A ledger file at `rcf/blueprints/<slug>.disposition.json` sits alongside the existing applied sidecar and carries one record per contributed AC with an `action` (`pending-disposition`, `pending-operator`, `accepted`, `adjusted`, `dropped`, `escalated-as-defect`), optional `reason`, `resolvedAt`, `resolvedBy`, `escalatedAt`, `escalatedToOperator`, `defectUrl`, and an optional `sourceDisposition` of `fixed` or `template`. Written on a fresh `rcf define blueprint add`; a re-apply leaves an existing ledger byte-identical so operator edits are never clobbered. The read-only `dispositions` verb prints one line per record with action, reason and resolution timestamp, or the raw ledger with `--json`. Exit 0 always; a missing ledger is a legitimate zero state. Schema follow-up: a `$defs/blueprintDispositionLedger` shape lands on `@stravica-ai/rcf-schemas` in a later release; for now the writer guards the record shape inline.
|
|
17
|
+
- **Apply-time disposition prompt (PR #183).** After a fresh `rcf define blueprint add` the CLI prints a canonical prompt naming the newly initialised ledger and the disposition vocabulary, so an operator lands on the ledger before the next verify. Suppressible with `--no-disposition-prompt`.
|
|
18
|
+
- **Identity register field on the seeded profile (PR #183).** The identity seed template gains a `## Register` section under `## Working preferences` with the three-value vocabulary (`productOwner`, `engineer`, `unstated`) seeded to `unstated`. Register capture stays a managed-block flow, so `cli/init.js` is unchanged; existing projects pick up the field on the next `rcf doctor --fix` sweep of the managed block.
|
|
19
|
+
- **Blueprint authoring standard sections 7c, 7d, 7e (PR #182).** Section 7c (REQ-layer sufficiency): every capability token declared on `blueprint.json:capabilities[]` and every option value on `elicits[]` is backed by at least one must-priority requirement; a blueprint without an `elicits` key is legal only when no apply-time answer is named anywhere in the requirements, guide or TAC records. Section 7d (positive-evidence verification): every verification artefact asserts positive evidence of the property it exists to protect (a request id, a response body excerpt, a created-then-deleted resource id in an inventory diff, or a real deploy record), never the absence of a previously seen bad signature; env vars are declared on the fixture, and `accountBoundSkipped: true` is legal only when the account env is unset and paired with an exact non-empty `reason`. Section 7e (single-definition ownership and authoring order): every named contract is defined exactly once in one owning artefact (TAC for wire shapes and interfaces, ADR for behaviour decisions, REQ for externally promised properties with a `deliveredBy` link into the delivering TAC or ADR); US, AC and guide reference the owner rather than restating it, and authoring order follows. RULE 16 lands in `guidance/managed/agent-instructions-block.md` so applying agents inherit both disciplines from the managed block on the next `rcf doctor --fix` sweep.
|
|
20
|
+
- **Ten new blueprint-authoring-checklist rows in section 6 (PR #182).** Three rows from section 7c, five from section 7d, two from section 7e; the block hash and the `harness-template.md` fragment regenerate via `scripts/gen-managed-artefacts.mjs`.
|
|
21
|
+
- **Positive-evidence remap at record composition (PR #182).** `packages/rcf-lite/src/browser-verify/manifest-writer.js:composeBrowserVerificationRecord` remaps a `checks[]` or `preChecks[]` record with `verdict: 'pass'` and neither an `evidence` field nor `accountBoundSkipped: true` (with a non-empty `reason`) to `verdict: 'fail'` with `detail: 'positive-evidence-missing'`. The remap fires at record composition so the composed record is what the manifest carries and the fail surfaces at the ship gate. `pack-runner.js` carries `evidence`, `accountBoundSkipped` and `reason` through from each check's and pre-check's `run` outcome so the composer sees the fields.
|
|
22
|
+
- **H-2 Cloudflare-platform probe integrity (PR #179).** The Cloudflare-platform blueprints on the shelf (`platform-cloudflare-kv`, `platform-cloudflare-durable-objects`, `messaging-queue-cloudflare`, `edge-cloudflare-tunnel`) pick up real-account probe drivers that assert positive evidence: KV re-anchor and observability on cache-aside miss/hit, DO storage round-trip with anchor coverage, queue real concurrency with dead-letter observation, tunnel connector health and hostname routing. The `packages/rcf-lite/test/fixtures/cf-platform/` fixture README gains a "Declared env vars" section naming the first-tier `CI_HAS_CLOUDFLARE_ACCOUNT` gate and the four second-tier variables the account-bound branches read (`CF_ACCOUNT_ID`, `CF_API_TOKEN`, `CF_KV_NAMESPACE_ID`, `CF_WORKER_NAME`), so a runner without those env vars sees clean `accountBoundSkipped: true` rows with a stated reason rather than a silent pass.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **Schema floor: `@stravica-ai/rcf-schemas` 0.6.1 to 0.6.2.** No new required fields on shipped artefacts; picks up the shape work that supports the disposition-ledger record and the identity-register field.
|
|
27
|
+
|
|
28
|
+
#### Also in this release
|
|
29
|
+
|
|
30
|
+
- **`object-storage-s3` v1.0.0 to v1.1.0 (PR #177).** The Hetzner Object Storage adapter lands as the follow-up sibling to the R2 first adapter shipped in 0.23.0, per the 0.25.0 headline. The blueprint bumps but the rcf-lite package version does not bump for this alone; it rides along in this release.
|
|
31
|
+
- **`deploy-hetzner-server` v1.0.0 to v1.0.1 (PR #178).** Real-path fix for the cloud-init render path resolution so an applied project on Node 24 finds the template through the resolved module directory rather than the workspace-relative path.
|
|
32
|
+
- **Blueprint authoring notes on AC sufficiency and fixed-vs-template dispositions (PRs #180, #181).** Two docs-only additions to `packages/rcf-lite/docs/blueprint-authoring.md` that pair with the section 7c and disposition-ledger work: how many ACs are enough for a given US, and how a contributed AC's `sourceDisposition` reads as `fixed` (delivered verbatim) or `template` (adjusted at apply time).
|
|
33
|
+
|
|
9
34
|
## [0.25.0] - 2026-09-08
|
|
10
35
|
|
|
11
36
|
rcf-lite 0.25.0 opens the Hetzner line with three blueprints in one round: a Hetzner Cloud server as the shipped Linux VM shape with the estate hardening baseline and a snapshot cadence, a docker-compose platform on top with healthchecks, secrets as file mounts and a first-class reverse-proxy artefact, and a Cloudflare Tunnel bridge that composes with Access when Zero Trust is applied and drops to public-hostname mode when it is not. Every real-account probe across the round ships accountBoundSkipped pending the Hetzner and Cloudflare token vault ruling; the local probes carry the ship-time verdicts. The follow-up Hetzner Object Storage adapter ships as a sequential PR under `object-storage-s3` v1.1.0 after this release; the rcf-lite package version does not bump for it.
|
|
@@ -26,7 +26,7 @@ The blueprint claims no new global topics at v1.0.0. Account-settings is a consu
|
|
|
26
26
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
27
27
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
28
28
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
29
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
29
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
30
30
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
31
31
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
32
32
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -26,7 +26,7 @@ The blueprint claims no new global topics at v1.0.0. The console is a consumer o
|
|
|
26
26
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
27
27
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
28
28
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
29
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
29
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
30
30
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
31
31
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
32
32
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -49,7 +49,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
|
|
|
49
49
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
50
50
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
51
51
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
52
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
52
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
53
53
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
54
54
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
55
55
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -25,7 +25,7 @@ The blueprint claims no new global topics at v1.0.0. The chart-component contrac
|
|
|
25
25
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
26
26
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
27
27
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
28
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
28
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
29
29
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
30
30
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
31
31
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -25,7 +25,7 @@ The blueprint claims no new global topics at v1.0.0. The dashboard-shell contrac
|
|
|
25
25
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
26
26
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
27
27
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
28
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
28
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
29
29
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
30
30
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
31
31
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -24,7 +24,7 @@ The blueprint claims no new global topics at v1.0.0. The datatable shell is a ge
|
|
|
24
24
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
25
25
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
26
26
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
27
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
27
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
28
28
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
29
29
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
30
30
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -25,7 +25,7 @@ The blueprint claims no new global topics at v1.0.0. Every ADR (HTTP status cont
|
|
|
25
25
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
26
26
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
27
27
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
28
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
28
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
29
29
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
30
30
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
31
31
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
32
32
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
33
33
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
34
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
34
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
35
35
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
36
36
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
37
37
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -26,7 +26,7 @@ The blueprint claims no new global topics at v1.0.0. Every ADR (transport branch
|
|
|
26
26
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
27
27
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
28
28
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
29
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
29
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
30
30
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
31
31
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
32
32
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -25,7 +25,7 @@ The blueprint claims no new global topics at v1.0.0. Every ADR (navigation postu
|
|
|
25
25
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
26
26
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
27
27
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
28
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
28
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorage` |
|
|
29
29
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
30
30
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
31
31
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -28,7 +28,7 @@ The `application-notifications-` prefix is reserved for the sibling channel blue
|
|
|
28
28
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
29
29
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
30
30
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
31
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
31
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
32
32
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
33
33
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
34
34
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -24,7 +24,7 @@ The blueprint claims no new global topics at v1.0.0. Onboarding-tour is a consum
|
|
|
24
24
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
25
25
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
26
26
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
27
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
27
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
28
28
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
29
29
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
30
30
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -54,7 +54,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
|
|
|
54
54
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
55
55
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
56
56
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
57
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
57
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
58
58
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
59
59
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
60
60
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -60,7 +60,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
|
|
|
60
60
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
61
61
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
62
62
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
63
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
63
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
64
64
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
65
65
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
66
66
|
| 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.
|
|
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,3 +1,24 @@
|
|
|
1
|
+
## 1.0.1
|
|
2
|
+
|
|
3
|
+
H-1 hardening patch train (round-7 real-account gate d-2026-09-08-010 defect list). Fixes nine real-path defects in the shared throwaway-Hetzner-server fixture at packages/rcf-lite/test/fixtures/hetzner-throwaway-server/ so the three T-1 real-account probes return aggregateVerdict pass on a live Hetzner Cloud project unpatched, and destroy plus sweep-orphans never leak a server. Every existing v1.0.0 contribution id byte-identical apart from the cloud-init template bodies (baseline unchanged; adds a NOPASSWD sudoers.d fragment for the deploy user) and the three T-1 mocked probe bodies (mutation-purity clean plus a new rendered-file assertion).
|
|
4
|
+
|
|
5
|
+
Defect list fixed:
|
|
6
|
+
|
|
7
|
+
- (1) provision.mjs reads snake_case fields off the hcloud server create response (public_net.ipv4.ip, datacenter.location.name, server_type.name). v1.0.0 read camelCase and threw on every real-account provision, so destroy.mjs could not find the server to tear down.
|
|
8
|
+
- (2) provision.mjs renders the cloud-init user-data via src/cloud-init-renderer.mjs before hcloud server create fires and writes it to hetzner/servers/rendered/<name>.cloud-init.yaml. v1.0.0 expected a file that no shipped code wrote; every provision failed on missing user-data.
|
|
9
|
+
- (3) src/cloud-init-renderer.mjs resolves each ssh key name to its .publicKey via hcloud ssh-key describe --output json and inlines the material into ssh_authorized_keys under the deploy user. v1.0.0 inlined the key NAME and the deploy user had no working authorized_keys entry.
|
|
10
|
+
- (4) destroy.mjs and sweep-orphans.mjs never pass --output json to a hcloud <resource> delete verb (the delete verbs reject the flag with unknown flag: --output). The tolerant JSON parser accepts arrays, objects and the delete verbs short informational text output.
|
|
11
|
+
- (5) src/snapshot-verb.mjs shells hcloud server create-image --type snapshot --description <label> <server-id> and recovers the created snapshot id via hcloud image list --type=snapshot --output json by matching the serverName label. v1.0.0 called a non-existent hcloud image create-image verb.
|
|
12
|
+
- (6) src/ssh-baseline-check.mjs polls port 22 open and a trivial ssh true exec (bounded up to 6 minutes) before firing the six baseline checks. v1.0.0 spawned ssh immediately after provision and every check timed out on connection refused because cloud-init had not finished.
|
|
13
|
+
- (7) contributions/templates/cloud-init.yaml.tmpl adds a write_files sudoers.d fragment at /etc/sudoers.d/90-deploy-nopasswd (mode 0440) that grants NOPASSWD to the deploy user, so the sudoed baseline checks (cloud-init status --wait, ufw status, iptables -L, systemctl is-active fail2ban) never block on a tty prompt.
|
|
14
|
+
- (9) provision.mjs reads an env override RCF_LITE_CI_SSH_KEY_NAME (per-key comma-separated) for the manifest sshKeyIds, with the manifest value staying the default when the override is unset. Documented in the fixture README.
|
|
15
|
+
|
|
16
|
+
Also: the three T-1 mocked probe modules (cloud-init-render-lint, manifest-schema-validate, hcloud-dry-run-mock) no longer read any process.env.SIMULATE_ switch inside the probe body per the H-1 mutation-purity gate row. The switches live entirely inside fixture-side files (src/cloud-init-renderer.mjs, src/hcloud-mock.mjs, src/provisioner-facade.mjs and the fixture-side run-manifest-schema-validate.mjs shim) and alter INPUT only. The hcloud-dry-run-mock probe now consumes the SAME rendered cloud-init file the real path consumes (renderer writes it, both the mock and real path read it) and asserts an ssh public-key line under the deploy user plus a NOPASSWD directive naming that user (H-1 REQ-145 / AC-14501-1). Report envelopes under .rcf/reports/blueprints/deploy-hetzner-server/ regenerated from the real run.
|
|
17
|
+
|
|
18
|
+
Chain: HQ hardening block H-1 REQ-145..149 / TS-175..179 / FBS-165..169 / CN-510..519 per HQ chain-block ruling 2026-09-08. Real run under Baz one-off approval 2026-09-08: yes, one-off for round 7 gates; Ensure that after any testing etc the Hertner account leaves NO orphaned resources behind. Leave it in the state it started in.
|
|
19
|
+
|
|
20
|
+
Defect (8) T-2 and T-3 stub drivers are OUT of scope for H-1 (H-2 owns them).
|
|
21
|
+
|
|
1
22
|
# deploy-hetzner-server changelog
|
|
2
23
|
|
|
3
24
|
## 1.0.0
|
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
// via the fixture's src/cloud-init-renderer.mjs. Asserts the six hardening
|
|
8
8
|
// baseline blocks appear in the rendered YAML.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
10
|
+
// Purity: the probe reads only the shipped template and the fixture
|
|
11
|
+
// manifest; no process.env.SIMULATE_ switch is read here. The
|
|
12
|
+
// fixture-side renderer at src/cloud-init-renderer.mjs is the sole
|
|
13
|
+
// reader of SIMULATE_HARDENING_DRIFT and it mutates INPUT (the
|
|
14
|
+
// rendered YAML) only; the probe then FAILS naming the missing
|
|
15
|
+
// baseline block per hetzner-round-7-spec-2026-09-07.md section 3.4
|
|
16
|
+
// lesson 4. H-1 (2026-09-08).
|
|
13
17
|
|
|
14
18
|
import { readFile } from 'node:fs/promises';
|
|
15
19
|
import { resolve } from 'node:path';
|
|
@@ -23,7 +27,11 @@ export default async function runProbe() {
|
|
|
23
27
|
const manifestPath = resolve(FIXTURE_DIR, 'hetzner/servers/ci-throwaway.json');
|
|
24
28
|
const { renderCloudInit, BASELINE_BLOCKS } = await import(rendererPath);
|
|
25
29
|
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
26
|
-
|
|
30
|
+
// In-process render only; pass explicit stub public-key material so
|
|
31
|
+
// the renderer never shells to hcloud (mock path stays hermetic).
|
|
32
|
+
const rendered = await renderCloudInit(manifest, {
|
|
33
|
+
publicKeys: ['ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFakePubKeyForRenderLintHardeningH1 rcf-lite-ci-mock'],
|
|
34
|
+
});
|
|
27
35
|
const missing = [];
|
|
28
36
|
const present = [];
|
|
29
37
|
for (const block of BASELINE_BLOCKS) {
|
|
@@ -58,7 +66,6 @@ export default async function runProbe() {
|
|
|
58
66
|
renderedByteLength: rendered.length,
|
|
59
67
|
presentBlocks: present,
|
|
60
68
|
missingBlocks: missing.map((m) => m.id),
|
|
61
|
-
mutationActive: process.env.SIMULATE_HARDENING_DRIFT === 'true',
|
|
62
69
|
},
|
|
63
70
|
};
|
|
64
71
|
}
|
|
@@ -1,11 +1,26 @@
|
|
|
1
|
-
// Probe: hcloud dry-run mock.
|
|
1
|
+
// Probe: hcloud dry-run mock (v1.0.1 mutation-purified per H-1).
|
|
2
2
|
//
|
|
3
3
|
// anchorAcId: AC-37101-1 (provisioner facade sole reader + provisionerReady
|
|
4
|
-
// on boot; also covers AC-37109-1 event-secrecy across the lifecycle
|
|
4
|
+
// on boot; also covers AC-37109-1 event-secrecy across the lifecycle;
|
|
5
|
+
// also covers AC-14501-1 mock-consumes-rendered-file assertions per H-1
|
|
6
|
+
// hardening block REQ-145).
|
|
5
7
|
// accountBound: false.
|
|
6
8
|
//
|
|
7
9
|
// Drives the provisioner facade with the fixture's ci-throwaway manifest
|
|
8
|
-
// against the mocked hcloud shim (src/hcloud-mock.mjs)
|
|
10
|
+
// against the mocked hcloud shim (src/hcloud-mock.mjs) and consumes the
|
|
11
|
+
// SAME rendered cloud-init user-data file the real path consumes (the
|
|
12
|
+
// fixture renderer writes it under hetzner/servers/rendered/ before the
|
|
13
|
+
// facade is exercised, and this probe reads it back and asserts an
|
|
14
|
+
// ssh-ed25519/ssh-rsa public-key line under the deploy user plus a
|
|
15
|
+
// NOPASSWD directive naming that user). The rendered-file assertion is
|
|
16
|
+
// what stops a mocked probe from passing while the real path fails on
|
|
17
|
+
// the same artefact (H-1 REQ-145 / AC-14501-1).
|
|
18
|
+
//
|
|
19
|
+
// The probe body reads NO process.env.SIMULATE_ switch. Fixture-side
|
|
20
|
+
// mutations live in src/cloud-init-renderer.mjs, src/hcloud-mock.mjs
|
|
21
|
+
// and src/provisioner-facade.mjs and alter INPUT only.
|
|
22
|
+
//
|
|
23
|
+
// Assertions:
|
|
9
24
|
// - the facade opens on ready() and fires provisionerReady with a
|
|
10
25
|
// metadata-only payload {tool, apiHost}.
|
|
11
26
|
// - createServer parses the mocked JSON stdout and emits
|
|
@@ -15,17 +30,14 @@
|
|
|
15
30
|
// - destroyServer fires hetznerServerDestroyed with the id.
|
|
16
31
|
// - no event body across the run carries the token, an ssh private-key
|
|
17
32
|
// marker, or the rendered user-data bytes (event-secrecy check).
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// SIMULATE_EVENT_SECRECY_LEAK=true has provisioner-facade.mjs inject
|
|
23
|
-
// the token into the hetznerServerProvisioned payload; the event-
|
|
24
|
-
// secrecy scan FAILS with a defensive-fake finding.
|
|
33
|
+
// - the rendered cloud-init file exists under the fixture's rendered
|
|
34
|
+
// directory and carries at least one ssh-ed25519 or ssh-rsa
|
|
35
|
+
// authorized-keys line under the deploy user plus a NOPASSWD
|
|
36
|
+
// directive naming that user (H-1 REQ-145 / AC-14501-1).
|
|
25
37
|
|
|
26
|
-
import { readFile } from 'node:fs/promises';
|
|
27
|
-
import { resolve } from 'node:path';
|
|
28
|
-
import { FIXTURE_DIR } from './probe-utils.mjs';
|
|
38
|
+
import { readFile, access } from 'node:fs/promises';
|
|
39
|
+
import { relative, resolve } from 'node:path';
|
|
40
|
+
import { FIXTURE_DIR, PROJECT_ROOT } from './probe-utils.mjs';
|
|
29
41
|
|
|
30
42
|
export const anchorAcId = 'AC-37101-1';
|
|
31
43
|
export const accountBound = false;
|
|
@@ -36,9 +48,47 @@ const PRIVATE_KEY_MARKER = 'BEGIN OPENSSH PRIVATE KEY';
|
|
|
36
48
|
|
|
37
49
|
export default async function runProbe() {
|
|
38
50
|
const facadePath = resolve(FIXTURE_DIR, 'src/provisioner-facade.mjs');
|
|
51
|
+
const rendererPath = resolve(FIXTURE_DIR, 'src/cloud-init-renderer.mjs');
|
|
39
52
|
const manifestPath = resolve(FIXTURE_DIR, 'hetzner/servers/ci-throwaway.json');
|
|
40
53
|
const { createProvisionerFacade, createEventLog } = await import(facadePath);
|
|
54
|
+
const { renderCloudInitToFile } = await import(rendererPath);
|
|
41
55
|
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
56
|
+
const results = [];
|
|
57
|
+
|
|
58
|
+
// Render the cloud-init to disk with a fixture-side stubbed public
|
|
59
|
+
// key (the mock path never calls hcloud ssh-key describe); the real
|
|
60
|
+
// path resolves the material via hcloud and writes to the same
|
|
61
|
+
// location. Both consume the same file shape.
|
|
62
|
+
const publicKeys = ['ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFakePubKeyForMockedRunHardeningH1 rcf-lite-ci-mock'];
|
|
63
|
+
const { renderedPath, rendered } = await renderCloudInitToFile(manifest, { publicKeys });
|
|
64
|
+
|
|
65
|
+
// H-1 REQ-145 / AC-14501-1: assert the rendered file carries an
|
|
66
|
+
// ssh-ed25519 or ssh-rsa line for the deploy user AND a NOPASSWD
|
|
67
|
+
// directive naming that user. This assertion is what breaks the
|
|
68
|
+
// "mock passes while real path fails on the same artefact" class.
|
|
69
|
+
const renderedFromDisk = await readFile(renderedPath, 'utf8');
|
|
70
|
+
const sshKeyLine = /(ssh-ed25519|ssh-rsa)\s+/i.test(renderedFromDisk);
|
|
71
|
+
const nopasswdLine = /deploy\s+ALL\s*=\s*\(ALL\)\s+NOPASSWD/i.test(renderedFromDisk);
|
|
72
|
+
if (!sshKeyLine) {
|
|
73
|
+
results.push({
|
|
74
|
+
anchorAcId: 'AC-14501-1',
|
|
75
|
+
verdict: 'fail',
|
|
76
|
+
detail: `rendered cloud-init at ${relative(PROJECT_ROOT, renderedPath)} carries no ssh-ed25519 or ssh-rsa authorized-keys line for the deploy user; the real path would provision a server with an unusable authorized_keys entry.`,
|
|
77
|
+
});
|
|
78
|
+
} else if (!nopasswdLine) {
|
|
79
|
+
results.push({
|
|
80
|
+
anchorAcId: 'AC-14501-1',
|
|
81
|
+
verdict: 'fail',
|
|
82
|
+
detail: `rendered cloud-init at ${relative(PROJECT_ROOT, renderedPath)} carries no NOPASSWD directive for the deploy user; the six sudoed baseline checks would block on a tty prompt.`,
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
results.push({
|
|
86
|
+
anchorAcId: 'AC-14501-1',
|
|
87
|
+
verdict: 'pass',
|
|
88
|
+
detail: `rendered cloud-init at ${relative(PROJECT_ROOT, renderedPath)} carries an ssh public-key line for the deploy user (byteLength ${rendered.length}) and a NOPASSWD sudoers directive for that user; the mock and the real path consume the same artefact.`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
42
92
|
const { events, eventSink } = createEventLog();
|
|
43
93
|
const facade = createProvisionerFacade({
|
|
44
94
|
token: TOKEN,
|
|
@@ -46,7 +96,6 @@ export default async function runProbe() {
|
|
|
46
96
|
apiHost: 'https://api.hetzner.cloud/v1',
|
|
47
97
|
eventSink,
|
|
48
98
|
});
|
|
49
|
-
const results = [];
|
|
50
99
|
try {
|
|
51
100
|
await facade.ready();
|
|
52
101
|
const server = await facade.createServer(manifest);
|
|
@@ -86,7 +135,7 @@ export default async function runProbe() {
|
|
|
86
135
|
results.push({
|
|
87
136
|
anchorAcId: 'AC-37109-1',
|
|
88
137
|
verdict: 'fail',
|
|
89
|
-
detail: `event-secrecy leak: ${leaks.map((l) => `${l.event} carries ${l.marker}`).join('; ')} (defensive-fake finding; check for a SIMULATE_EVENT_SECRECY_LEAK mutation).`,
|
|
138
|
+
detail: `event-secrecy leak: ${leaks.map((l) => `${l.event} carries ${l.marker}`).join('; ')} (defensive-fake finding; check for a SIMULATE_EVENT_SECRECY_LEAK mutation in the fixture-side facade shim).`,
|
|
90
139
|
});
|
|
91
140
|
} else {
|
|
92
141
|
results.push({
|
|
@@ -99,7 +148,7 @@ export default async function runProbe() {
|
|
|
99
148
|
results.push({
|
|
100
149
|
anchorAcId: 'AC-37101-1',
|
|
101
150
|
verdict: 'fail',
|
|
102
|
-
detail: `provisioner facade lifecycle threw: ${err.message} (probable cause: mocked hcloud stdout is not JSON; check for a SIMULATE_JSON_PARSE_STRIP mutation).`,
|
|
151
|
+
detail: `provisioner facade lifecycle threw: ${err.message} (probable cause: mocked hcloud stdout is not JSON; check for a SIMULATE_JSON_PARSE_STRIP mutation in the fixture-side mock shim).`,
|
|
103
152
|
});
|
|
104
153
|
}
|
|
105
154
|
return {
|
|
@@ -107,10 +156,9 @@ export default async function runProbe() {
|
|
|
107
156
|
extra: {
|
|
108
157
|
eventCount: events.length,
|
|
109
158
|
events: events.map((e) => ({ event: e.event, keys: Object.keys(e).sort() })),
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
},
|
|
159
|
+
renderedPath: relative(PROJECT_ROOT, renderedPath),
|
|
160
|
+
renderedByteLength: rendered.length,
|
|
161
|
+
renderedAssertions: { sshKeyLine, nopasswdLine },
|
|
114
162
|
},
|
|
115
163
|
};
|
|
116
164
|
}
|
|
@@ -9,9 +9,14 @@
|
|
|
9
9
|
// validator ships with the probe so the shelf gains no runtime dependency
|
|
10
10
|
// on ajv or similar. See validate() at the bottom.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// hetzner-
|
|
12
|
+
// Purity: the probe reads only the manifest dir and the shipped schema;
|
|
13
|
+
// no process.env.SIMULATE_ switch is read here. The fixture-side shim
|
|
14
|
+
// packages/rcf-lite/test/fixtures/hetzner-throwaway-server/
|
|
15
|
+
// run-manifest-schema-validate.mjs is the sole reader of
|
|
16
|
+
// SIMULATE_MANIFEST_INVALID and it mutates INPUT (a temp manifest dir
|
|
17
|
+
// pointed at via RCF_FIXTURE_MANIFEST_DIR) only; the probe then FAILS
|
|
18
|
+
// naming the offending field per hetzner-round-7-spec-2026-09-07.md
|
|
19
|
+
// section 3.4 lesson 4. H-1 (2026-09-08).
|
|
15
20
|
|
|
16
21
|
import { readFile } from 'node:fs/promises';
|
|
17
22
|
import { readManifestFiles, SCHEMA_PATH } from './probe-utils.mjs';
|
|
@@ -32,7 +37,6 @@ export default async function runProbe() {
|
|
|
32
37
|
};
|
|
33
38
|
}
|
|
34
39
|
const results = [];
|
|
35
|
-
const mutationActive = process.env.SIMULATE_MANIFEST_INVALID === 'true';
|
|
36
40
|
for (const f of files) {
|
|
37
41
|
let doc;
|
|
38
42
|
try {
|
|
@@ -45,7 +49,6 @@ export default async function runProbe() {
|
|
|
45
49
|
});
|
|
46
50
|
continue;
|
|
47
51
|
}
|
|
48
|
-
if (mutationActive) doc = { ...doc, location: 'mars1' };
|
|
49
52
|
const errors = validate(schema, doc, `#/${f.name}`);
|
|
50
53
|
if (errors.length > 0) {
|
|
51
54
|
for (const e of errors) {
|
|
@@ -69,7 +72,6 @@ export default async function runProbe() {
|
|
|
69
72
|
extra: {
|
|
70
73
|
manifestCount: files.length,
|
|
71
74
|
manifestNames: files.map((f) => f.name),
|
|
72
|
-
mutationActive,
|
|
73
75
|
},
|
|
74
76
|
};
|
|
75
77
|
}
|
|
@@ -21,7 +21,13 @@ export const FIXTURE_DIR = resolve(
|
|
|
21
21
|
PROJECT_ROOT,
|
|
22
22
|
'packages/rcf-lite/test/fixtures/hetzner-throwaway-server',
|
|
23
23
|
);
|
|
24
|
-
|
|
24
|
+
// Config override, not a mutation switch: the fixture-side shim for
|
|
25
|
+
// manifest-schema-validate may write a mutated copy to a scratch dir
|
|
26
|
+
// and point MANIFEST_DIR at it via RCF_FIXTURE_MANIFEST_DIR so the
|
|
27
|
+
// probe stays SIMULATE-free (H-1 mutation-purity gate row, 2026-09-08).
|
|
28
|
+
export const MANIFEST_DIR = process.env.RCF_FIXTURE_MANIFEST_DIR
|
|
29
|
+
? resolve(process.env.RCF_FIXTURE_MANIFEST_DIR)
|
|
30
|
+
: resolve(FIXTURE_DIR, 'hetzner/servers');
|
|
25
31
|
export const SCHEMA_PATH = resolve(HERE, '..', 'schemas', 'hetzner-server.schema.json');
|
|
26
32
|
export const REPORT_DIR = resolve(
|
|
27
33
|
PROJECT_ROOT,
|
package/blueprints/deploy-hetzner-server/contributions/probes/real-account-cloud-init-hardened.mjs
CHANGED
|
@@ -45,11 +45,12 @@ export default async function runProbe() {
|
|
|
45
45
|
})),
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
+
const blocks = report.checks.filter((c) => c.verdict === 'pass').map((c) => c.id);
|
|
48
49
|
return {
|
|
49
50
|
results: [{
|
|
50
51
|
anchorAcId,
|
|
51
52
|
verdict: 'pass',
|
|
52
|
-
detail: `cloud-init
|
|
53
|
+
detail: `cloud-init reached terminal state on server ${provisioned.id} (cloud-init status --wait exit ${report.cloudInit ? report.cloudInit.code : 'skipped'}); six baseline hardening blocks observed: ${blocks.join(', ')}.`,
|
|
53
54
|
}],
|
|
54
55
|
extra: report,
|
|
55
56
|
};
|
|
@@ -21,6 +21,18 @@ users:
|
|
|
21
21
|
# Block 2: root disabled (PermitRootLogin no).
|
|
22
22
|
# Block 1 continued: PasswordAuthentication no.
|
|
23
23
|
write_files:
|
|
24
|
+
# Block 1 continued: NOPASSWD sudo for the deploy user (mode 0440,
|
|
25
|
+
# visudo-shaped fragment). Baseline hardening requires the deploy
|
|
26
|
+
# user to run sudoed baseline checks without a tty; the write_files
|
|
27
|
+
# entry ships the fragment in the same posture as the sshd config.
|
|
28
|
+
- path: /etc/sudoers.d/90-deploy-nopasswd
|
|
29
|
+
permissions: '0440'
|
|
30
|
+
content: |
|
|
31
|
+
# deploy-hetzner-server v1.0.1 hardening: passwordless sudo for
|
|
32
|
+
# the deploy user so the ssh baseline checks (cloud-init status
|
|
33
|
+
# --wait, ufw status, iptables -L, systemctl is-active fail2ban)
|
|
34
|
+
# never block on a tty prompt.
|
|
35
|
+
deploy ALL=(ALL) NOPASSWD:ALL
|
|
24
36
|
- path: /etc/ssh/sshd_config.d/hardening.conf
|
|
25
37
|
permissions: '0644'
|
|
26
38
|
content: |
|
|
@@ -70,13 +82,17 @@ write_files:
|
|
|
70
82
|
APT::Periodic::Update-Package-Lists "1";
|
|
71
83
|
APT::Periodic::Unattended-Upgrade "1";
|
|
72
84
|
|
|
73
|
-
# Package install: ufw for block 3, fail2ban for block 5
|
|
74
|
-
# unattended-upgrades
|
|
85
|
+
# Package install: ufw for block 3, fail2ban for block 5.
|
|
86
|
+
# unattended-upgrades ships in the ubuntu-24.04 base image (v1.0.1 fix:
|
|
87
|
+
# was in packages: v1.0.0 which caused a spurious NoNewestVersion line).
|
|
88
|
+
# iptables-persistent conflicts with ufw on ubuntu-24.04 noble (ufw
|
|
89
|
+
# Breaks: iptables-persistent), so we DO NOT install it; the DOCKER-USER
|
|
90
|
+
# chain is loaded in runcmd via iptables directly (block 4 stays valid
|
|
91
|
+
# for the running boot; reboot-persistence for the chain is a follow-up
|
|
92
|
+
# item outside the H-1 hardening scope).
|
|
75
93
|
packages:
|
|
76
94
|
- ufw
|
|
77
95
|
- fail2ban
|
|
78
|
-
- unattended-upgrades
|
|
79
|
-
- iptables-persistent
|
|
80
96
|
|
|
81
97
|
runcmd:
|
|
82
98
|
# Block 3: UFW default-deny incoming, allow 22 80 443.
|
|
@@ -89,5 +105,9 @@ runcmd:
|
|
|
89
105
|
- systemctl reload ssh || systemctl restart ssh
|
|
90
106
|
- systemctl enable --now fail2ban
|
|
91
107
|
- systemctl enable --now unattended-upgrades
|
|
92
|
-
#
|
|
93
|
-
|
|
108
|
+
# Block 4 runtime: create the DOCKER-USER chain directly via iptables
|
|
109
|
+
# (v1.0.1 fix: iptables-persistent conflicts with ufw on Ubuntu 24.04
|
|
110
|
+
# noble; iptables itself is preinstalled, so the chain lands cleanly).
|
|
111
|
+
- iptables -N DOCKER-USER 2>/dev/null || true
|
|
112
|
+
- iptables -A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
|
|
113
|
+
- iptables -A DOCKER-USER -j DROP
|
|
@@ -25,7 +25,7 @@ to `deploy-hetzner-server` v1.0.0.
|
|
|
25
25
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
26
26
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
27
27
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
28
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
28
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
29
29
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
30
30
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
31
31
|
| 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.
|
|
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` |
|
|
@@ -49,7 +49,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
|
|
|
49
49
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
50
50
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
51
51
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
52
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
52
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
53
53
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
54
54
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
55
55
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# edge-cloudflare-tunnel CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.0.1 - 2026-09-08
|
|
4
|
+
|
|
5
|
+
H-2 hardening train (`h2-cf-platform-probe-integrity`): probe-integrity patch. No capability change.
|
|
6
|
+
|
|
7
|
+
- Replaced the warn-on-env-set stub in `real-account-connector-healthy.mjs` with a real cloudflared connector-health driver via the new cf-edge shim (`packages/rcf-lite/test/fixtures/cf-edge/h2-cf-tunnel-shim.mjs`). With both `CI_HAS_CLOUDFLARE_ACCOUNT` and `CI_HAS_HETZNER_ACCOUNT` set, the driver provisions the throwaway server through the hetzner fixture surface (H-1 territory, consumed as-is), runs `cloudflared tunnel info <name>` via the shim, asserts healthy connector count, captures the `tunnelConnectorUp` event, and always tears the server down. Env-absent branch keeps pass-with-skip. Env-present-with-fixture-failure yields FAIL with a pointer to the fixture step, never warn.
|
|
8
|
+
- Replaced both warn-on-env-set stubs in `real-account-tunnel-hostname-routes.mjs` with real drivers. Public-hostname sub-case: undici fetch against the scratch subdomain via the shim's `fetchTunnelHostname` seam, activated on `CI_HAS_CLOUDFLARE_ACCOUNT`. Access-gated sub-case: two-identity HS256 JWT check, activated only when `CI_HAS_CLOUDFLARE_ACCESS` is ALSO set (amendment 4). Env-absent branch keeps pass-with-skip. A pass is no longer reachable from `CI_HAS_CLOUDFLARE_ACCOUNT` presence alone.
|
|
9
|
+
- Converted the credentials-placeholder-read-failed branch in `manifest-schema-validate.mjs::eventSecrecyScan` from warn to fail with a pointer to the exact fixture path (`packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/*/credentials/probe.json.example`) and the setup requirement (must exist and parse as JSON).
|
|
10
|
+
- Added the cf-edge tunnel shim (`packages/rcf-lite/test/fixtures/cf-edge/h2-cf-tunnel-shim.mjs`) with five seams: `provisionScratchServer`, `destroyScratchServer`, `cloudflaredTunnelInfo`, `fetchTunnelHostname`, `mintScratchIdentity`. The shim reads `H2_CF_TUNNEL_SHIM_MODE` and `H2_CF_TUNNEL_HOSTNAME_SHIM_MODE` for synthetic-mode branches; the probe bodies hold zero `SIMULATE_` token references.
|
|
11
|
+
- Tunnel symbolic-anchor ruling (task C4): the `AC-tunnel-*` symbolic ids on the five tunnel probes ARE the shipped AC ids in the `US-39101..US-39108` contribution band, so no re-anchor was performed. Full mapping in the H-2 PR body.
|
|
12
|
+
|
|
3
13
|
## 1.0.0 - 2026-09-08
|
|
4
14
|
|
|
5
15
|
Initial release. Round-7 T-3 of the Hetzner spec at `projects/blueprint-library/specs/hetzner-round-7-spec-2026-09-07.md`.
|