# EC2 instance-command contract, version 1 This package owns the cross-process contract used to direct a command to the node that owns a running EC2 instance. It is not an in-process service interface or the owner of instance state. ## Route and purpose The subject is `ec2.cmd.`. It is request/reply, and the daemon that hosts that instance's QEMU process is the only subscriber. This route exists because lifecycle operations such as start, stop, terminate, volume attach/detach and ENI attach/detach must reach that one process; sending them through the normal daemon queue group could select a different node. `InstanceCommandSubject` constructs a targeted subject. The corresponding subscription and permission pattern is `ec2.cmd.*`: the `*` deliberately matches exactly one final instance-ID token, not an arbitrary descendant. ## Compatibility boundary `EC2InstanceCommand`, its nested data objects, their JSON field names and the subject shape are wire compatibility. Callers and receivers must use this package rather than rebuild those details. `commands_test.go` preserves the version-1 JSON shape and subject form. This package owns shared command payloads and the drain-volume response. It does not define every action's reply body, authorize the request, store EC2 state or implement NATS transport. Those remain responsibilities of the EC2 domain and its runtime composition. Any incompatible subject or payload change requires an explicit versioned contract or an approved compatibility plan; it is not a package refactor. ## Default VPC request `EnsureDefaultVpcSubject` (`ec2.EnsureDefaultVpc`) is the request/reply route on which the gateway's account creation asks a daemon in the `spinifex-workers` queue group to build the account's default VPC. Only the subject is owned here today. The request (`account_id`) and reply (`vpc_id`, `error`) bodies are still declared as anonymous structs on each side; moving them into this package is a contract change that needs its own characterization first. ## Platform inventory `PlatformInventorySubject` (`ec2.v1.PlatformInventory`) is a request/reply route on which a daemon in the `spinifex-workers` queue group reports one account's key pairs, placement groups, launch templates and their `ec2-tags` entries from the EC2 owner stores. EC2 owns it: `domains/ec2/accountinventory` produces the reply and the daemon serves it; the only consumer today is `accountteardown`, which calls it only on an explicit operator dry run (`spx admin account delete --dry-run` or the admin API `DryRun`) and only logs a comparison with its legacy listings; no teardown calls it. The request names the account in its body (`account_id`, a 12-digit ID) and may carry an opaque `correlation_ref` that is echoed for logs. The daemon ignores `X-Account-ID` for this subject. Neither field grants anything: the subject is read-only, writes to no store, and its reply is never authority to delete. The reply has one `kinds` entry per kind (`key-pair`, `placement-group`, `launch-template`, `tag-index`) with a status and scan start and end times. The times are zero when no owner scan ran: a consumer-synthesised `scan-incomplete` report for no responder, a timeout or a transport failure carries zero timestamps and must not be read as a scan. `observed-clean-at-scan-time` means every store for that kind was read completely and held nothing at scan time; writers are not fenced, so it is a snapshot and never evidence of absence. `items` carry only a resource ID, kind and disposition (`live`, `dependency-held`, or tag-index `residue`), which are the identities the per-kind Describe subjects already return. A `dependency-held` placement group does not name its member instances, which may be stale and are not exposed by the existing subjects. Records no existing subject exposes (public-key objects without metadata, launch-template version bodies and name-index entries without a header, and every unreadable record) appear only as aggregate `counts` per kind, disposition and class. The reply never carries an object key, raw KV key, key or template name, tag key or value, public-key bytes, launch-template data, user data, record body or store error text; `incomplete_reasons` are fixed categories. ### Compatibility boundary The subject name carries the contract major; `platform_inventory_test.go` preserves the version-1 JSON shape and wire vocabulary. Producer and consumer are in the coordinated Spinifex release, so the window is N and N-1. A daemon that predates the subject has no subscription, so the request gets no responder; the consumer treats no responder, a timeout, an error reply, an undecodable reply, a reply for another account, and a missing, repeated or unknown-status kind as `scan-incomplete`, never clean. An incompatible change to the subject, fields or vocabulary needs a new major.