Skip to content

Commit aa4fe94

Browse files
committed
fix(provisioning): clear an SQS Queue attribute that resolves away on UPDATE (Fn::If -> AWS::NoValue) instead of leaving the stale value
The new conditions-update-2 integ fixture surfaced a real cdkd UPDATE-path bug: when an SQS Queue property whose value is Fn::If(cond, <value>, AWS::NoValue) flips from <value> (phase a, condition true) to AWS::NoValue (phase b, condition false), the resolved desired properties OMIT the property entirely. cdkd's diff layer correctly classifies that as a change (compareProperties unions current + desired keys, so a key present in state but absent from the resolved template is detected), but SQSQueueProvider.update() only acted on keys PRESENT in the new properties -- so the stale value (e.g. RedrivePolicy) was never cleared on AWS. The fixture's phase-b assertion (WorkQueue RedrivePolicy GONE) failed. This is the "providers only act on keys present in newProperties" gap (feedback_internal_contract_audit_first). The fix adds a removal branch to SQSQueueProvider.update(): a CDK-managed attribute present in previousProperties but absent from the resolved desired properties is reset to its default via SetQueueAttributes, mirroring CloudFormation's reset-to-default-on-removal behavior. The new SQS_ATTRIBUTE_REMOVAL_RESET map clears the JSON policy attributes (RedrivePolicy / RedriveAllowPolicy) and KmsMasterKeyId to the empty string SQS documents for removal, and resets the numeric attributes to their documented SetQueueAttributes defaults. The branch is gated on the attribute being present in previousProperties AND in the reset map, so it never spuriously clears an attribute that was never set (a tag-only update issues no SetQueueAttributes), and immutable / FIFO-discriminated attributes (FifoQueue / DeduplicationScope / FifoThroughputLimit) are deliberately excluded. The change is confined to the SQS provider -- no shared / cross-provider code path changed, so other providers' update semantics are untouched. Unit tests (tests/unit/provisioning/sqs-queue-provider-update.test.ts): clear-on-removal (RedrivePolicy -> ""), numeric-reset-on-removal (VisibilityTimeout -> 30), and the no-over-clear guard (attribute absent on both sides -> no SetQueueAttributes). Also adds the conditions-update-2 integ fixture that stresses the harder CloudFormation-Conditions-on-UPDATE semantics the simple #840 flip does not cover (moved-condition resources, condition-gated outputs, dangling DependsOn on a pruned resource, Ref into a pruned resource, and the Fn::If -> AWS::NoValue property-removal case above). NOTE: needs /run-integ conditions-update-2 against real AWS before merge.
1 parent 8108312 commit aa4fe94

18 files changed

Lines changed: 914 additions & 4 deletions

docs/_generated/integ-coverage.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3259,6 +3259,7 @@
32593259
"bench-cdk-sample",
32603260
"bench-sdk",
32613261
"composite-stack",
3262+
"conditions-update-2",
32623263
"data-pipeline",
32633264
"drift-revert-arrays",
32643265
"event-driven",
@@ -3286,6 +3287,9 @@
32863287
"l2",
32873288
"literal"
32883289
],
3290+
"conditions-update-2": [
3291+
"l1"
3292+
],
32893293
"data-pipeline": [
32903294
"l2"
32913295
],
@@ -3345,6 +3349,7 @@
33453349
"bench-sdk",
33463350
"composite-stack",
33473351
"conditions-and-if",
3352+
"conditions-update-2",
33483353
"context-test",
33493354
"cross-region-state-bucket",
33503355
"cross-stack-references",
@@ -3382,6 +3387,9 @@
33823387
"conditions-and-if": [
33833388
"l1"
33843389
],
3390+
"conditions-update-2": [
3391+
"l1"
3392+
],
33853393
"context-test": [
33863394
"l2"
33873395
],

docs/_generated/scenario-coverage.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
"tag": "conditions-and-if",
2121
"description": "CloudFormation Conditions section + resource-level `Condition:` key + `Fn::If` / `Fn::Equals` / `Fn::And` / `Fn::Or` / `Fn::Not` evaluated by cdkd itself. Two deploys flip a CDK-context-driven CfnParameter Default so the SAME stack is asserted in both settings: condition-gated resource creation (PRESENT vs ABSENT on AWS), `Fn::If` property + tag branch values reaching AWS, and `Fn::If` -> `AWS::NoValue` genuinely OMITTING a property."
2222
},
23+
{
24+
"tag": "conditions-update-semantics",
25+
"description": "Harder CloudFormation-Conditions-on-UPDATE semantics beyond the simple flip in `conditions-and-if` (which surfaced #840). A CDK-context phase flip (-c phase=a|b) redeploys the SAME stack in place and asserts: a resource that MOVES gating conditions (IsPhaseA-gated -> condition-false -> DELETED) and its reverse (IsPhaseB-gated absent -> CREATED); `Fn::If` -> `AWS::NoValue` REMOVING a nested property block (SQS RedrivePolicy) on an in-place UPDATE (same physical id, not a replacement); a condition-gated OUTPUT present vs absent in cdkd state outputs; a `DependsOn` to a condition-EXCLUDED resource being dropped (the depender still deploys); and a `Ref` to a condition-excluded resource living inside another condition-excluded resource (both pruned together, no dangling-ref crash)."
26+
},
2327
{
2428
"tag": "cross-cutting-deploy-destroy",
2529
"description": "Broad real-AWS regression set (39+ resource VPC+NAT+CF+Lambda+SQS or comparable breadth). Refreshes the integ-broad gate."
@@ -338,6 +342,13 @@
338342
"conditions-and-if"
339343
]
340344
},
345+
{
346+
"name": "conditions-update-2",
347+
"annotated": true,
348+
"scenarios": [
349+
"conditions-update-semantics"
350+
]
351+
},
341352
{
342353
"name": "context-test",
343354
"annotated": true,
@@ -1100,6 +1111,13 @@
11001111
"conditions-and-if"
11011112
]
11021113
},
1114+
{
1115+
"scenario": "conditions-update-semantics",
1116+
"description": "Harder CloudFormation-Conditions-on-UPDATE semantics beyond the simple flip in `conditions-and-if` (which surfaced #840). A CDK-context phase flip (-c phase=a|b) redeploys the SAME stack in place and asserts: a resource that MOVES gating conditions (IsPhaseA-gated -> condition-false -> DELETED) and its reverse (IsPhaseB-gated absent -> CREATED); `Fn::If` -> `AWS::NoValue` REMOVING a nested property block (SQS RedrivePolicy) on an in-place UPDATE (same physical id, not a replacement); a condition-gated OUTPUT present vs absent in cdkd state outputs; a `DependsOn` to a condition-EXCLUDED resource being dropped (the depender still deploys); and a `Ref` to a condition-excluded resource living inside another condition-excluded resource (both pruned together, no dangling-ref crash).",
1117+
"fixtures": [
1118+
"conditions-update-2"
1119+
]
1120+
},
11031121
{
11041122
"scenario": "cross-cutting-deploy-destroy",
11051123
"description": "Broad real-AWS regression set (39+ resource VPC+NAT+CF+Lambda+SQS or comparable breadth). Refreshes the integ-broad gate.",

docs/changelog-cdkd.md

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

docs/integ-coverage.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@ Registered without an integ fixture, with an explicit `// allow-no-integ: <ratio
137137
| `AWS::SNS::Subscription` | [`composite-stack`](../tests/integration/composite-stack/) (literal)<br>[`sns-sqs-event`](../tests/integration/sns-sqs-event/) (literal) |
138138
| `AWS::SNS::Topic` | [`bench-sdk`](../tests/integration/bench-sdk/) (l2)<br>[`cloudwatch`](../tests/integration/cloudwatch/) (l2)<br>[`composite-stack`](../tests/integration/composite-stack/) (l2,literal)<br>[`conditions-and-if`](../tests/integration/conditions-and-if/) (l1)<br>[`deployment-events`](../tests/integration/deployment-events/) (l2)<br>[`drift-revert`](../tests/integration/drift-revert/) (l2)<br>[`drift-revert-arrays`](../tests/integration/drift-revert-arrays/) (l2)<br>[`event-driven`](../tests/integration/event-driven/) (l2)<br>[`export`](../tests/integration/export/) (l2,literal)<br>[`full-stack-demo`](../tests/integration/full-stack-demo/) (l2)<br>[`iam-propagation-stress`](../tests/integration/iam-propagation-stress/) (l2)<br>[`intrinsics-torture`](../tests/integration/intrinsics-torture/) (l2)<br>[`microservices`](../tests/integration/microservices/) (l2)<br>[`migrate-from-cfn`](../tests/integration/migrate-from-cfn/) (l2)<br>[`monitoring`](../tests/integration/monitoring/) (l2)<br>[`scheduled-task`](../tests/integration/scheduled-task/) (l2)<br>[`serverless-api`](../tests/integration/serverless-api/) (l2)<br>[`sns-sqs-event`](../tests/integration/sns-sqs-event/) (l2) |
139139
| `AWS::SNS::TopicPolicy` | [`iam-propagation-stress`](../tests/integration/iam-propagation-stress/) (literal)<br>[`migrate-from-cfn`](../tests/integration/migrate-from-cfn/) (literal)<br>[`sns-sqs-event`](../tests/integration/sns-sqs-event/) (l2) |
140-
| `AWS::SQS::Queue` | [`basic`](../tests/integration/basic/) (l1)<br>[`bench-cdk-sample`](../tests/integration/bench-cdk-sample/) (l2)<br>[`bench-sdk`](../tests/integration/bench-sdk/) (l2)<br>[`composite-stack`](../tests/integration/composite-stack/) (l2,literal)<br>[`data-pipeline`](../tests/integration/data-pipeline/) (l2)<br>[`drift-revert-arrays`](../tests/integration/drift-revert-arrays/) (l2)<br>[`event-driven`](../tests/integration/event-driven/) (l2)<br>[`eventbridge`](../tests/integration/eventbridge/) (l2)<br>[`full-stack-demo`](../tests/integration/full-stack-demo/) (l2)<br>[`iam-propagation-stress`](../tests/integration/iam-propagation-stress/) (l2)<br>[`intrinsics-torture`](../tests/integration/intrinsics-torture/) (l2)<br>[`microservices`](../tests/integration/microservices/) (l2)<br>[`migrate-from-cfn`](../tests/integration/migrate-from-cfn/) (l2)<br>[`multi-resource`](../tests/integration/multi-resource/) (l2)<br>[`rollback-failure-injection`](../tests/integration/rollback-failure-injection/) (l1)<br>[`sns-sqs-event`](../tests/integration/sns-sqs-event/) (l2) |
140+
| `AWS::SQS::Queue` | [`basic`](../tests/integration/basic/) (l1)<br>[`bench-cdk-sample`](../tests/integration/bench-cdk-sample/) (l2)<br>[`bench-sdk`](../tests/integration/bench-sdk/) (l2)<br>[`composite-stack`](../tests/integration/composite-stack/) (l2,literal)<br>[`conditions-update-2`](../tests/integration/conditions-update-2/) (l1)<br>[`data-pipeline`](../tests/integration/data-pipeline/) (l2)<br>[`drift-revert-arrays`](../tests/integration/drift-revert-arrays/) (l2)<br>[`event-driven`](../tests/integration/event-driven/) (l2)<br>[`eventbridge`](../tests/integration/eventbridge/) (l2)<br>[`full-stack-demo`](../tests/integration/full-stack-demo/) (l2)<br>[`iam-propagation-stress`](../tests/integration/iam-propagation-stress/) (l2)<br>[`intrinsics-torture`](../tests/integration/intrinsics-torture/) (l2)<br>[`microservices`](../tests/integration/microservices/) (l2)<br>[`migrate-from-cfn`](../tests/integration/migrate-from-cfn/) (l2)<br>[`multi-resource`](../tests/integration/multi-resource/) (l2)<br>[`rollback-failure-injection`](../tests/integration/rollback-failure-injection/) (l1)<br>[`sns-sqs-event`](../tests/integration/sns-sqs-event/) (l2) |
141141
| `AWS::SQS::QueuePolicy` | [`iam-propagation-stress`](../tests/integration/iam-propagation-stress/) (literal)<br>[`migrate-from-cfn`](../tests/integration/migrate-from-cfn/) (literal) |
142-
| `AWS::SSM::Parameter` | [`bench-sdk`](../tests/integration/bench-sdk/) (l2)<br>[`composite-stack`](../tests/integration/composite-stack/) (l2,literal)<br>[`conditions-and-if`](../tests/integration/conditions-and-if/) (l1)<br>[`context-test`](../tests/integration/context-test/) (l2)<br>[`cross-region-state-bucket`](../tests/integration/cross-region-state-bucket/) (l1)<br>[`cross-stack-references`](../tests/integration/cross-stack-references/) (l1)<br>[`deletion-policy-retain`](../tests/integration/deletion-policy-retain/) (l2,literal)<br>[`deployment-events`](../tests/integration/deployment-events/) (l2)<br>[`destroy-interrupt`](../tests/integration/destroy-interrupt/) (l2,literal)<br>[`export-nested-stack`](../tests/integration/export-nested-stack/) (l2,literal)<br>[`import-nested-stack`](../tests/integration/import-nested-stack/) (l2,literal)<br>[`import-value-strong-ref`](../tests/integration/import-value-strong-ref/) (l2)<br>[`infra-security`](../tests/integration/infra-security/) (l2)<br>[`intrinsics-torture`](../tests/integration/intrinsics-torture/) (l1,literal)<br>[`legacy-bucket-name-fallback`](../tests/integration/legacy-bucket-name-fallback/) (l2)<br>[`legacy-state-migration`](../tests/integration/legacy-state-migration/) (l2)<br>[`local-invoke-from-cfn-stack-multi-stack`](../tests/integration/local-invoke-from-cfn-stack-multi-stack/) (l2)<br>[`microservices`](../tests/integration/microservices/) (l2)<br>[`multi-region-same-stack`](../tests/integration/multi-region-same-stack/) (l2)<br>[`nested-stack`](../tests/integration/nested-stack/) (l2)<br>[`nested-stack-deep`](../tests/integration/nested-stack-deep/) (l2)<br>[`rds-full-stack`](../tests/integration/rds-full-stack/) (l2)<br>[`rollback-failure-injection`](../tests/integration/rollback-failure-injection/) (l2)<br>[`schema-v5-to-v6-migration`](../tests/integration/schema-v5-to-v6-migration/) (l2)<br>[`schema-v6-to-v7-migration`](../tests/integration/schema-v6-to-v7-migration/) (l2)<br>[`schema-v7-to-v8-migration`](../tests/integration/schema-v7-to-v8-migration/) (l1,l2)<br>[`state-info-command`](../tests/integration/state-info-command/) (l2)<br>[`vpc-lookup`](../tests/integration/vpc-lookup/) (l2) |
142+
| `AWS::SSM::Parameter` | [`bench-sdk`](../tests/integration/bench-sdk/) (l2)<br>[`composite-stack`](../tests/integration/composite-stack/) (l2,literal)<br>[`conditions-and-if`](../tests/integration/conditions-and-if/) (l1)<br>[`conditions-update-2`](../tests/integration/conditions-update-2/) (l1)<br>[`context-test`](../tests/integration/context-test/) (l2)<br>[`cross-region-state-bucket`](../tests/integration/cross-region-state-bucket/) (l1)<br>[`cross-stack-references`](../tests/integration/cross-stack-references/) (l1)<br>[`deletion-policy-retain`](../tests/integration/deletion-policy-retain/) (l2,literal)<br>[`deployment-events`](../tests/integration/deployment-events/) (l2)<br>[`destroy-interrupt`](../tests/integration/destroy-interrupt/) (l2,literal)<br>[`export-nested-stack`](../tests/integration/export-nested-stack/) (l2,literal)<br>[`import-nested-stack`](../tests/integration/import-nested-stack/) (l2,literal)<br>[`import-value-strong-ref`](../tests/integration/import-value-strong-ref/) (l2)<br>[`infra-security`](../tests/integration/infra-security/) (l2)<br>[`intrinsics-torture`](../tests/integration/intrinsics-torture/) (l1,literal)<br>[`legacy-bucket-name-fallback`](../tests/integration/legacy-bucket-name-fallback/) (l2)<br>[`legacy-state-migration`](../tests/integration/legacy-state-migration/) (l2)<br>[`local-invoke-from-cfn-stack-multi-stack`](../tests/integration/local-invoke-from-cfn-stack-multi-stack/) (l2)<br>[`microservices`](../tests/integration/microservices/) (l2)<br>[`multi-region-same-stack`](../tests/integration/multi-region-same-stack/) (l2)<br>[`nested-stack`](../tests/integration/nested-stack/) (l2)<br>[`nested-stack-deep`](../tests/integration/nested-stack-deep/) (l2)<br>[`rds-full-stack`](../tests/integration/rds-full-stack/) (l2)<br>[`rollback-failure-injection`](../tests/integration/rollback-failure-injection/) (l2)<br>[`schema-v5-to-v6-migration`](../tests/integration/schema-v5-to-v6-migration/) (l2)<br>[`schema-v6-to-v7-migration`](../tests/integration/schema-v6-to-v7-migration/) (l2)<br>[`schema-v7-to-v8-migration`](../tests/integration/schema-v7-to-v8-migration/) (l1,l2)<br>[`state-info-command`](../tests/integration/state-info-command/) (l2)<br>[`vpc-lookup`](../tests/integration/vpc-lookup/) (l2) |
143143
| `AWS::SecretsManager::Secret` | [`composite-stack`](../tests/integration/composite-stack/) (l2,literal)<br>[`event-driven`](../tests/integration/event-driven/) (l2)<br>[`full-stack-demo`](../tests/integration/full-stack-demo/) (l2)<br>[`local-run-task-from-state`](../tests/integration/local-run-task-from-state/) (l2,literal) |
144144
| `AWS::ServiceDiscovery::PrivateDnsNamespace` | [`drift-revert-vpc`](../tests/integration/drift-revert-vpc/) (l2,literal)<br>[`local-ecs-service-connect`](../tests/integration/local-ecs-service-connect/) (literal) |
145145
| `AWS::ServiceDiscovery::Service` | [`local-ecs-service-connect`](../tests/integration/local-ecs-service-connect/) (literal) |

docs/scenario-coverage.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
Run `vp run scenario-coverage` to regenerate.
66

7-
**52 / 52 canonical scenarios** have at least one integ fixture exercising them. **119 / 138 integ fixtures** carry a `.scenarios.json` sidecar (with 0+ tags); the rest are un-annotated and contributor-reviewed below.
7+
**53 / 53 canonical scenarios** have at least one integ fixture exercising them. **120 / 139 integ fixtures** carry a `.scenarios.json` sidecar (with 0+ tags); the rest are un-annotated and contributor-reviewed below.
88

99
## How this is computed
1010

@@ -26,7 +26,7 @@ This report is a visibility tool, not a commit-time gate. Many cdkd fixtures leg
2626

2727
_None._ Every canonical scenario has at least one integ fixture tagged with it.
2828

29-
## Per-scenario coverage (52 scenarios)
29+
## Per-scenario coverage (53 scenarios)
3030

3131
| Scenario | Description | Integ Fixture(s) |
3232
|---|---|---|
@@ -35,6 +35,7 @@ _None._ Every canonical scenario has at least one integ fixture tagged with it.
3535
| `cfn-macro-expansion` | CloudFormation macro / `Fn::Transform` expansion via transient CFn changeset round-trip (SAM, AWS::Include, AWS::LanguageExtensions, custom macros). See `docs/design/463-cfn-macros.md`. | [`macro-expansion`](../tests/integration/macro-expansion/) |
3636
| `cloudfront-oai-attribute-enrichment` | CloudFront OAI `S3CanonicalUserId` attribute enrichment (the attribute is not on `GetCloudFrontOriginAccessIdentity` directly). | [`s3-cloudfront`](../tests/integration/s3-cloudfront/) |
3737
| `conditions-and-if` | CloudFormation Conditions section + resource-level `Condition:` key + `Fn::If` / `Fn::Equals` / `Fn::And` / `Fn::Or` / `Fn::Not` evaluated by cdkd itself. Two deploys flip a CDK-context-driven CfnParameter Default so the SAME stack is asserted in both settings: condition-gated resource creation (PRESENT vs ABSENT on AWS), `Fn::If` property + tag branch values reaching AWS, and `Fn::If` -> `AWS::NoValue` genuinely OMITTING a property. | [`conditions-and-if`](../tests/integration/conditions-and-if/) |
38+
| `conditions-update-semantics` | Harder CloudFormation-Conditions-on-UPDATE semantics beyond the simple flip in `conditions-and-if` (which surfaced #840). A CDK-context phase flip (-c phase=a|b) redeploys the SAME stack in place and asserts: a resource that MOVES gating conditions (IsPhaseA-gated -> condition-false -> DELETED) and its reverse (IsPhaseB-gated absent -> CREATED); `Fn::If` -> `AWS::NoValue` REMOVING a nested property block (SQS RedrivePolicy) on an in-place UPDATE (same physical id, not a replacement); a condition-gated OUTPUT present vs absent in cdkd state outputs; a `DependsOn` to a condition-EXCLUDED resource being dropped (the depender still deploys); and a `Ref` to a condition-excluded resource living inside another condition-excluded resource (both pruned together, no dangling-ref crash). | [`conditions-update-2`](../tests/integration/conditions-update-2/) |
3839
| `cross-cutting-deploy-destroy` | Broad real-AWS regression set (39+ resource VPC+NAT+CF+Lambda+SQS or comparable breadth). Refreshes the integ-broad gate. | [`bench-ccapi`](../tests/integration/bench-ccapi/)<br>[`bench-cdk-sample`](../tests/integration/bench-cdk-sample/)<br>[`bench-sdk`](../tests/integration/bench-sdk/)<br>[`full-stack-demo`](../tests/integration/full-stack-demo/)<br>[`lambda`](../tests/integration/lambda/)<br>[`microservices`](../tests/integration/microservices/)<br>[`multi-resource`](../tests/integration/multi-resource/) |
3940
| `custom-resource-async-poll` | Custom Resource backed by Lambda + cfn-response via S3 pre-signed URL polling. | [`cloudfront-function-url`](../tests/integration/cloudfront-function-url/)<br>[`custom-resource-provider`](../tests/integration/custom-resource-provider/)<br>[`destroy-interrupt`](../tests/integration/destroy-interrupt/)<br>[`vpc-lambda-cr-race`](../tests/integration/vpc-lambda-cr-race/) |
4041
| `deletion-policy-retain` | DeletionPolicy: Retain skip on destroy (schema v5 recorded value wins over template). | [`deletion-policy-retain`](../tests/integration/deletion-policy-retain/) |

scripts/build-scenario-coverage-matrix.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ const KNOWN_SCENARIOS: Record<string, string> = {
137137
// ---- Conditions / intrinsic-function patterns ----
138138
'conditions-and-if':
139139
'CloudFormation Conditions section + resource-level `Condition:` key + `Fn::If` / `Fn::Equals` / `Fn::And` / `Fn::Or` / `Fn::Not` evaluated by cdkd itself. Two deploys flip a CDK-context-driven CfnParameter Default so the SAME stack is asserted in both settings: condition-gated resource creation (PRESENT vs ABSENT on AWS), `Fn::If` property + tag branch values reaching AWS, and `Fn::If` -> `AWS::NoValue` genuinely OMITTING a property.',
140+
'conditions-update-semantics':
141+
'Harder CloudFormation-Conditions-on-UPDATE semantics beyond the simple flip in `conditions-and-if` (which surfaced #840). A CDK-context phase flip (-c phase=a|b) redeploys the SAME stack in place and asserts: a resource that MOVES gating conditions (IsPhaseA-gated -> condition-false -> DELETED) and its reverse (IsPhaseB-gated absent -> CREATED); `Fn::If` -> `AWS::NoValue` REMOVING a nested property block (SQS RedrivePolicy) on an in-place UPDATE (same physical id, not a replacement); a condition-gated OUTPUT present vs absent in cdkd state outputs; a `DependsOn` to a condition-EXCLUDED resource being dropped (the depender still deploys); and a `Ref` to a condition-excluded resource living inside another condition-excluded resource (both pruned together, no dangling-ref crash).',
140142

141143
// ---- Drift / state patterns ----
142144
'drift-revert-roundtrip':

0 commit comments

Comments
 (0)