| name | hulumi-for-operations | ||||
|---|---|---|---|---|---|
| created | 2026-05-01 | ||||
| status | proposed | ||||
| tla_required | false | ||||
| security_libs_required | false | ||||
| ai_component | false | ||||
| compliance |
|
Decision record for opening a time-based hardening surface in Hulumi. Authored 2026-05-01 in response to the
hulumi-for-operationsidea doc, the two real-world consumer guides (sunlit-guardian's AWS-SETUP-GUIDE and MAINTENANCE-GUIDE) that surfaced the gap, and the two open issues (#47 AuditTrail and #49 DetectiveServicesEnable) that this runbook closes.Status: proposed. Every "Decision" line below is a commitment-point that survives PR review or gets revised. Open questions live in § Open questions.
/slo-architectwas inlined into this design doc — Hulumi-for-Operations is a feature addition to an already-designed workspace, not a new product. Mirrors thehulumi-k8s-surface.mdprecedent.
Hulumi v1.1 ships hardened defaults at the moment infrastructure is created. SecureBucket, AccountFoundation, MonitoringFoundation, IdentityAlarms, SecureRepository, OrgFoundation, the seven @hulumi/k8s-baseline components — every single one is a create-time control. "Is the EC2 instance you spun up four months ago patched today?" is not a create-time question. It is a time-based control, and Hulumi has no answer for it.
A consumer who lands on Hulumi v1.1 with a simple AWS account, a small EC2 fleet, and an ECR repository now has to re-derive five separate patterns by hand:
- SSM Patch Manager: Patch Baseline + Maintenance Window + Patch Group tag selection + service role + compliance reporting wired to CloudWatch (the §7 of
MAINTENANCE-GUIDE.mdgap) - Inspector v2 enablement + EventBridge rule routing CRITICAL findings to an SNS topic (the §3.7 of
MAINTENANCE-GUIDE.mdgap; partial scope of #49) - The four "always-on" detective services as a bundle: GuardDuty + IAM Access Analyzer + Cost Anomaly Detection + Inspector v2 (#49 verbatim)
- CloudTrail multi-region trail with CW Logs delivery + S3 bucket-policy ordering correctly + lifecycle (#47 verbatim)
- CrossGuard policies that enforce the time-based controls at create time (mirroring the existing
HulumiHardeningPackH1–H4 pattern)
These are not sunlit-specific patterns. Anyone running EC2 + ECR on AWS, in any account that produces audit-relevant signal, hits all five. The §4.8 of MAINTENANCE-GUIDE.md instruction "if any CRITICAL, rebuild with updated base image" is the smoking gun: a maintenance guide in 2026 telling a single developer to run a query manually because there is no notification path.
This doc is not a runbook. The runbook is the v1.x milestone breakdown that lives at docs/slo/completed/RUNBOOK-hulumi-operations.md once these decisions are accepted. This doc commits to the shape; the runbook commits to the sequencing.
The line we draw, mirroring the hulumi-k8s Rule 0 boundary contract:
Hulumi codifies time-based defaults as IaC. The consumer's findings triage and runtime orchestration are theirs.
In scope. Declarable AWS-managed-service configuration that runs on a schedule and produces signal a consumer-supplied SNS topic can subscribe to:
- SSM Patch Manager (Patch Baseline + Maintenance Window + Maintenance Window Target + Maintenance Window Task + Resource Data Sync + Service Role)
- AWS Inspector v2 enablement (account-level subscription, EC2 + ECR + Lambda scan resource types)
- GuardDuty enablement (with publishing frequency)
- IAM Access Analyzer (account-level analyzer)
- AWS Cost Anomaly Detection (one detector + one or more subscriptions)
- CloudTrail multi-region trail with log-file validation, CW Logs delivery, S3 lifecycle, KMS encryption
- EventBridge rules + targets that route findings to existing
MonitoringFoundationSNS topics — never net-new SNS topics - CrossGuard policy rules that enforce all of the above at preview-time
/hulumi-threat-modelscenarios for the operations failure modes (patch-compliance lapse, detective-service-disabled, audit-pipeline-broken)
Out of scope and explicitly non-goals. Anything that requires Hulumi to author or maintain runtime code:
- Authoring custom Lambda code that ships in Hulumi tarballs (sets a precedent the project has not adopted; see Approach B in the idea doc)
- Authoring CVE triage logic (which findings to ignore, which to escalate)
- Authoring rebuild-orchestration code (
repository_dispatchto consumer CI from a Hulumi-shipped Lambda) - Anything that requires reading the consumer's repo or runtime state to function
- Operating-system-specific patch content authoring (we configure SSM Patch Manager; we do not ship custom patch baselines beyond AWS-managed-baseline references)
- Net-new SNS topics, alerting tools, or paging integrations (consumers pass
MonitoringFoundationoutputs in) - VPN-and-CI-access defaults (
AwsClientVpnFederated,EksGithubActionsAccessBundle) — different theme, separate runbook
Decision. Land the Operations surface inside the existing @hulumi/baseline package, under @hulumi/baseline.aws.* — alongside SecureBucket, AccountFoundation, MonitoringFoundation, IdentityAlarms. No new npm package.
Why.
- Peer-deps are identical to the existing AWS surface:
@pulumi/awsonly. The k8s-baseline precedent for a separate package was driven by@pulumi/kubernetesadding ~50 MB unpacked + a different upstream cadence. Operations adds neither — Patch Manager, Inspector, GuardDuty, CloudTrail are all in@pulumi/awsalready. - Threat-model trust boundaries are the same as
AccountFoundation's (Pulumi program → AWS account-level service). No new boundary set. - Consumers who install
@hulumi/baselineforAccountFoundationget the Operations components for free, no extrapnpm addstep. This raises the floor of "what an account looks like when Hulumi-hardened" without taxing consumers. - Tier ladder is already in
@hulumi/baseline(Sandbox|StartupHardened). Reuse, don't fork.
Consequence. @hulumi/baseline v1.2.0 (the release this runbook ships) gains five new exported names. v1.x semver: minor bump because additions are non-breaking. The existing surface is unchanged.
Decision. Build a ComponentResource Ec2PatchBaseline that wraps aws.ssm.PatchBaseline + aws.ssm.PatchGroup + aws.ssm.MaintenanceWindow + aws.ssm.MaintenanceWindowTarget + aws.ssm.MaintenanceWindowTask + aws.ssm.ResourceDataSync + the IAM service role. Tag-based target selection is the only supported targeting mode in v1.
API shape (proposed).
new baseline.aws.Ec2PatchBaseline("prod-linux-patches", {
tier: Tier.StartupHardened, // Sandbox | StartupHardened — same enum as the rest of @hulumi/baseline
patchGroupTagValue: "production", // enum: "dev" | "staging" | "production" — REVISED 2026-05-01 per Flaw 2; refused otherwise
operatingSystem: "AMAZON_LINUX_2023", // also: UBUNTU, WINDOWS, AMAZON_LINUX_2, REDHAT_ENTERPRISE_LINUX
approvalRules: {
approveAfterDays: 7, // PCI-DSS-aligned (Req 6.3.3 — within 1 month, 7 days = sane buffer)
severities: ["Critical", "Important", "Medium"], // tier-aware default
},
maintenanceWindow: {
schedule: "cron(0 02 ? * WED *)", // explicit — no Hulumi default for StartupHardened
durationHours: 2,
cutoffHours: 1,
rebootOption: "RebootIfNeeded", // see § Decision: RebootOption default
timezone: "Etc/UTC",
},
staggering: {
bucketCount: 4, // splits target by tag-hash modulo 4 — see § Decision: synchronized-reboot
bucketWindowOffsetMinutes: 15,
},
complianceMetric: {
snsTopicArn: monitoring.high.arn, // routes "patch compliance failed" through MonitoringFoundation
severityThreshold: "Critical",
},
});Outputs. patchBaselineId, patchBaselineArn, maintenanceWindowId, serviceRoleArn, complianceMetricFilterName, complianceAlarmArn. Stable names (stable interface level — see § Public interfaces).
Why a single Ec2PatchBaseline and not split (PatchBaseline, MaintenanceWindow, MaintenanceWindowTask as separate components). Splitting would mirror aws.ssm.* 1:1 but the value Hulumi adds is bundling the six resources with the right inter-dependencies (target selector → window task → patch baseline → service role) so that pulumi destroy cleans up cleanly. Five of every six adopters re-derive the same six-resource glue — that is exactly the hand-rolled-boilerplate cost the § Why this doc exists section above is designed to delete.
Decision. Build a sibling ComponentResource Ec2PatchWaves that composes three Ec2PatchBaselines — one per environment wave — with sequenced MaintenanceWindow schedules and a CloudWatch composite-alarm health gate between waves. Ships in the same milestone (M1) as Ec2PatchBaseline because they share ~90% of the implementation.
Why. The original "production-only Patch Group" framing was wrong: dev and staging drift from production fast, regress patch coverage, and provide no canary signal before production patches roll. The wave model — dev → staging → production with health gates between — is how every team running >1 environment actually does this safely. Hulumi's tier ladder maps onto wave count (Sandbox: dev only; StartupHardened: all three).
API shape (proposed).
new baseline.aws.Ec2PatchWaves("fleet-patches", {
tier: Tier.StartupHardened,
// Each wave is a full Ec2PatchBaseline arg minus tier (inherited) and patchGroupTagValue (set by the wave key)
waves: {
dev: {
operatingSystem: "AMAZON_LINUX_2023",
approvalRules: { approveAfterDays: 0, severities: ["Critical", "Important", "Medium"] },
maintenanceWindow: {
schedule: "cron(0 02 ? * SUN *)",
durationHours: 2,
cutoffHours: 1,
rebootOption: { kind: "RebootIfNeeded" },
},
staggering: { bucketCount: 1, bucketWindowOffsetMinutes: 0 },
},
staging: {
operatingSystem: "AMAZON_LINUX_2023",
approvalRules: { approveAfterDays: 0, severities: ["Critical", "Important", "Medium"] },
maintenanceWindow: {
schedule: "cron(0 02 ? * MON *)",
durationHours: 2,
cutoffHours: 1,
rebootOption: { kind: "RebootIfNeeded" },
},
staggering: { bucketCount: 2, bucketWindowOffsetMinutes: 30 },
},
production: {
operatingSystem: "AMAZON_LINUX_2023",
approvalRules: { approveAfterDays: 0, severities: ["Critical", "Important", "Medium"] },
maintenanceWindow: {
schedule: "cron(0 02 ? * TUE *)",
durationHours: 4,
cutoffHours: 2,
rebootOption: { kind: "RebootIfNeeded" },
},
staggering: { bucketCount: 4, bucketWindowOffsetMinutes: 15 },
},
},
complianceMetric: { snsTopicArn: monitoring.high.arn, severityThreshold: "Critical" }, // shared across all three waves
// Wave health gate inputs — composite alarm gates the next wave
waveHealthGate: {
appHealthAlarmArns: [albAlarm.arn, apdexAlarm.arn], // consumer-supplied; combined with prior wave's SSM-Compliance-Failed
onAlarmFireDisableNextWave: true, // default true — fail-loud
},
});Outputs. waves: { dev: Ec2PatchBaselineOutputs; staging?: ...; production?: ... }, compositeAlarmArns: pulumi.Output<{ devToStaging: string; stagingToProduction: string }>.
Tier ladder.
- Sandbox: degrades to single-wave (
devonly). Thewaves.staging/waves.productionkeys may be present but produce no resources. No composite alarm. Sandbox accounts often don't have three environments — refusing to construct would be hostile. - StartupHardened: all three waves required. Refusing construction with a clear error if any wave is missing.
Health gate mechanism — no Lambda. The gate is an aws.cloudwatch.CompositeAlarm whose OK state is wired via Pulumi Output<bool> chain into the next wave's MaintenanceWindow.enabled field. When the alarm fires (any of: prior-wave SSM-Compliance-Failed, consumer app-health alarm), the next wave's window is enabled: false until a human resets the alarm to OK and re-runs pulumi up. This is rollback-as-IaC, not rollback-as-runtime-code. Rule 0 (no Hulumi-shipped Lambdas) holds.
Generalization beyond sunlit. Every team running >1 environment hits this. The wave keys (dev, staging, production) are an industry-standard set; teams with extra environments (e.g., qa, pre-prod) compose multiple Ec2PatchWaves instances or extend in v1.3 if demand surfaces.
This is the breach-risk lever identified in the idea doc's Top risks. The "patch downloaded but kernel not active" trap is the silent-un-patching failure mode.
Decision. Both tiers default to RebootIfNeeded (the patch-applies-and-reboots path). The NoReboot option is available but requires the consumer to write it explicitly with a // HULUMI_DECISION: comment. Sandbox does not default to NoReboot.
Why. The failure mode of a sandbox tenant being surprised by a 2am reboot is visible and recoverable (single-developer sandbox: re-launch a container, re-establish a VPN session). The failure mode of a sandbox running on the same kernel for sixty days is invisible until exploited. Hulumi's hardened-by-default discipline says we choose the visible-failure default.
What we ship to make it tolerable. Sandbox tier defaults MaintenanceWindow.schedule to cron(0 04 ? * SUN *) (Sunday 04:00 UTC — the lowest-collision window across UK / EU / US time zones for solo developers). StartupHardened tier ships no schedule default — the consumer must set it explicitly. Fail-loud is the right discipline for production.
Documentation requirement. The Ec2PatchBaseline component reference doc must lead with a "Reboot policy and what it means for you" section that names the trade-off in plain English, citing this design decision.
The idea doc's prolonged-outage risk is "50 EC2s reboot at the same Maintenance Window cron firing → 45 min synchronized outage."
Decision. Ship a staggering: { bucketCount, bucketWindowOffsetMinutes } arg on Ec2PatchBaseline. The component creates bucketCount Maintenance Window Tasks, each with a target filter tag:Patch:Group = production AND hash-bucket = N (N in 0..bucketCount-1), each offset by bucketWindowOffsetMinutes from the prior. Hash-bucket is computed as crc32(instance-id) mod bucketCount, deterministic per-instance.
Sandbox default. bucketCount: 1 (single window — cost dominates over availability for sandbox).
StartupHardened default. No default. Consumer must set explicitly. Fail-loud — the synchronized-reboot incident pattern is too common.
Why CRC32 and not stable-hash via tag. Tag-based hashing requires an additional tag (Patch:HashBucket=N) which would have to be set at instance-creation time. CRC32 of the instance ID is computed by SSM at run-time without per-instance tagging. The trade-off: when a consumer adds an instance, it lands in a deterministic bucket — predictable.
Decision: DetectiveServicesEnable shape (M2 — closes #49)
Decision. Build a ComponentResource DetectiveServicesEnable per #49's proposal verbatim, with one revision: the component always emits an EventBridge rule routing GuardDuty findings + Inspector v2 findings + Cost Anomaly Detection alerts to an SNS topic ARN supplied by the consumer (typically a MonitoringFoundation output). #49's draft made the SNS wiring optional; we make it required at StartupHardened and optional with a default-no at Sandbox.
Why required at StartupHardened. A detective service that emits findings to a console nobody reads is operationally identical to "off." The hardened-by-default discipline says we make the routing the path of least resistance.
API shape (revised twice — first per #49, then 2026-05-01 per Flaw 1 research-resolution).
new baseline.aws.DetectiveServicesEnable("detective", {
tier: Tier.StartupHardened,
enableGuardDuty: true, // default true
enableAccessAnalyzer: true, // default true
enableCostAnomalyDetection: true, // default true
enableInspectorV2: true, // default true; covers EC2 + ECR + Lambda scan resource types
guardDutyPublishingFrequency: "FIFTEEN_MINUTES",
costAnomalyThresholdUsd: 20,
inspectorScanResourceTypes: ["EC2", "ECR", "LAMBDA"],
// Routing is dual at StartupHardened — see § Decision: dual-route default below.
// - findingsRoutingSnsArn / findingsSeverityFloor route the firehose (HIGH+CRITICAL by default)
// - findingsKevRoutingSnsArn routes the high-priority subset (KEV catalog membership)
findingsRoutingSnsArn: monitoring.med.arn, // medium-priority firehose at hardened
findingsSeverityFloor: "HIGH", // route HIGH+CRITICAL only
findingsKevRoutingSnsArn: monitoring.high.arn, // high-priority KEV-only route — NEW arg per Flaw 1
});Research finding (2026-05-01). Amazon Inspector v2 has surfaced CISA KEV catalog membership inline in finding payloads since 2023. Each Inspector v2 Inspector2 Finding event includes:
kev.dateAdded— the date CISA added the CVE to the Known Exploited Vulnerabilities catalog (key existence indicates KEV membership)kev.dateDue— CISA's federal-agency remediation deadlineepss.score— Exploit Prediction Scoring System score (0.0–1.0)exploitAvailable— boolean indicating whether public exploit code exists
This collapses the prior speculation about "EventBridge Pipes + Step Functions joining cisa.gov KEV JSON." No external fetch. No Step Functions. No Lambda. EventBridge rule patterns can match the KEV fields directly.
Decision: dual-route default. At tier: StartupHardened, DetectiveServicesEnable ships two EventBridge routes:
- Firehose route —
findingsRoutingSnsArn(typicallyMonitoringFoundation.med.arn) receives every finding at or abovefindingsSeverityFloor(defaultHIGH). - KEV-only route —
findingsKevRoutingSnsArn(typicallyMonitoringFoundation.high.arn) receives only findings with$.detail.findingDetails.kev.dateAddedpresent (i.e., the CVE is on CISA's actively-exploited list).
The EventBridge rule patterns are pure JSON — no runtime code. Sandbox tier defaults to KEV route only (cost-conscious, signal-rich).
Cost (confirmed 2026-05-01). Inspector v2 EC2 scanning is $1.258/instance/month continuous. ECR is $0.09/image first scan, $0.01/re-scan. 15-day free trial per new account. For a sub-10-instance fleet with ~50 ECR pushes/month, expect ~$5–15/month total — sub-line-item to a Client VPN. Documented in M5's detective-services-enable.md cookbook with a worked example.
Cost-zero alternative (cookbook only). Trivy-in-CI runs in the consumer's GitHub Actions, scans ECR images at build time with the same KEV catalog awareness Inspector v2 has, free. Pair with SSM Patch Compliance scanning (free side-effect of Ec2PatchBaseline) for EC2-side coverage. Hulumi ships no components for this path; M5 cookbook documents the YAML.
Outputs. guardDutyDetectorId, accessAnalyzerArn, costAnomalyDetectorArn, inspectorAccountStatus, findingsEventRuleArn. All stable.
Decision: AuditTrail shape (M3 — closes #47)
Decision. Build #47's proposal verbatim, with two revisions:
- The S3 bucket is created via
@hulumi/baseline.aws.SecureBucket(not rawaws.s3.BucketV2). This eliminates the bucket-policy-vs-trail-arn ordering gotcha mentioned in #47 —SecureBucketalready enforces TLS-only, public-access-block, SSE-KMS, versioning. The CloudTrail-write policy is added as a separateaws.s3.BucketPolicywith the trail ARN known after construction (Pulumi's standardOutput<>chaining handles the ordering). - CW Logs group is created with KMS encryption using the same KMS alias as
AccountFoundation(alias/<account>-logsif present, else a Hulumi-managed aliasalias/hulumi-audit-trail-<stack>). This avoids the "audit-trail logs are encrypted with AWS-managed KMS but everything else is customer-managed" smell.
API shape.
new baseline.aws.AuditTrail("audit", {
tier: Tier.StartupHardened,
name: "management-events",
cwLogsRetentionDays: 90, // tier-aware default; StartupHardened: 365
s3LifecycleArchiveDays: 90,
s3LifecycleExpireDays: 365,
captureS3DataEvents: false, // off by default (cost)
captureLambdaDataEvents: false, // off by default (cost)
kmsKeyAliasName: foundation.kmsLogsAlias.name, // optional; falls back to hulumi-managed alias
});Outputs. trailArn, logGroupName, logGroupArn, bucketArn, kmsKeyArn. All stable.
Decision. Add a new CrossGuard policy pack HulumiOperationsHardeningPack to @hulumi/policies, mirroring HulumiHardeningPack's tier-aware advisory | mandatory shape. Five rules, all prefix O_:
| Rule ID | Tier-aware level | What it catches |
|---|---|---|
O_PATCH_1 |
Sandbox: advisory; Hardened: mandatory | aws.ec2.Instance without a Patch:Group=* tag |
O_PATCH_2 |
both: mandatory | aws.ssm.PatchBaseline not associated to a MaintenanceWindow (orphan baseline) |
O_DETECT_1 |
Sandbox: advisory; Hardened: mandatory | account-level stack with EC2 / ECR resources but no DetectiveServicesEnable |
O_AUDIT_1 |
Sandbox: advisory; Hardened: mandatory | account-level stack without an AuditTrail (or documented Suppression) |
O_INSPECTOR_1 |
both: mandatory | aws.ecr.Repository with imageScanningConfiguration.scanOnPush != true |
Suppression API (existing) accepts these rule IDs. Tier monotonicity meta-test (existing pattern, H4-shape) extends to verify Sandbox emits ≤ controls than StartupHardened across this pack — a regression in tier ladder is a CI failure.
Compliance mappings (IDs only, per Hulumi's licence-boundary discipline).
| Rule | CIS AWS FB v5 | NIST 800-53 r5 | PCI-DSS v4.0.1 |
|---|---|---|---|
O_PATCH_1 |
(no v5 patch ID — see § Open question 4) | SI-2(2) | 6.3.3 |
O_PATCH_2 |
(same) | SI-2(3) | 6.3.3 |
O_DETECT_1 |
4.16 | AU-12, SI-4(2) | 10.1, 11.5.1 |
O_AUDIT_1 |
3.x section | AU-2, AU-3 | 10.2.1 |
O_INSPECTOR_1 |
5.5 | RA-5(2) | 6.3.2 |
The mapping table follows docs/mappings/ house style — IDs only, framework URLs cited in the rule's metadata, no verbatim control text.
Decision. Add three new /hulumi-threat-model scenarios to the existing skill (no new skill, no new package — the skill ships scenarios as data files):
aws-patch-compliance-lapse— un-patched fleet attack-surfaceaws-detective-service-disabled— silent-tamper of GuardDuty / Inspector by a privileged insideraws-audit-pipeline-broken— CloudTrail → CW Logs delivery failure (silent un-logging)
Each follows the existing scenario JSON schema (skills/hulumi-threat-model/scenarios/*.json). The recommendedComponents[].availability strings are pinned to "Shipped in M<N>" per the #15 sweep convention.
┌──────────────────────────────────────────────────────────────────────────┐
│ User Environment (laptop or CI) │
│ │
│ Engineer ─→ Claude Code ─→ Pulumi program │
│ │
│ Imports @hulumi/baseline.aws.{Ec2PatchBaseline, DetectiveServicesEnable,│
│ AuditTrail, MonitoringFoundation, …} │
│ Imports @hulumi/policies.HulumiOperationsHardeningPack │
└─────────────────────────────────┬────────────────────────────────────────┘
│ trust boundary 1 — IaC role
│ tagged hulumi:iac-role=true
▼
┌──────────────────────────────────────────────────────────────────────────┐
│ AWS Account │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Account-level managed services configured by this surface │ │
│ │ │ │
│ │ SSM Patch Manager ───→ EC2 fleet (tag:Patch:Group=*) │ │
│ │ Inspector v2 ───→ EC2 + ECR + Lambda │ │
│ │ GuardDuty ───→ CloudTrail + VPC Flow + DNS │ │
│ │ IAM Access Analyzer │ │
│ │ Cost Anomaly Detection │ │
│ │ CloudTrail (multi-region, log-file validation) │ │
│ └─────────────────────────────────┬───────────────────────────────────┘ │
│ │ trust boundary 2 — service-to-service│
│ │ IAM service roles per service │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ EventBridge + CloudWatch Logs │ │
│ │ │ │
│ │ EventBridge rules (Inspector2 Finding, GuardDuty Finding, Cost- │ │
│ │ AnomalyDetection Anomaly, SSM-Compliance metric filter) │ │
│ │ CW Logs group (CloudTrail log delivery; KMS-encrypted) │ │
│ └─────────────────────────────────┬───────────────────────────────────┘ │
│ │ trust boundary 3 — pre-existing │
│ │ MonitoringFoundation SNS topics │
│ │ (M5/#46) consumer-supplied ARN │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ MonitoringFoundation severity-tiered SNS topics (existing, M5) │ │
│ │ high.arn ──→ consumer's email / PagerDuty / Slack subscription │ │
│ │ med.arn │ │
│ │ low.arn │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
Solid lines = exists at HEAD (M5 already shipped MonitoringFoundation).
Dashed lines = added by this runbook.
Downstream milestones cannot rename or reshape these without explicit migration. Stability ladder mirrors hulumi-k8s-surface.md.
| Surface | Stability | Notes |
|---|---|---|
@hulumi/baseline.aws.Ec2PatchBaseline constructor + Args shape |
stable |
M1 — frozen at v1.2.0. Args additions are non-breaking; existing fields are frozen. |
@hulumi/baseline.aws.DetectiveServicesEnable constructor + Args |
stable |
M2. Resolves #49. |
@hulumi/baseline.aws.AuditTrail constructor + Args |
stable |
M3. Resolves #47. |
@hulumi/policies.HulumiOperationsHardeningPack rule IDs (O_*) |
stable |
M4. Renaming a rule ID is a major-version bump. |
Tag keys: Patch:Group, hulumi:patch-bucket, hulumi:operations |
stable |
Cross-component contract. |
Ec2PatchBaseline.staggering.bucketCount semantics (CRC32 mod) |
evolving |
Reserves the right to swap hash function in v2 with a documented migration. |
| Internal: shape of the IAM service role for Maintenance Window Task | internal |
Fair game — consumers should not depend on policy ARNs being stable. |
@hulumi/baseline.aws.Ec2PatchBaselineconsumesMonitoringFoundationoutputs as plainOutput<string>SNS ARNs. No shared module state.@hulumi/policies.HulumiOperationsHardeningPackis a sibling rule pack toHulumiHardeningPack; they may both be loaded in the samepolicies/directory, and theSuppressionAPI accepts rule IDs from either pack indifferently.@hulumi/baseline.aws.AuditTrailmay consume anAccountFoundation's KMS alias as input. Optional. No required dependency between the two —AuditTrailships a Hulumi-managed KMS alias when no foundation is supplied.@hulumi/baseline.aws.DetectiveServicesEnabledoes not depend on any other@hulumi/baseline.aws.*component. It is the one new component that any consumer can drop into a 5-lineindex.tsregardless of what else they have.
No concurrent actors / distributed-state guarantees beyond Pulumi's standard apply ordering. The "synchronized-reboot mitigation" decision (CRC32-based staggering) is the only place ordering matters, and the ordering is a stateless function over instance-id. Mirrors the hulumi-k8s-surface.md decision.
These are the four /slo-research open-question buckets from the idea doc, reframed against the architecture decisions above. Answering them is not blocking for /slo-plan to produce M1's contract block — they refine M2–M5.
- (was Q1, Q3 from idea doc) SSM Patch Manager + PCI-DSS Req 6.3.3 — is
complianceMetrictime-to-deploy, time-to-scan, or compliance-state-failed the right primary signal? This shapes M1'scomplianceMetricoutput. - (was Q2 from idea doc) — RESOLVED 2026-05-01. AWS Inspector v2 surfaces KEV catalog membership + EPSS scores inline in finding payloads since 2023. The
Inspector2 Findingevent shape is stable. M2 ships dual-route via pure EventBridge JSON patterns; no external fetch or runtime code. - (was Q4 from idea doc) CIS AWS Foundations Benchmark v5.0.0 patch-management coverage — historically v8 has patch IDs that v5 doesn't. If v5 has no patch ID, the
O_PATCH_*mappings cite v8 and we surface the version skew indocs/mappings/docs honestly. - (was Q9 from idea doc) Cross-account Maintenance Window Targets — does
MaintenanceWindowTarget.Targets[].TargetAccountIdswork cleanly via the Pulumi provider? If yes, M1 surfaces atargetAccountsknob; if no, defer to v1.3.
- DHI catalog coverage for sunlit-shaped runtimes — does Docker Hardened Images carry
rust:1.88-equivalent, Node, and HashiCorp Vault images? Verification is a 30-mindocker pullexercise; if gaps exist, document the Chainguard fallback in the M5 hardened-base-images cookbook. Tracked indocs/slo/idea/hulumi-for-operations-v1-3.mdfor the v1.3 ECR pull-through-cache work. - Wave gate semantics under partial failure — when the dev-wave-to-staging-wave composite alarm fires mid-window (e.g., a single dev EC2 fails patch compliance but the rest succeed), should the gate disable the staging wave for the current week or permanently until reset? Decision: current week only; the
MaintenanceWindow.enabled: falseis a flop, not a latch. Document in M1 lessons file when implemented.
- Tier: The shared
@hulumi/baseline.Tierenum:Sandbox|StartupHardened. Reused unchanged. - MonitoringFoundation: Existing component shipped in M5 (#46). Provides severity-tiered SNS topics. This runbook depends on its outputs but does not modify it.
- IaC role: The IAM role with
hulumi:iac-role=truetag that Pulumi assumes. The Operations surface adds five new IAM permissions to this role's allowed action set; details in M1's Contract Block. - Patch Group: AWS SSM concept — a tag-keyed group of EC2 instances that share a Patch Baseline. The default tag key in this surface is
Patch:Group(AWS-conventional).