docs: record RDS DB subnet group lifecycle contract

This commit is contained in:
joshsiv-mulga
2026-10-08 09:46:12 +10:00
parent 01276bf29d
commit 56384f9046
2 changed files with 39 additions and 1 deletions
+2 -1
View File
@@ -288,6 +288,7 @@ while the branch is in flight; they do not describe the umbrella repository's
| `b81ae4eb4` | `spinifex/gateway` (service served/action/error-protocol selection, overlap check) | `spinifex/ingress/aws/dispatch` (`Selector`, `Legacy`) | ADR-0002 phase 4 slice 2: one selector decides whether a service is served, its throttle/trace/audit action, its error protocol, its registered entry and the startup overlap; a registered service always wins and a resolver-less registration does not fall back to legacy action resolution. The gateway supplies its legacy tables through the three-method `dispatch.Legacy` interface, so ingress imports nothing from gateway; legacy dispatch, the Bedrock sub-service mapping and invocation building (which read gateway request-context keys) stay in gateway. A 160-case golden capture through the real SigV4, trace and `Request` chain with live test NATS and the ECR registration was identical before and after; mutating `Served`, `ResolveAction` and `Validate` each failed tests. Only startup-visible change: the overlap error now begins `dispatch:` and names the first overlap in sorted order. Full preflight passed. |
| `4b0a4dd6b` | — | — | ACM characterization before registration: full-pipeline 400 MissingAction/InvalidAction envelopes, a 200 `ListCertificates` against a live NATS responder, dispatch and telemetry target-parser agreement, authorization before the NATS call (subject counter stays zero), 413 before dispatch, 503 when NATS is down (ACM's own nil-connection check is unreachable over HTTP), the action reaching throttle, trace and audit, and the nine-action inventory. Mutating the parser, the authorization call and the action table each failed tests. Test-only; full preflight passed. |
| `fdaf589ca` | `spinifex/gateway` (`ACM_Request`, `acmActions`, `acmActionFromTarget`, legacy `acm` entries) | `spinifex/domains/acm/awsapi` (`NewRegistration`, `OperationInventory`) | ACM registers its AWS JSON 1.1 control plane from its domain, wired only by the awsgw role; the dispatcher reproduces the old order (missing/unknown action, `InternalError` without an account, bounded body, resource ARNs, authorization, then nil-NATS and the action). The S4 composition invariant now covers every registered domain `awsapi` with the same named coverage-tool exception. Coverage output was byte-identical and the ACM integration conformance summary was identical at the parent and after. Mutating authorization order, the target parser, the inventory and gateway imports each failed tests. Temporary edge: `Deps.NATS` is a raw `*nats.Conn` because every ACM action function takes one. Full preflight passed. |
| `01276bf29` | — | — | RDS DB subnet group characterization before extraction (ADR-0003 S1, ADR-0004): repeat delete reports not found, name scoped to the account, modify never moves a placed instance, snapshots do not block delete, restore rejects an unknown group, tags reached through the ARN and gone with the group, persisted record field names. Present contract and gaps in `docs/package-boundary/rds-db-subnet-group-lifecycle.md`; no conformance claimed. |
The EC2 contract row is intentionally different from the directory moves: it creates
a compatibility boundary. `ec2.cmd.*` retains its deployed one-token NATS
@@ -411,7 +412,7 @@ Blocked dependencies recorded, not designed around:
Findings to resolve before extraction:
- The branch is 59 commits behind `dev`, including `ModifyDBSubnetGroup` (#1152) and the EKS stub unregistration (#1172), so the subnet-group inventory must be redone after `dev` is merged in.
- `dev` is merged in (`9e77181f2`), including `ModifyDBSubnetGroup` (#1152); the subnet-group inventory is superseded by its lifecycle contract, `docs/package-boundary/rds-db-subnet-group-lifecycle.md`.
- The EKS control-plane template persists the OIDC signing key and k3s join token; see the `handlers/eks` potential security risk in the dependency-rule debt.
## Recording rule
@@ -0,0 +1,37 @@
# RDS DB subnet group: lifecycle contract
Present contract under ADR-0003 S1, recorded before the resource moves.
It describes the code as it stands, so it claims no conformance; the gaps below are observed behaviour, not targets.
Paths are relative to `spinifex/` unless they start with `tests/`.
## Scope
A DB subnet group has no realization.
Create, modify and delete each complete synchronously within the request, and nothing outside the record has to be provisioned or cleaned up.
The group's one external effect is indirect: instance create and restore read it to choose the endpoint subnet.
## Contract
| Item | Present behaviour | Evidence |
|---|---|---|
| Identity | `DBSubnetGroupName`, case-sensitive, immutable, `default` reserved. ARN `arn:aws:rds:{region}:{account}:subgrp:{name}`. | `handlers/rds/subnetgroup_test.go` `TestCreateDBSubnetGroup_RejectsMalformedRequests`, `handlers/rds/arn_test.go` |
| Scope | Account and Region: one record per name in the `rds-account-{account}` bucket. Not AZ-scoped; each member subnet carries the AZ recorded on the EC2 subnet. | `TestDBSubnetGroup_NameIsScopedToTheAccount`, `TestCreateDBSubnetGroup_AcceptsASingleSubnet` |
| Durable owner and record | `handlers/rds/subnetgroup.go` owns `db-subnet-groups/{name}` (`DBSubnetGroupRecord`). `tags.go` also writes the record's `Tags` field through CAS. | `TestDBSubnetGroupRecord_PersistedFieldNames`, `TestDBSubnetGroup_TagsAreReachedThroughItsARN` |
| Desired state | Description, member subnets and their single VPC, and tags. Subnets are validated as the caller through EC2 `DescribeSubnets`: same account, one VPC, at most 20, no duplicates. | `TestCreateDBSubnetGroup_Rejects*`, `TestModifyDBSubnetGroup_RefusesAMoveToAnotherVPC` |
| Generation | None. Create is exclusive (`createJSON`); modify and tag writes retry against the KV revision so a concurrent write is not undone; delete is unconditional. | `TestCreateDBSubnetGroup_RejectsADuplicateName`, `TestTagWrites_ConcurrentAddsAndRemovesDoNotLoseEachOther` |
| Observed state | Not tracked separately. `SubnetGroupStatus` is always `Complete` and every member subnet reports `Active`. | `TestDescribeDBInstances_ReportsTheSubnetGroupTheInstanceWasPlacedFrom` |
| Readiness | Usable as soon as the create returns. | `TestCreateDBInstance_PlacesTheEndpointFromTheNamedSubnetGroup` |
| Idempotency | No client token, as in AWS. A repeated create returns `DBSubnetGroupAlreadyExists`; a repeated delete returns `DBSubnetGroupNotFoundFault`. Internal redelivery of a NATS request re-runs the same checks and gets the same answer. | `TestCreateDBSubnetGroup_RejectsADuplicateName`, `TestDeleteDBSubnetGroup_RepeatReportsNotFound` |
| Dependants | Instance create and restore read the group once, at placement, and store the chosen subnet and VPC on the instance. A later modify never moves a placed instance. | `TestModifyDBSubnetGroup_LeavesAPlacedInstanceWhereItIs`, `TestRestoreDBInstanceFromDBSnapshot_RejectsAnUnknownSubnetGroup`, `TestCreateDBInstance_RejectsAnUnknownSubnetGroup` |
| Deletion ordering | Refused with `InvalidDBSubnetGroupStateFault` while any instance record names the group, including a `deleting` one. Snapshots naming the group do not block it. Account teardown deletes instances at `StageCompute` and member EC2 subnets at `StageNetwork`, both before groups at `StagePlatform` (`accountteardown/reapers_rds.go`, `accountteardown/types.go`). | `TestDeleteDBSubnetGroup_RefusesWhileAnInstanceReferencesIt`, `TestDeleteDBSubnetGroup_IsNotBlockedByASnapshotNamingIt` |
| Restart and interruption | Each action is one KV write, so an interrupted request leaves either the old record or the new one; there is no partial cleanup to resume. | none beyond the single-write code path |
| End-to-end | `tests/e2e/rds/groups_test.go` `TestSubnetAndParameterGroups`; also exercised by `TestConnectivity`, `TestCrossSubnetConnectivity`, `TestSnapshotRestore` and `TestAPIMatrix`. No Terraform apply/destroy evidence for `aws_db_subnet_group`. | |
## Gaps
1. Delete is check-then-delete with no fence against a concurrent instance create or restore that has already read the group (`subnetgroup.go` `DeleteDBSubnetGroup`, `network.go` `resolvePlacement`), so an instance can name a deleted group. Its placement is already stored, so the endpoint keeps working; only the name dangles.
2. The in-use guard reads the instance's private records directly (`instancesUsingGroup`), and placement reads the group's private record directly (`getDBSubnetGroup` in `network.go`). Both cross the resource boundary ADR-0003 S4 forbids.
3. `DescribeDBInstances` projects the group from the instance record alone, so its `DBSubnetGroup` carries no description or subnets (`describe.go`). AWS documents both fields on that response; not verified against live AWS.
4. Member subnets always report `Active`, even after the EC2 subnet is deleted. What AWS reports in that case is not verified.
5. Modify accepts a new subnet set that drops the subnet a placed instance sits in. Whether AWS refuses this is not verified.
6. No ADR-0003 S5 failure or recovery evidence, and no Terraform apply/destroy evidence.