@intentius/chant-lexicon-aws 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/components/__tests__/mock-cloud-executor.d.ts +106 -0
  2. package/dist/components/__tests__/mock-cloud-executor.d.ts.map +1 -0
  3. package/dist/components/apply.d.ts +242 -0
  4. package/dist/components/apply.d.ts.map +1 -0
  5. package/dist/components/builders.d.ts +37 -0
  6. package/dist/components/builders.d.ts.map +1 -0
  7. package/dist/components/capability-plugin.d.ts +23 -0
  8. package/dist/components/capability-plugin.d.ts.map +1 -0
  9. package/dist/components/cloud-executor.d.ts +350 -0
  10. package/dist/components/cloud-executor.d.ts.map +1 -0
  11. package/dist/components/config-bom.d.ts +135 -0
  12. package/dist/components/config-bom.d.ts.map +1 -0
  13. package/dist/components/host-delivery.d.ts +110 -0
  14. package/dist/components/host-delivery.d.ts.map +1 -0
  15. package/dist/components/index.d.ts +19 -0
  16. package/dist/components/index.d.ts.map +1 -0
  17. package/dist/components/job-submission.d.ts +89 -0
  18. package/dist/components/job-submission.d.ts.map +1 -0
  19. package/dist/components/publish.d.ts +194 -0
  20. package/dist/components/publish.d.ts.map +1 -0
  21. package/dist/components/safety.d.ts +49 -0
  22. package/dist/components/safety.d.ts.map +1 -0
  23. package/dist/components/wait-aws.d.ts +65 -0
  24. package/dist/components/wait-aws.d.ts.map +1 -0
  25. package/dist/generated/index.d.ts +7 -4
  26. package/dist/generated/index.d.ts.map +1 -1
  27. package/dist/import/live-export.d.ts.map +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/integrity.json +4 -4
  31. package/dist/lint/audit-catalog.d.ts +10 -0
  32. package/dist/lint/audit-catalog.d.ts.map +1 -0
  33. package/dist/manifest.json +1 -1
  34. package/dist/meta.json +141 -30
  35. package/dist/op/activities/floci.d.ts +54 -0
  36. package/dist/op/activities/floci.d.ts.map +1 -0
  37. package/dist/op/activities/index.d.ts +8 -0
  38. package/dist/op/activities/index.d.ts.map +1 -0
  39. package/dist/ownership.d.ts +10 -0
  40. package/dist/ownership.d.ts.map +1 -0
  41. package/dist/plugin.d.ts.map +1 -1
  42. package/dist/serializer.d.ts.map +1 -1
  43. package/dist/types/index.d.ts +145 -38
  44. package/package.json +12 -2
  45. package/src/components/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
  46. package/src/components/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
  47. package/src/components/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
  48. package/src/components/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
  49. package/src/components/__fixtures__/synthesized-template.json +53 -0
  50. package/src/components/__tests__/mock-cloud-executor.ts +425 -0
  51. package/src/components/apply.test.ts +382 -0
  52. package/src/components/apply.ts +499 -0
  53. package/src/components/builders.test.ts +49 -0
  54. package/src/components/builders.ts +59 -0
  55. package/src/components/capability-plugin.ts +73 -0
  56. package/src/components/cloud-executor.test.ts +25 -0
  57. package/src/components/cloud-executor.ts +814 -0
  58. package/src/components/config-bom.test.ts +273 -0
  59. package/src/components/config-bom.ts +310 -0
  60. package/src/components/host-delivery.test.ts +121 -0
  61. package/src/components/host-delivery.ts +193 -0
  62. package/src/components/index.ts +19 -0
  63. package/src/components/job-submission.test.ts +97 -0
  64. package/src/components/job-submission.ts +138 -0
  65. package/src/components/pilots-e2e.test.ts +460 -0
  66. package/src/components/presets-e2e.test.ts +166 -0
  67. package/src/components/publish.test.ts +318 -0
  68. package/src/components/publish.ts +356 -0
  69. package/src/components/safety.test.ts +47 -0
  70. package/src/components/safety.ts +78 -0
  71. package/src/components/wait-aws.test.ts +79 -0
  72. package/src/components/wait-aws.ts +132 -0
  73. package/src/generated/index.d.ts +145 -38
  74. package/src/generated/index.ts +12 -9
  75. package/src/generated/lexicon-aws.json +141 -30
  76. package/src/import/live-export.ts +2 -1
  77. package/src/index.ts +5 -0
  78. package/src/lint/audit-catalog.ts +45 -0
  79. package/src/op/activities/floci.test.ts +67 -0
  80. package/src/op/activities/floci.ts +149 -0
  81. package/src/op/activities/index.ts +16 -0
  82. package/src/ownership.test.ts +18 -0
  83. package/src/ownership.ts +15 -0
  84. package/src/plugin.ts +3 -0
  85. package/src/serializer.ts +2 -1
@@ -1015,15 +1015,24 @@
1015
1015
  "resourceType": "AWS::ServiceCatalog::AcceptedPortfolioShare",
1016
1016
  "kind": "resource",
1017
1017
  "lexicon": "aws",
1018
- "attrs": {
1019
- "Id": "Id"
1018
+ "propertyConstraints": {
1019
+ "AcceptLanguage": {
1020
+ "pattern": "^(en|jp|zh)$"
1021
+ },
1022
+ "PortfolioId": {
1023
+ "minLength": 1,
1024
+ "maxLength": 100
1025
+ }
1020
1026
  },
1021
1027
  "createOnly": [
1022
- "PortfolioId",
1028
+ "AcceptLanguage",
1029
+ "PortfolioId"
1030
+ ],
1031
+ "writeOnly": [
1023
1032
  "AcceptLanguage"
1024
1033
  ],
1025
1034
  "primaryIdentifier": [
1026
- "Id"
1035
+ "PortfolioId"
1027
1036
  ]
1028
1037
  },
1029
1038
  "AccessConfig": {
@@ -3789,6 +3798,7 @@
3789
3798
  "maxLength": 1024
3790
3799
  },
3791
3800
  "Arn": {
3801
+ "pattern": "^arn:[^:]+:access-analyzer(-beta|-gamma)?:[^:]*:[^:]*:analyzer/.+$",
3792
3802
  "minLength": 1,
3793
3803
  "maxLength": 1600
3794
3804
  },
@@ -8657,6 +8667,11 @@
8657
8667
  "kind": "property",
8658
8668
  "lexicon": "aws"
8659
8669
  },
8670
+ "AzureConnectorConfiguration": {
8671
+ "resourceType": "AWS::Config::Connector.AzureConnectorConfiguration",
8672
+ "kind": "property",
8673
+ "lexicon": "aws"
8674
+ },
8660
8675
  "B2BICapability": {
8661
8676
  "resourceType": "AWS::B2BI::Capability",
8662
8677
  "kind": "resource",
@@ -14540,24 +14555,36 @@
14540
14555
  "kind": "resource",
14541
14556
  "lexicon": "aws",
14542
14557
  "attrs": {
14543
- "ProvisioningArtifactIds": "ProvisioningArtifactIds",
14544
14558
  "Id": "Id",
14559
+ "ProvisioningArtifactIds": "ProvisioningArtifactIds",
14545
14560
  "ProvisioningArtifactNames": "ProvisioningArtifactNames",
14546
14561
  "ProductName": "ProductName"
14547
14562
  },
14563
+ "propertyConstraints": {
14564
+ "ProductType": {
14565
+ "enum": [
14566
+ "CLOUD_FORMATION_TEMPLATE",
14567
+ "MARKETPLACE_AMI",
14568
+ "MARKETPLACE_CAR",
14569
+ "TERRAFORM_OPEN_SOURCE",
14570
+ "TERRAFORM_CLOUD",
14571
+ "EXTERNAL"
14572
+ ]
14573
+ }
14574
+ },
14575
+ "writeOnly": [
14576
+ "AcceptLanguage",
14577
+ "ReplaceProvisioningArtifacts",
14578
+ "ProvisioningArtifactParameters/*/DisableTemplateValidation"
14579
+ ],
14548
14580
  "primaryIdentifier": [
14549
14581
  "Id"
14550
- ]
14551
- },
14552
- "CloudFormationProduct_CodeStarParameters": {
14553
- "resourceType": "AWS::ServiceCatalog::CloudFormationProduct.CodeStarParameters",
14554
- "kind": "property",
14555
- "lexicon": "aws"
14556
- },
14557
- "CloudFormationProduct_ConnectionParameters": {
14558
- "resourceType": "AWS::ServiceCatalog::CloudFormationProduct.ConnectionParameters",
14559
- "kind": "property",
14560
- "lexicon": "aws"
14582
+ ],
14583
+ "tagging": {
14584
+ "taggable": true,
14585
+ "tagOnCreate": true,
14586
+ "tagUpdatable": true
14587
+ }
14561
14588
  },
14562
14589
  "CloudFormationProduct_ProvisioningArtifactProperties": {
14563
14590
  "resourceType": "AWS::ServiceCatalog::CloudFormationProduct.ProvisioningArtifactProperties",
@@ -16285,11 +16312,6 @@
16285
16312
  "kind": "property",
16286
16313
  "lexicon": "aws"
16287
16314
  },
16288
- "CodeStarParameters": {
16289
- "resourceType": "AWS::ServiceCatalog::CloudFormationProduct.CodeStarParameters",
16290
- "kind": "property",
16291
- "lexicon": "aws"
16292
- },
16293
16315
  "CognitoIdentityProvider": {
16294
16316
  "resourceType": "AWS::Cognito::IdentityPool.CognitoIdentityProvider",
16295
16317
  "kind": "property",
@@ -17227,6 +17249,49 @@
17227
17249
  "tagUpdatable": true
17228
17250
  }
17229
17251
  },
17252
+ "ConfigConnector": {
17253
+ "resourceType": "AWS::Config::Connector",
17254
+ "kind": "resource",
17255
+ "lexicon": "aws",
17256
+ "attrs": {
17257
+ "Arn": "Arn",
17258
+ "Name": "Name",
17259
+ "CreatedTime": "CreatedTime"
17260
+ },
17261
+ "propertyConstraints": {
17262
+ "Name": {
17263
+ "minLength": 1,
17264
+ "maxLength": 256
17265
+ }
17266
+ },
17267
+ "createOnly": [
17268
+ "ConnectorConfiguration"
17269
+ ],
17270
+ "primaryIdentifier": [
17271
+ "Arn"
17272
+ ],
17273
+ "replacementStrategy": "delete_then_create",
17274
+ "tagging": {
17275
+ "taggable": true,
17276
+ "tagOnCreate": true,
17277
+ "tagUpdatable": true
17278
+ }
17279
+ },
17280
+ "ConfigConnector_AzureConnectorConfiguration": {
17281
+ "resourceType": "AWS::Config::Connector.AzureConnectorConfiguration",
17282
+ "kind": "property",
17283
+ "lexicon": "aws"
17284
+ },
17285
+ "ConfigConnector_ConnectorConfiguration": {
17286
+ "resourceType": "AWS::Config::Connector.ConnectorConfiguration",
17287
+ "kind": "property",
17288
+ "lexicon": "aws"
17289
+ },
17290
+ "ConfigConnector_Tag": {
17291
+ "resourceType": "AWS::Config::Connector.Tag",
17292
+ "kind": "property",
17293
+ "lexicon": "aws"
17294
+ },
17230
17295
  "ConfigData": {
17231
17296
  "resourceType": "AWS::GroundStation::Config.ConfigData",
17232
17297
  "kind": "property",
@@ -19388,11 +19453,6 @@
19388
19453
  "kind": "property",
19389
19454
  "lexicon": "aws"
19390
19455
  },
19391
- "ConnectionParameters": {
19392
- "resourceType": "AWS::ServiceCatalog::CloudFormationProduct.ConnectionParameters",
19393
- "kind": "property",
19394
- "lexicon": "aws"
19395
- },
19396
19456
  "ConnectionPasswordEncryption": {
19397
19457
  "resourceType": "AWS::Glue::DataCatalogEncryptionSettings.ConnectionPasswordEncryption",
19398
19458
  "kind": "property",
@@ -19428,6 +19488,11 @@
19428
19488
  "kind": "property",
19429
19489
  "lexicon": "aws"
19430
19490
  },
19491
+ "ConnectorConfiguration": {
19492
+ "resourceType": "AWS::Config::Connector.ConnectorConfiguration",
19493
+ "kind": "property",
19494
+ "lexicon": "aws"
19495
+ },
19431
19496
  "ConnectorDefinition": {
19432
19497
  "resourceType": "AWS::Greengrass::ConnectorDefinition",
19433
19498
  "kind": "resource",
@@ -31704,6 +31769,29 @@
31704
31769
  "kind": "property",
31705
31770
  "lexicon": "aws"
31706
31771
  },
31772
+ "DomainObjectType": {
31773
+ "resourceType": "AWS::CustomerProfiles::DomainObjectType",
31774
+ "kind": "resource",
31775
+ "lexicon": "aws",
31776
+ "attrs": {
31777
+ "CreatedAt": "CreatedAt",
31778
+ "LastUpdatedAt": "LastUpdatedAt"
31779
+ },
31780
+ "createOnly": [
31781
+ "DomainName",
31782
+ "ObjectTypeName",
31783
+ "Fields"
31784
+ ],
31785
+ "primaryIdentifier": [
31786
+ "DomainName",
31787
+ "ObjectTypeName"
31788
+ ],
31789
+ "tagging": {
31790
+ "taggable": true,
31791
+ "tagOnCreate": true,
31792
+ "tagUpdatable": true
31793
+ }
31794
+ },
31707
31795
  "DomainResult": {
31708
31796
  "resourceType": "AWS::CloudFront::DistributionTenant.DomainResult",
31709
31797
  "kind": "property",
@@ -33046,7 +33134,8 @@
33046
33134
  "FailureReason": "FailureReason",
33047
33135
  "CreatedAt": "CreatedAt",
33048
33136
  "SecurityGroup": "SecurityGroup",
33049
- "CertificateAuthority": "CertificateAuthority"
33137
+ "CertificateAuthority": "CertificateAuthority",
33138
+ "AuthProxyUrl": "AuthProxyUrl"
33050
33139
  },
33051
33140
  "propertyConstraints": {
33052
33141
  "Name": {
@@ -33058,6 +33147,10 @@
33058
33147
  "pattern": "[A-Za-z0-9._/-]+",
33059
33148
  "minLength": 1,
33060
33149
  "maxLength": 64
33150
+ },
33151
+ "SessionIdleTimeoutInMinutes": {
33152
+ "minimum": 1,
33153
+ "maximum": 1440
33061
33154
  }
33062
33155
  },
33063
33156
  "createOnly": [
@@ -33066,7 +33159,11 @@
33066
33159
  "Type",
33067
33160
  "ReleaseLabel",
33068
33161
  "ExecutionRoleArn",
33069
- "ConfigurationOverrides"
33162
+ "ConfigurationOverrides",
33163
+ "SessionIdleTimeoutInMinutes"
33164
+ ],
33165
+ "writeOnly": [
33166
+ "SessionIdleTimeoutInMinutes"
33070
33167
  ],
33071
33168
  "primaryIdentifier": [
33072
33169
  "Arn"
@@ -46882,7 +46979,8 @@
46882
46979
  "BlockDeviceMappings",
46883
46980
  "WorkingDirectory",
46884
46981
  "AdditionalInstanceConfiguration",
46885
- "AmiTags"
46982
+ "AmiTags",
46983
+ "AmiWatermarks"
46886
46984
  ],
46887
46985
  "primaryIdentifier": [
46888
46986
  "Arn"
@@ -51978,6 +52076,10 @@
51978
52076
  "createOnly": [
51979
52077
  "KeyspaceName"
51980
52078
  ],
52079
+ "writeOnly": [
52080
+ "ClientSideTimestampsEnabled",
52081
+ "ReplicationSpecification/RegionList"
52082
+ ],
51981
52083
  "primaryIdentifier": [
51982
52084
  "KeyspaceName"
51983
52085
  ],
@@ -82379,6 +82481,7 @@
82379
82481
  "lexicon": "aws",
82380
82482
  "attrs": {
82381
82483
  "Arn": "Arn",
82484
+ "ContentQualityAnalysisType": "ContentQualityAnalysisType",
82382
82485
  "CreatedAt": "CreatedAt",
82383
82486
  "Id": "Id",
82384
82487
  "InputType": "InputType",
@@ -91344,6 +91447,9 @@
91344
91447
  "resourceType": "AWS::SSM::Parameter",
91345
91448
  "kind": "resource",
91346
91449
  "lexicon": "aws",
91450
+ "attrs": {
91451
+ "Arn": "Arn"
91452
+ },
91347
91453
  "propertyConstraints": {
91348
91454
  "Type": {
91349
91455
  "enum": [
@@ -91366,6 +91472,9 @@
91366
91472
  },
91367
91473
  "Name": {
91368
91474
  "pattern": "^((?![aA][wW][sS]|[sS]{2}[mM])[\\w.-]+|\\/(?![aA][wW][sS]|[sS]{2}[mM])[\\w.-]+(\\/[\\w.-]+)+)$"
91475
+ },
91476
+ "Arn": {
91477
+ "pattern": "arn:aws(-[a-z0-9-]+)*:ssm:[a-z0-9-]+:[0-9]{12}:parameter/.+"
91369
91478
  }
91370
91479
  },
91371
91480
  "createOnly": [
@@ -101103,7 +101212,9 @@
101103
101212
  },
101104
101213
  "createOnly": [
101105
101214
  "ContainerProvider",
101106
- "Name"
101215
+ "Name",
101216
+ "SecurityConfigurationId",
101217
+ "SessionEnabled"
101107
101218
  ],
101108
101219
  "primaryIdentifier": [
101109
101220
  "Id"
@@ -1,5 +1,6 @@
1
1
  import type { ExportedTemplate, ResourceSelector } from "@intentius/chant/lexicon";
2
2
  import { hasOwnershipMarker, tagArrayToMap } from "@intentius/chant/ownership";
3
+ import { AWS_TAG_OWNERSHIP_KEYS } from "../ownership";
3
4
  import { CFParser } from "./parser";
4
5
 
5
6
  /**
@@ -30,7 +31,7 @@ export function parseStackTemplate(
30
31
  if (selector?.name !== undefined && r.logicalId !== selector.name) return false;
31
32
  if (owned) {
32
33
  const tags = (r.properties as { Tags?: Array<{ Key?: string; Value?: unknown }> }).Tags;
33
- if (!hasOwnershipMarker(tagArrayToMap(tags), "aws-tag")) return false;
34
+ if (!hasOwnershipMarker(tagArrayToMap(tags), AWS_TAG_OWNERSHIP_KEYS)) return false;
34
35
  }
35
36
  return true;
36
37
  }),
package/src/index.ts CHANGED
@@ -93,3 +93,8 @@ export type {
93
93
  export { generate, writeGeneratedFiles } from "./codegen/generate";
94
94
  export { packageLexicon } from "./codegen/package";
95
95
  export type { PackageOptions, PackageResult } from "./codegen/package";
96
+
97
+ // Component/release capabilities — the AWS operational leaves (cfn-deploy,
98
+ // emr-*, code-deploy, …) contributed to core's capability-plugin seam. Core
99
+ // loads `awsCapabilityPlugin` when a project's chant.config lists this lexicon.
100
+ export { awsCapabilityPlugin } from "./components/capability-plugin";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The aws lexicon's `chant audit` catalog — metadata (title/tier/fix/authority/
3
+ * category) for the WAW* CloudFormation post-synth rules this lexicon ships.
4
+ * Contributed to core's aggregate via `awsPlugin.auditCatalog()` (#687, epic
5
+ * #350); core keeps only the generic catalog machinery + cross-cutting ids.
6
+ */
7
+
8
+ import { auditRule, type RuleMeta, type Authority } from "@intentius/chant/audit/catalog";
9
+
10
+ /** AWS Well-Architected Security Pillar — the authority chant cites for AWS security findings. */
11
+ const AWS_SEC: Authority = {
12
+ name: "AWS — Security Pillar (Well-Architected)",
13
+ url: "https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html",
14
+ };
15
+
16
+ /** Every WAW* audit rule's catalog entry. */
17
+ export const awsAuditCatalog: Record<string, RuleMeta> = {
18
+ WAW010: auditRule("WAW010", "report-only", "guidance", "Redundant DependsOn", "Remove DependsOn already implied by a Ref/GetAtt.", { category: "best-practice" }),
19
+ WAW011: auditRule("WAW011", "report-only", "guidance", "Deprecated Lambda runtime", "Upgrade to a supported Lambda runtime.", { category: "best-practice" }),
20
+ WAW013: auditRule("WAW013", "merge-worthy", "guidance", "Child stack exports nothing", "Add stackOutput() exports the parent can reference.", { category: "correctness" }),
21
+ WAW014: auditRule("WAW014", "report-only", "guidance", "Nested stack outputs never referenced", "Reference the outputs or split into a separate build.", { category: "best-practice" }),
22
+ WAW015: auditRule("WAW015", "merge-worthy", "guidance", "Circular dependency between nested stacks", "Break the cycle between nested stacks.", { category: "correctness" }),
23
+ WAW016: auditRule("WAW016", "report-only", "guidance", "Deprecated property", "Replace the deprecated CloudFormation property.", { category: "best-practice" }),
24
+ WAW017: auditRule("WAW017", "report-only", "guidance", "Missing tags on a taggable resource", "Add tags for cost allocation and compliance.", { category: "best-practice" }),
25
+ WAW018: auditRule("WAW018", "merge-worthy", "guidance", "S3 bucket missing public access block", "Add a PublicAccessBlockConfiguration blocking all public access.", { authority: [AWS_SEC] }),
26
+ WAW019: auditRule("WAW019", "merge-worthy", "guidance", "Security group allows unrestricted ingress on a sensitive port", "Restrict the CIDR on SSH/RDP/database ports to known sources.", { authority: [AWS_SEC] }),
27
+ WAW020: auditRule("WAW020", "merge-worthy", "guidance", "IAM policy uses a wildcard Action", "Scope the policy to specific actions (least privilege).", { authority: [AWS_SEC] }),
28
+ WAW021: auditRule("WAW021", "merge-worthy", "guidance", "RDS storage not encrypted", "Enable StorageEncrypted for encryption at rest.", { authority: [AWS_SEC] }),
29
+ WAW022: auditRule("WAW022", "report-only", "guidance", "Lambda has no VpcConfig", "Consider a VpcConfig for network isolation if the function needs VPC resources.", { category: "best-practice" }),
30
+ WAW023: auditRule("WAW023", "report-only", "guidance", "CloudFront has no WAF web ACL", "Consider attaching a WAF web ACL.", { category: "best-practice" }),
31
+ WAW024: auditRule("WAW024", "report-only", "guidance", "ALB access logging disabled", "Enable access logging for audit trails.", { category: "best-practice" }),
32
+ WAW025: auditRule("WAW025", "merge-worthy", "guidance", "SNS topic not encrypted", "Set KmsMasterKeyId for encryption at rest.", { authority: [AWS_SEC] }),
33
+ WAW026: auditRule("WAW026", "merge-worthy", "guidance", "SQS queue not encrypted", "Enable SqsManagedSseEnabled or set KmsMasterKeyId.", { authority: [AWS_SEC] }),
34
+ WAW027: auditRule("WAW027", "report-only", "guidance", "DynamoDB point-in-time recovery disabled", "Enable PITR for recovery.", { category: "best-practice" }),
35
+ WAW028: auditRule("WAW028", "merge-worthy", "guidance", "EBS volume not encrypted", "Enable encryption at rest.", { authority: [AWS_SEC] }),
36
+ WAW029: auditRule("WAW029", "merge-worthy", "guidance", "Invalid DependsOn target", "Fix the dangling/self DependsOn reference.", { category: "correctness" }),
37
+ WAW030: auditRule("WAW030", "report-only", "guidance", "Missing DependsOn for a known ordering pattern", "Add the DependsOn the pattern requires.", { category: "best-practice" }),
38
+ WAW031: auditRule("WAW031", "report-only", "guidance", "EKS Addon missing ServiceAccountRoleArn", "Set ServiceAccountRoleArn (IRSA) for addons that need it.", { category: "best-practice" }),
39
+ WAW032: auditRule("WAW032", "merge-worthy", "guidance", "EFS transit encryption disabled on Fargate", "Enable transit encryption for the EFS volume.", { authority: [AWS_SEC] }),
40
+ WAW033: auditRule("WAW033", "merge-worthy", "guidance", "Solr heap exceeds Fargate task memory", "Lower SOLR_HEAP or raise task memory.", { category: "correctness" }),
41
+ WAW034: auditRule("WAW034", "report-only", "guidance", "Fargate Solr task under-provisioned", "Allocate >= 2048MB for the Solr task.", { category: "best-practice" }),
42
+ WAW035: auditRule("WAW035", "report-only", "guidance", "Solr container missing nofile ulimit", "Set a nofile ulimit >= 65535.", { category: "best-practice" }),
43
+ WAW036: auditRule("WAW036", "merge-worthy", "guidance", "Non-ASCII characters in resource properties", "Remove non-ASCII characters rejected at changeset time.", { category: "correctness" }),
44
+ WAW037: auditRule("WAW037", "merge-worthy", "guidance", "Null values in resource properties", "Fix the invalid AttrRef producing null property values.", { category: "correctness" }),
45
+ };
@@ -0,0 +1,67 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import {
3
+ flociRunCommand,
4
+ flociRmCommand,
5
+ flociExistsCommand,
6
+ flociHealthUrl,
7
+ flociEnv,
8
+ isFlociReady,
9
+ } from "./floci";
10
+
11
+ describe("flociRunCommand (#704)", () => {
12
+ test("minimal — defaults name/port/image, no docker socket", () => {
13
+ expect(flociRunCommand({})).toBe(
14
+ "docker run -d --rm --name chant-floci -p 4566:4566 floci/floci:latest",
15
+ );
16
+ });
17
+
18
+ test("custom name, port, and image", () => {
19
+ const cmd = flociRunCommand({ name: "floci-e2e", port: 4599, image: "floci/floci:1.5.30" });
20
+ expect(cmd).toContain("--name floci-e2e");
21
+ expect(cmd).toContain("-p 4599:4566");
22
+ expect(cmd.endsWith("floci/floci:1.5.30")).toBe(true);
23
+ });
24
+
25
+ test("dockerSocket mounts the socket before the image (for the ECR backing registry)", () => {
26
+ const cmd = flociRunCommand({ dockerSocket: true });
27
+ expect(cmd).toContain("-v /var/run/docker.sock:/var/run/docker.sock");
28
+ // socket flag precedes the trailing image arg
29
+ expect(cmd.indexOf("/var/run/docker.sock")).toBeLessThan(cmd.indexOf("floci/floci"));
30
+ });
31
+ });
32
+
33
+ describe("flociRmCommand / flociExistsCommand (#704)", () => {
34
+ test("rm force-removes the container", () => {
35
+ expect(flociRmCommand("chant-floci")).toBe("docker rm -f chant-floci");
36
+ });
37
+
38
+ test("exists checks a running container by name", () => {
39
+ expect(flociExistsCommand("chant-floci")).toBe("docker ps -q -f name=chant-floci");
40
+ });
41
+ });
42
+
43
+ describe("flociHealthUrl / flociEnv (#704)", () => {
44
+ test("health url targets the localstack-compatible endpoint on the host port", () => {
45
+ expect(flociHealthUrl(4599)).toBe("http://localhost:4599/_localstack/health");
46
+ });
47
+
48
+ test("env points the aws CLI at the endpoint with test creds", () => {
49
+ expect(flociEnv(4566, "us-east-1")).toEqual({
50
+ AWS_ENDPOINT_URL: "http://localhost:4566",
51
+ AWS_ACCESS_KEY_ID: "test",
52
+ AWS_SECRET_ACCESS_KEY: "test",
53
+ AWS_REGION: "us-east-1",
54
+ });
55
+ });
56
+ });
57
+
58
+ describe("isFlociReady (#704)", () => {
59
+ test("true when the health body names the required service", () => {
60
+ const body = '{"services": {"s3": "available", "cloudformation": "available"}}';
61
+ expect(isFlociReady(body, "cloudformation")).toBe(true);
62
+ });
63
+
64
+ test("false before the service appears", () => {
65
+ expect(isFlociReady('{"services": {"s3": "available"}}', "cloudformation")).toBe(false);
66
+ });
67
+ });
@@ -0,0 +1,149 @@
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { safeHeartbeat, sleep } from "@intentius/chant/op";
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ const DEFAULT_NAME = "chant-floci";
8
+ const DEFAULT_PORT = 4566;
9
+ const DEFAULT_IMAGE = "floci/floci:latest";
10
+ const DEFAULT_REGION = "us-east-1";
11
+ const DEFAULT_READY_SERVICE = "cloudformation";
12
+
13
+ export interface FlociUpArgs {
14
+ /** Container name. Default: `chant-floci`. */
15
+ name?: string;
16
+ /** Host port mapped to the emulator's `:4566`. Default: `4566`. */
17
+ port?: number;
18
+ /** Floci image. Default: `floci/floci:latest`. */
19
+ image?: string;
20
+ /** Mount the docker socket — Floci needs it for the ECR backing registry. Default: `false`. */
21
+ dockerSocket?: boolean;
22
+ /** `AWS_REGION` exported for subsequent steps. Default: `us-east-1`. */
23
+ region?: string;
24
+ /** Service to wait for in `/_localstack/health`. Default: `cloudformation`. */
25
+ readyService?: string;
26
+ /** Readiness timeout in ms. Default: `60000`. */
27
+ timeoutMs?: number;
28
+ /** Health poll interval in ms. Default: `2000`. */
29
+ intervalMs?: number;
30
+ }
31
+
32
+ export interface FlociDownArgs {
33
+ /** Container name to remove. Default: `chant-floci`. */
34
+ name?: string;
35
+ }
36
+
37
+ /** `docker ps -q -f name=<name>` — non-empty stdout means the container is running. */
38
+ export function flociExistsCommand(name: string): string {
39
+ return `docker ps -q -f name=${name}`;
40
+ }
41
+
42
+ /** Build the `docker run` command that boots Floci. */
43
+ export function flociRunCommand(args: FlociUpArgs): string {
44
+ const name = args.name ?? DEFAULT_NAME;
45
+ const port = args.port ?? DEFAULT_PORT;
46
+ const image = args.image ?? DEFAULT_IMAGE;
47
+ const parts = ["docker", "run", "-d", "--rm", "--name", name, "-p", `${port}:4566`];
48
+ if (args.dockerSocket) parts.push("-v", "/var/run/docker.sock:/var/run/docker.sock");
49
+ parts.push(image);
50
+ return parts.join(" ");
51
+ }
52
+
53
+ /** Build the `docker rm -f` command. */
54
+ export function flociRmCommand(name: string): string {
55
+ return `docker rm -f ${name}`;
56
+ }
57
+
58
+ /** The Floci health endpoint URL for a host port. */
59
+ export function flociHealthUrl(port: number): string {
60
+ return `http://localhost:${port}/_localstack/health`;
61
+ }
62
+
63
+ /** The AWS env vars that point the `aws` CLI / SDK at a local Floci endpoint. */
64
+ export function flociEnv(port: number, region: string): Record<string, string> {
65
+ return {
66
+ AWS_ENDPOINT_URL: `http://localhost:${port}`,
67
+ AWS_ACCESS_KEY_ID: "test",
68
+ AWS_SECRET_ACCESS_KEY: "test",
69
+ AWS_REGION: region,
70
+ };
71
+ }
72
+
73
+ /** True once the health body reports the required service (e.g. `"cloudformation"`). */
74
+ export function isFlociReady(healthBody: string, service: string): boolean {
75
+ return healthBody.includes(`"${service}"`);
76
+ }
77
+
78
+ /**
79
+ * Boot a local Floci AWS emulator in Docker and point subsequent steps at it.
80
+ *
81
+ * Idempotent: reuses a running container of the same name. Waits for the health
82
+ * endpoint to report `readyService`, then sets `AWS_ENDPOINT_URL` + test creds in
83
+ * the process environment so a following `nativeApply`/`cfn-deploy` targets the
84
+ * emulator. Env injection assumes the in-process **local executor**; under a
85
+ * distributed Temporal worker, pass the endpoint explicitly instead.
86
+ *
87
+ * Uses longInfra profile — 20m timeout, heartbeat every poll (the image may pull).
88
+ */
89
+ export async function flociUp(args: FlociUpArgs, signal?: AbortSignal): Promise<{ endpoint: string }> {
90
+ const name = args.name ?? DEFAULT_NAME;
91
+ const port = args.port ?? DEFAULT_PORT;
92
+ const region = args.region ?? DEFAULT_REGION;
93
+ const service = args.readyService ?? DEFAULT_READY_SERVICE;
94
+ const timeoutMs = args.timeoutMs ?? 60_000;
95
+ const intervalMs = args.intervalMs ?? 2_000;
96
+
97
+ let running = false;
98
+ try {
99
+ const { stdout } = await execAsync(flociExistsCommand(name), { signal });
100
+ running = Boolean(stdout.trim());
101
+ } catch {
102
+ // `docker ps` failed — assume not running and try to start it.
103
+ }
104
+
105
+ if (running) {
106
+ console.log(`Floci container "${name}" already running — reusing`);
107
+ } else {
108
+ await execAsync(flociRunCommand({ ...args, name, port }), { signal });
109
+ }
110
+
111
+ const url = flociHealthUrl(port);
112
+ const deadline = Date.now() + timeoutMs;
113
+ let ready = false;
114
+ while (Date.now() < deadline) {
115
+ if (signal?.aborted) throw new Error("flociUp aborted");
116
+ safeHeartbeat({ step: "flociUp", container: name });
117
+ try {
118
+ const { stdout } = await execAsync(`curl -fs ${url}`, { signal });
119
+ if (isFlociReady(stdout, service)) {
120
+ ready = true;
121
+ break;
122
+ }
123
+ } catch {
124
+ // Not up yet (connection refused / non-2xx) — retry.
125
+ }
126
+ await sleep(intervalMs, signal);
127
+ }
128
+ if (!ready) {
129
+ throw new Error(`Floci "${name}" did not become ready within ${timeoutMs}ms`);
130
+ }
131
+
132
+ const env = flociEnv(port, region);
133
+ Object.assign(process.env, env);
134
+ console.log(`Floci ready on ${env.AWS_ENDPOINT_URL} (service: ${service})`);
135
+ return { endpoint: env.AWS_ENDPOINT_URL };
136
+ }
137
+
138
+ /**
139
+ * Stop and remove the local Floci emulator container. A no-op success when the
140
+ * container is already gone. Uses fastIdempotent profile — 5m timeout.
141
+ */
142
+ export async function flociDown(args: FlociDownArgs, signal?: AbortSignal): Promise<void> {
143
+ const name = args.name ?? DEFAULT_NAME;
144
+ try {
145
+ await execAsync(flociRmCommand(name), { signal });
146
+ } catch {
147
+ // Already removed (`--rm` on exit, or never started) — treat as success.
148
+ }
149
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * AWS Op activities — resolved by the core activity registry when a project's
3
+ * `chant.config.ts` lists the `aws` lexicon. Contributes the local Floci AWS
4
+ * emulator lifecycle (`flociUp`/`flociDown`).
5
+ */
6
+ export {
7
+ flociUp,
8
+ flociDown,
9
+ flociRunCommand,
10
+ flociRmCommand,
11
+ flociExistsCommand,
12
+ flociHealthUrl,
13
+ flociEnv,
14
+ isFlociReady,
15
+ } from "./floci";
16
+ export type { FlociUpArgs, FlociDownArgs } from "./floci";
@@ -0,0 +1,18 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { ownershipEntries } from "@intentius/chant/ownership";
3
+ import { AWS_TAG_OWNERSHIP_KEYS } from "./ownership";
4
+
5
+ describe("AWS_TAG_OWNERSHIP_KEYS", () => {
6
+ test("uses AWS colon-form tag keys", () => {
7
+ expect(AWS_TAG_OWNERSHIP_KEYS).toEqual({
8
+ managedBy: "chant:managed-by",
9
+ stack: "chant:stack",
10
+ env: "chant:env",
11
+ });
12
+ });
13
+
14
+ test("stamps the colon keys via the core ownershipEntries helper", () => {
15
+ const e = ownershipEntries(AWS_TAG_OWNERSHIP_KEYS, { stack: "billing", env: "prod" });
16
+ expect(e).toEqual({ "chant:managed-by": "chant", "chant:stack": "billing", "chant:env": "prod" });
17
+ });
18
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The aws lexicon's ownership tag convention. AWS tag keys allow `:`, so
3
+ * chant's markers use the `chant:<name>` form (distinct from the label-based
4
+ * convention core keeps for k8s/gcp). Core owns the generic stamp/detect logic
5
+ * (@intentius/chant/ownership); this is the aws-specific key naming it stamps.
6
+ */
7
+
8
+ import type { ChannelKeys } from "@intentius/chant/ownership";
9
+
10
+ /** AWS tag keys for chant's ownership markers. */
11
+ export const AWS_TAG_OWNERSHIP_KEYS: ChannelKeys = {
12
+ managedBy: "chant:managed-by",
13
+ stack: "chant:stack",
14
+ env: "chant:env",
15
+ };
package/src/plugin.ts CHANGED
@@ -8,6 +8,7 @@ import type { TypeScriptGenerator } from "@intentius/chant/import/generator";
8
8
  import type { CompletionContext, CompletionItem, HoverContext, HoverInfo } from "@intentius/chant/lsp/types";
9
9
  import { discoverLintRules } from "@intentius/chant/lint/discover";
10
10
  import { postSynthChecks as postSynthCheckList } from "./lint/post-synth";
11
+ import { awsAuditCatalog } from "./lint/audit-catalog";
11
12
  import { createSkillsLoader, createDiffTool, createCatalogResource } from "@intentius/chant/lexicon-plugin-helpers";
12
13
  import { readFileSync } from "fs";
13
14
  import { join, dirname } from "path";
@@ -28,6 +29,8 @@ import { awsHover } from "./lsp/hover";
28
29
  export const awsPlugin: LexiconPlugin = {
29
30
  name: "aws",
30
31
  serializer: awsSerializer,
32
+ // Audit rule metadata for this lexicon's WAW* checks (#687).
33
+ auditCatalog: () => awsAuditCatalog,
31
34
 
32
35
  lintRules(): LintRule[] {
33
36
  const rulesDir = join(dirname(fileURLToPath(import.meta.url)), "lint", "rules");