@raindancers/raindancers-crew 0.0.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.
- package/.jsii +10552 -0
- package/.kiro/specs/private-brain-construct-changes/design.md +147 -0
- package/.kiro/specs/private-brain-construct-changes/requirements.md +123 -0
- package/.kiro/specs/private-brain-construct-changes/tasks.md +119 -0
- package/API.md +1821 -0
- package/LICENSE +202 -0
- package/README.md +253 -0
- package/lib/crew-backup-bucket.d.ts +62 -0
- package/lib/crew-backup-bucket.js +108 -0
- package/lib/fargate-crew-base.d.ts +68 -0
- package/lib/fargate-crew-base.js +102 -0
- package/lib/fargate-crew.d.ts +89 -0
- package/lib/fargate-crew.js +200 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +22 -0
- package/lib/remote-crew-instance-props.d.ts +287 -0
- package/lib/remote-crew-instance-props.js +15 -0
- package/lib/remote-crew-instance.d.ts +35 -0
- package/lib/remote-crew-instance.js +309 -0
- package/package.json +138 -0
- package/src/assets/backup.sh +57 -0
- package/src/assets/bootstrap.sh +339 -0
- package/src/assets/restore-from-s3.sh +51 -0
- package/src/crew-backup-bucket.ts +107 -0
- package/src/fargate-crew-base.ts +109 -0
- package/src/fargate-crew.ts +248 -0
- package/src/index.ts +5 -0
- package/src/remote-crew-instance-props.ts +316 -0
- package/src/remote-crew-instance.ts +395 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Design — `@raindancers/raindancers-crew` changes for the 55minutes brain
|
|
2
|
+
|
|
3
|
+
All changes land on **`RemoteCrewInstance`** and its `bootstrap.sh`. Every one is **additive**;
|
|
4
|
+
the no-prop path is unchanged. Files touched:
|
|
5
|
+
`src/remote-crew-instance-props.ts`, `src/remote-crew-instance.ts`, `src/assets/bootstrap.sh`,
|
|
6
|
+
and tests under `test/`.
|
|
7
|
+
|
|
8
|
+
## Prop additions (jsii-clean)
|
|
9
|
+
|
|
10
|
+
Extend `RemoteCrewInstanceProps` with these optional fields:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
/** Assign an IPv6 address to the instance ENI and permit IPv6 egress.
|
|
14
|
+
* Requires the selected subnet(s) to have IPv6 CIDRs — this construct does NOT
|
|
15
|
+
* provision subnet CIDRs, an Egress-Only IGW, or routes (consumer VPC concern).
|
|
16
|
+
* @default false */
|
|
17
|
+
readonly enableIpv6?: boolean;
|
|
18
|
+
|
|
19
|
+
/** Expose the gateway's native-webhook port to ONE source security group only.
|
|
20
|
+
* There is deliberately no CIDR form: the box is never internet-reachable. */
|
|
21
|
+
readonly webhookIngress?: WebhookIngress;
|
|
22
|
+
|
|
23
|
+
/** Secrets Manager ARN of the Bearer token that authenticates POST /api/hooks/agent.
|
|
24
|
+
* Fetched at boot into the gateway env; instance role gets GetSecretValue on this ARN only.
|
|
25
|
+
* Required when webhookIngress is set. @default - webhook disabled (loopback/SSM only) */
|
|
26
|
+
readonly webhookTokenSecretArn?: string;
|
|
27
|
+
|
|
28
|
+
/** 24/7 brain runtime settings, threaded into the bootstrap kirocrew config. */
|
|
29
|
+
readonly crewRuntime?: CrewRuntime;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
New jsii interfaces:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
export interface WebhookIngress {
|
|
36
|
+
/** Imported SG allowed to reach the webhook port (e.g. the ingest Lambda's SG). */
|
|
37
|
+
readonly source: ec2.ISecurityGroup;
|
|
38
|
+
/** Webhook listener TCP port. @default 5477 (dashboard stays on dashboardPort/5476, loopback) */
|
|
39
|
+
readonly port?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CrewRuntime {
|
|
43
|
+
/** Enable Autopilot mode. @default false (gateway default) */
|
|
44
|
+
readonly autopilot?: boolean;
|
|
45
|
+
/** Keep the brain session alive between events (disable idle-session-close). @default false */
|
|
46
|
+
readonly disableIdleClose?: boolean;
|
|
47
|
+
/** Roster config the bootstrap applies (installed agent configs incl. the conductor).
|
|
48
|
+
* Delivered via `source` or an inline manifest — see "Roster provisioning" below. */
|
|
49
|
+
readonly roster?: CrewRosterConfig;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Why a separate webhook port (default 5477), not 5476:** the dashboard port stays loopback-only,
|
|
54
|
+
reached via SSM port-forward, exactly as today (RC3.3). Opening ingress on 5476 would expose the
|
|
55
|
+
dashboard to the source SG. The webhook route binds on its own port so only `POST /api/hooks/agent`
|
|
56
|
+
is reachable from the ingest Lambda.
|
|
57
|
+
|
|
58
|
+
## Security-group changes (`remote-crew-instance.ts`)
|
|
59
|
+
|
|
60
|
+
The construct already creates one SG (`allowAllOutbound: true`, no inbound). Add, conditionally:
|
|
61
|
+
|
|
62
|
+
1. **IPv6 egress** (when `enableIpv6`):
|
|
63
|
+
```ts
|
|
64
|
+
this.securityGroup.addEgressRule(ec2.Peer.anyIpv6(), ec2.Port.allTraffic(), 'IPv6 egress');
|
|
65
|
+
```
|
|
66
|
+
(CDK's `allowAllOutbound` renders IPv4 `0.0.0.0/0` only, so the v6 rule is required — RC1.2.)
|
|
67
|
+
|
|
68
|
+
2. **Webhook ingress from the source SG** (when `webhookIngress`):
|
|
69
|
+
```ts
|
|
70
|
+
const port = props.webhookIngress.port ?? 5477;
|
|
71
|
+
this.securityGroup.addIngressRule(
|
|
72
|
+
ec2.Peer.securityGroupId(props.webhookIngress.source.securityGroupId),
|
|
73
|
+
ec2.Port.tcp(port),
|
|
74
|
+
'Native webhook from ingest Lambda SG only');
|
|
75
|
+
```
|
|
76
|
+
Never a CIDR peer (RC2.2). The existing `allowSshCidr` path is untouched and independent (RC2.3).
|
|
77
|
+
|
|
78
|
+
## ENI / IPv6 (when `enableIpv6`)
|
|
79
|
+
|
|
80
|
+
`RemoteCrewInstance` launches via a launch template / instance. Set IPv6 on the primary ENI:
|
|
81
|
+
- Launch-template network interface `ipv6AddressCount: 1` (or `assignIpv6AddressOnCreation` on the
|
|
82
|
+
ENI), gated on `enableIpv6`.
|
|
83
|
+
- Leave `associatePublicIp` to the existing prop; the 55minutes posture passes
|
|
84
|
+
`associatePublicIp: false` + `enableIpv6: true` → private, dual-stack, IPv6-egress, no public IPv4
|
|
85
|
+
(RC1.4). Add a synth-time note when `enableIpv6` is set but the resolved subnets expose no IPv6
|
|
86
|
+
CIDR (best-effort; the VPC owns the CIDR — RC1.3).
|
|
87
|
+
|
|
88
|
+
## Secrets grant (when `webhookTokenSecretArn`)
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const secret = secretsmanager.Secret.fromSecretCompleteArn(this, 'WebhookToken', arn);
|
|
92
|
+
secret.grantRead(this.role); // GetSecretValue on this one secret ARN only (RC3.2)
|
|
93
|
+
```
|
|
94
|
+
Fail synth if `webhookIngress` is set without `webhookTokenSecretArn` (a reachable webhook with no
|
|
95
|
+
auth is a defect, not a default).
|
|
96
|
+
|
|
97
|
+
## `bootstrap.sh` changes — all guarded, default path unchanged (RC3.4 / RC4.4)
|
|
98
|
+
|
|
99
|
+
Today: `kirocrew setup --agent-only` → `kirocrew gateway`; systemd unit sets `KIROCREW_PORT`,
|
|
100
|
+
`HOME`, `PATH`; health check `curl http://127.0.0.1:$DASHBOARD_PORT/`.
|
|
101
|
+
|
|
102
|
+
Add, only when the corresponding CFN parameter/env is non-empty (passed from the construct as
|
|
103
|
+
userData substitutions):
|
|
104
|
+
|
|
105
|
+
- **Webhook token:** if `WEBHOOK_TOKEN_SECRET_ARN` set → `aws secretsmanager get-secret-value` at
|
|
106
|
+
boot, export the token into the gateway env (e.g. `KIROCREW_HOOK_TOKEN`), and configure the
|
|
107
|
+
gateway to serve `POST /api/hooks/agent` on `WEBHOOK_PORT` (default 5477) bound to the instance's
|
|
108
|
+
routable address (not loopback). Dashboard stays on `KIROCREW_PORT` loopback.
|
|
109
|
+
- **Autopilot:** if enabled → the `kirocrew` config/env flag that turns Autopilot on.
|
|
110
|
+
- **No idle-close:** if enabled → the session-idle-close-disable setting.
|
|
111
|
+
- **Roster:** see below.
|
|
112
|
+
|
|
113
|
+
The token is fetched, never baked. Keep the WaitCondition/health-check gate; extend it to also
|
|
114
|
+
confirm the webhook route answers when the webhook is configured.
|
|
115
|
+
|
|
116
|
+
### Roster provisioning (RC4.3)
|
|
117
|
+
|
|
118
|
+
Two candidate mechanisms — design picks one in tasks:
|
|
119
|
+
- **(a) `source`-delivered manifest:** the roster ships as part of the KiroCrew source tarball /
|
|
120
|
+
repo the box already installs; bootstrap runs `kirocrew agent`-equivalent apply. Zero new transport.
|
|
121
|
+
- **(b) Inline `CrewRosterConfig`:** the construct writes a small manifest to userData/SSM param and
|
|
122
|
+
bootstrap applies it. More prop surface, but self-contained.
|
|
123
|
+
Lean **(a)** unless the consumer needs per-deploy roster variation; keep the prop as the seam.
|
|
124
|
+
|
|
125
|
+
## Backward-compatibility proof
|
|
126
|
+
|
|
127
|
+
- No-prop synth ⇒ identical template (assertion test snapshots the SG rules = none-inbound +
|
|
128
|
+
IPv4-only egress, no IPv6 ENI, no secret grant, and the rendered `bootstrap.sh` = current bytes).
|
|
129
|
+
- Each new capability is behind a truthy prop; the `if`-guards in `bootstrap.sh` mean an unset
|
|
130
|
+
parameter renders the current script.
|
|
131
|
+
|
|
132
|
+
## What stays in the consumer (55minutes), restated
|
|
133
|
+
|
|
134
|
+
EIGW (IPv6 egress route), fck-nat (IPv4 egress), the ingest Lambda + its SG, and the SCP carve-out
|
|
135
|
+
are **not** in this library. `RemoteCrewInstance` accepts the ingest Lambda's SG as
|
|
136
|
+
`webhookIngress.source` and nothing more.
|
|
137
|
+
|
|
138
|
+
## Decisions
|
|
139
|
+
|
|
140
|
+
| Decision | Choice | Why |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| Webhook port | separate `5477`, not the dashboard `5476` | dashboard stays loopback/SSM-only; only the hook route is exposed |
|
|
143
|
+
| Webhook peer | source-SG only, no CIDR form | the box is never internet-reachable by contract |
|
|
144
|
+
| Bearer token | Secrets Manager ARN prop, single-secret grant, fetched at boot | no secret in userData/code |
|
|
145
|
+
| IPv6 routing | out of scope (consumer VPC) | construct takes `IVpc`, provisions no routes/EIGW |
|
|
146
|
+
| Roster transport | prefer `source`-delivered manifest, keep prop as seam | least new surface; self-contained fallback exists |
|
|
147
|
+
| Compatibility | every prop optional, default = today | no migration for existing consumers |
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Requirements — `@raindancers/raindancers-crew` library changes for the 55minutes brain
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The 55minutes cloud orchestrator (a **consumer** of this library) needs the brain box —
|
|
6
|
+
`RemoteCrewInstance` — to run in a **private, dual-stack, IPv6-preferred subnet with no public
|
|
7
|
+
IP**, be woken by the **native KiroCrew webhook** (`POST /api/hooks/agent`, Bearer token) reachable
|
|
8
|
+
**only from the ingest Lambda's security group**, and run as an **always-on conductor-driven crew**
|
|
9
|
+
(Autopilot, no idle-session-close).
|
|
10
|
+
|
|
11
|
+
The current construct is **IPv4- and public-subnet-shaped, SSM-reached, with no inbound app port**.
|
|
12
|
+
This spec is a **change order against the existing construct**, written against its real interface
|
|
13
|
+
(PRs #1–#4 on `raindancers/raindancers-crew`), not a greenfield design. Everything here is
|
|
14
|
+
**additive and backward-compatible**: current consumers (public-subnet default, SSM-only, no
|
|
15
|
+
webhook) keep working with unchanged behaviour.
|
|
16
|
+
|
|
17
|
+
## Baseline — what the construct is today (do not re-derive)
|
|
18
|
+
|
|
19
|
+
`RemoteCrewInstanceProps` (current, verbatim): `vpc` (req), `permissionsBoundaryArn` (req),
|
|
20
|
+
`vpcSubnets` (default one **public** subnet), `instanceType`, `architecture` (default `ARM64`),
|
|
21
|
+
`volumeSizeGb` (60), `associatePublicIp` (**default `true`**), `stackTag` (`kirocrew`),
|
|
22
|
+
`dashboardPort` (5476, **loopback-only via SSM port-forward**), `allowSshCidr` (opt, opens tcp/22
|
|
23
|
+
from an IPv4 CIDR ≤/16), `source` (`CrewSource`), `bootstrapTimeoutMinutes` (25), `backupBucket`,
|
|
24
|
+
`backupSchedule`, `backupPrefix`.
|
|
25
|
+
|
|
26
|
+
Current facts that constrain the design:
|
|
27
|
+
- **One security group**, `allowAllOutbound: true` (⇒ IPv4 `0.0.0.0/0` egress only), **no inbound
|
|
28
|
+
by default**. The only ingress prop is `allowSshCidr` → `Peer.ipv4(cidr)` + `Port.tcp(22)`.
|
|
29
|
+
**No source-SG peer, no app port.**
|
|
30
|
+
- **No IPv6 anywhere** — no `assignIpv6AddressOnCreation`, no IPv6 egress rule, no EIGW awareness.
|
|
31
|
+
- **`bootstrap.sh`** runs `kirocrew setup --agent-only` then `kirocrew gateway`, systemd unit sets
|
|
32
|
+
only `KIROCREW_PORT`/`HOME`/`PATH`, health check hits `http://127.0.0.1:$DASHBOARD_PORT/`.
|
|
33
|
+
**No `POST /api/hooks/agent`, no Bearer token, no Autopilot, no idle-close, no roster.**
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
### RC1 — Dual-stack, IPv6-preferred ENI (additive)
|
|
38
|
+
|
|
39
|
+
- **RC1.1** A new optional prop enables IPv6 on the instance ENI: `enableIpv6?: boolean` (default
|
|
40
|
+
`false` — current consumers unaffected). When `true`, the ENI is assigned an IPv6 address
|
|
41
|
+
(`assignIpv6AddressOnCreation` / launch-template ENI IPv6 count = 1).
|
|
42
|
+
- **RC1.2** When `enableIpv6` is `true`, the construct's security group MUST add an **IPv6 egress
|
|
43
|
+
rule** (`ec2.Peer.anyIpv6()`, all traffic) alongside the existing IPv4 all-outbound, so IPv6
|
|
44
|
+
egress is actually permitted (CDK's `allowAllOutbound` emits IPv4 only).
|
|
45
|
+
- **RC1.3** `enableIpv6` requires the selected subnet(s) to have IPv6 CIDRs; the construct does
|
|
46
|
+
**not** provision subnet IPv6 CIDRs, an Egress-Only IGW, or any route — those are the consumer's
|
|
47
|
+
VPC (`IVpc`) responsibility. A synth-time note/validation should make this contract explicit.
|
|
48
|
+
- **RC1.4** `enableIpv6: true` with `associatePublicIp: false` (the 55minutes posture) MUST be a
|
|
49
|
+
valid, tested combination: a private dual-stack instance with IPv6 egress and no public IPv4.
|
|
50
|
+
|
|
51
|
+
### RC2 — Webhook ingress from a source security group (additive)
|
|
52
|
+
|
|
53
|
+
- **RC2.1** A new optional prop exposes the gateway's webhook port to a **specific source security
|
|
54
|
+
group** only: `webhookIngress?: { source: ec2.ISecurityGroup; port?: number }`. When set, the
|
|
55
|
+
construct's SG adds `addIngressRule(Peer.securityGroupId(source SG id), Port.tcp(port))`.
|
|
56
|
+
- **RC2.2** `port` defaults to the gateway's webhook listener port (see RC3.1). No wider peer is
|
|
57
|
+
ever allowed for the webhook — **no CIDR form** of this prop; it is source-SG-only by construction
|
|
58
|
+
(the whole point is that the box is never internet-reachable).
|
|
59
|
+
- **RC2.3** `webhookIngress` is independent of `allowSshCidr` — both, either, or neither may be set.
|
|
60
|
+
When neither any ingress prop is set, the SG remains no-inbound (unchanged default).
|
|
61
|
+
- **RC2.4** The construct MUST expose its security group (already does, `this.securityGroup`) so the
|
|
62
|
+
consumer can also reference it from the ingest Lambda side if needed; and MUST accept the source
|
|
63
|
+
SG as an `ISecurityGroup` (imported), not require creating it.
|
|
64
|
+
|
|
65
|
+
### RC3 — Native webhook wake + Bearer token in bootstrap (additive)
|
|
66
|
+
|
|
67
|
+
- **RC3.1** A new optional prop configures the gateway to serve the native webhook on a routable
|
|
68
|
+
bind (not loopback-only) when webhook ingress is in use: the gateway listens for
|
|
69
|
+
`POST /api/hooks/agent`. The listener port is what RC2.2 defaults to.
|
|
70
|
+
- **RC3.2** The Bearer token that authenticates the webhook is delivered as a **Secrets Manager
|
|
71
|
+
secret ARN** prop (e.g. `webhookTokenSecretArn?: string`). `bootstrap.sh` fetches it at boot and
|
|
72
|
+
sets it in the gateway's environment. **No token in userData, env literals, or code.** The
|
|
73
|
+
instance role is granted `secretsmanager:GetSecretValue` on **that one secret ARN only**.
|
|
74
|
+
- **RC3.3** The dashboard port (5476) remains **loopback-only via SSM** as today — RC3.1 opens only
|
|
75
|
+
the webhook route on its own port, not the dashboard.
|
|
76
|
+
- **RC3.4** When no webhook props are set, `bootstrap.sh` behaviour is byte-for-byte unchanged
|
|
77
|
+
(loopback dashboard, SSM-only, no token) — backward compatibility is a hard requirement.
|
|
78
|
+
|
|
79
|
+
### RC4 — Always-on crew runtime: Autopilot, no idle-close, conductor roster (additive)
|
|
80
|
+
|
|
81
|
+
- **RC4.1** A new optional prop enables **Autopilot mode** on the hosted crew, threaded into the
|
|
82
|
+
bootstrap `kirocrew` configuration/env.
|
|
83
|
+
- **RC4.2** A new optional prop disables **idle-session-close** so the 24/7 brain is not torn down
|
|
84
|
+
between events.
|
|
85
|
+
- **RC4.3** The construct supports provisioning a **crew-member / conductor roster** at boot (the
|
|
86
|
+
installed agent configs, including the conductor). Shape TBD in design: a prop carrying roster
|
|
87
|
+
config, or a documented `source`-delivered config the bootstrap applies. This is the R6.8 seam
|
|
88
|
+
from the 55minutes spec.
|
|
89
|
+
- **RC4.4** All of RC4 are optional and default to the current gateway defaults; omitting them
|
|
90
|
+
reproduces today's `kirocrew setup --agent-only` + `kirocrew gateway` behaviour exactly.
|
|
91
|
+
|
|
92
|
+
### RC5 — Out of scope for this library (stated so the consumer owns them)
|
|
93
|
+
|
|
94
|
+
- **RC5.1** **Egress-Only Internet Gateway** (IPv6 egress route) — a VPC/route-table resource. The
|
|
95
|
+
construct consumes an `IVpc` and provisions no routing. The 55minutes app / its VPC construct owns
|
|
96
|
+
it.
|
|
97
|
+
- **RC5.2** **fck-nat** (single-AZ IPv4 egress) — a consumer VPC instance + route concern. Not a
|
|
98
|
+
`RemoteCrewInstance` prop.
|
|
99
|
+
- **RC5.3** **The ingest Lambda and its security group** — live in the consumer app (55minutes
|
|
100
|
+
`CrewIngress`). `RemoteCrewInstance` only *accepts* that SG as `webhookIngress.source` (RC2.1).
|
|
101
|
+
|
|
102
|
+
## Non-functional requirements
|
|
103
|
+
|
|
104
|
+
- **Backward compatibility (hard):** every new prop is optional with a default that reproduces
|
|
105
|
+
current behaviour. `test/` must assert the no-prop synth is unchanged.
|
|
106
|
+
- **Security:** webhook ingress is source-SG-only (never a CIDR); the Bearer token is a Secrets
|
|
107
|
+
Manager ARN with a single-secret grant; the dashboard stays loopback/SSM-only; the permissions
|
|
108
|
+
boundary stays required.
|
|
109
|
+
- **jsii-clean:** new prop types must be jsii-compatible (interfaces/enums, no TS-only constructs) —
|
|
110
|
+
the library publishes via jsii (`~5.9.0`, cdk `2.260.0`).
|
|
111
|
+
- **Testability:** each requirement maps to a CDK assertion test (SG rules, ENI IPv6, secret grant)
|
|
112
|
+
and/or a bootstrap-rendering test.
|
|
113
|
+
|
|
114
|
+
## Definition of done
|
|
115
|
+
|
|
116
|
+
- `RemoteCrewInstance` accepts and correctly wires: `enableIpv6`, `webhookIngress`,
|
|
117
|
+
`webhookTokenSecretArn` (+ webhook listener config), and the Autopilot / no-idle-close / roster
|
|
118
|
+
props — all additive, all optional.
|
|
119
|
+
- The **private dual-stack + no-public-IP + source-SG webhook + native-webhook-wake** posture the
|
|
120
|
+
55minutes brain needs is achievable purely from props, with EIGW/fck-nat/ingest-Lambda left to the
|
|
121
|
+
consumer.
|
|
122
|
+
- No-prop synth is proven byte-unchanged; every new capability has a test.
|
|
123
|
+
- README usage gains a "private dual-stack brain" example.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Tasks — `@raindancers/raindancers-crew` changes for the 55minutes brain
|
|
2
|
+
|
|
3
|
+
Ordered, checkable. Each group is one reviewable PR against `raindancers/raindancers-crew`,
|
|
4
|
+
additive and backward-compatible. All work lands on `RemoteCrewInstance` + `bootstrap.sh` + tests.
|
|
5
|
+
`[ ]` not started. Do groups in order; RC2/RC3 depend on RC-props being in place.
|
|
6
|
+
|
|
7
|
+
**Repo facts:** jsii `AwsCdkConstructLibrary`, cdk `2.260.0`, jsii `~5.9.0`, default branch `main`,
|
|
8
|
+
`src/assets/**` force-bundled. Build/test: `npx projen build` (synth + compile + eslint + jest).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## RC0. Baseline lock (no code) — PR: n/a (do first, in the spec)
|
|
13
|
+
|
|
14
|
+
- [ ] RC0.1 Snapshot the current no-prop synth of `RemoteCrewInstance` into a golden test fixture
|
|
15
|
+
**before** any change, so every later group can assert "no-prop template unchanged". (NFR)
|
|
16
|
+
- [ ] RC0.2 Snapshot the current rendered `bootstrap.sh` bytes as a fixture for the same reason.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## RC1. Dual-stack IPv6 ENI — PR: `feat/rci-ipv6`
|
|
21
|
+
|
|
22
|
+
- [ ] RC1.1 Add `enableIpv6?: boolean` (default `false`) to `RemoteCrewInstanceProps`. (RC1.1)
|
|
23
|
+
- [ ] RC1.2 In `remote-crew-instance.ts`, when `enableIpv6`: set the primary ENI's
|
|
24
|
+
`ipv6AddressCount: 1` (launch template network interface / `assignIpv6AddressOnCreation`). (RC1.1)
|
|
25
|
+
- [ ] RC1.3 Add the IPv6 egress SG rule (`Peer.anyIpv6()`, `Port.allTraffic()`) when `enableIpv6`,
|
|
26
|
+
since `allowAllOutbound` emits IPv4-only. (RC1.2)
|
|
27
|
+
- [ ] RC1.4 Synth-time best-effort note when `enableIpv6` is set but the resolved subnets expose no
|
|
28
|
+
IPv6 CIDR (the VPC owns the CIDR/EIGW — not this construct). (RC1.3)
|
|
29
|
+
- [ ] RC1.5 Tests: assertion test that `enableIpv6: true` adds an IPv6 address to the ENI and an
|
|
30
|
+
IPv6 egress rule; that `enableIpv6: true` + `associatePublicIp: false` synths a private
|
|
31
|
+
dual-stack instance with no public IPv4; and that the **no-prop golden (RC0.1) is unchanged**. (RC1.4, NFR)
|
|
32
|
+
|
|
33
|
+
**Done when:** a consumer can request a private, dual-stack, IPv6-egress, no-public-IP instance
|
|
34
|
+
purely via `enableIpv6: true` + `associatePublicIp: false` + private `vpcSubnets`, proven by synth.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## RC2. Source-SG webhook ingress — PR: `feat/rci-webhook-ingress`
|
|
39
|
+
|
|
40
|
+
- [ ] RC2.1 Add the `WebhookIngress` jsii interface (`source: ec2.ISecurityGroup`, `port?: number`
|
|
41
|
+
default 5477) and the `webhookIngress?: WebhookIngress` prop. (RC2.1, RC2.2)
|
|
42
|
+
- [ ] RC2.2 When set, add `securityGroup.addIngressRule(Peer.securityGroupId(source.securityGroupId),
|
|
43
|
+
Port.tcp(port), ...)`. **No CIDR form.** Leave `allowSshCidr` path untouched. (RC2.1, RC2.3)
|
|
44
|
+
- [ ] RC2.3 Confirm/keep `this.securityGroup` exported so the consumer can reference it. (RC2.4)
|
|
45
|
+
- [ ] RC2.4 Tests: assertion test that `webhookIngress` adds exactly one ingress rule, peer =
|
|
46
|
+
source SG id, port = 5477 (or override), and **no** `0.0.0.0/0`/`::/0` ingress ever appears;
|
|
47
|
+
that omitting it leaves the SG no-inbound; golden unchanged. (RC2.2, NFR)
|
|
48
|
+
|
|
49
|
+
**Done when:** the webhook port is reachable only from the named source SG, and the default stays
|
|
50
|
+
no-inbound.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## RC3. Native webhook wake + Bearer token — PR: `feat/rci-webhook-runtime`
|
|
55
|
+
|
|
56
|
+
- [ ] RC3.1 Add `webhookTokenSecretArn?: string`. Synth-fail if `webhookIngress` is set without it
|
|
57
|
+
(reachable webhook must have auth). (RC3.2, design guard)
|
|
58
|
+
- [ ] RC3.2 Grant `secret.grantRead(this.role)` via `Secret.fromSecretCompleteArn` — GetSecretValue
|
|
59
|
+
on that one ARN only. (RC3.2)
|
|
60
|
+
- [ ] RC3.3 `bootstrap.sh` (guarded on a non-empty `WEBHOOK_TOKEN_SECRET_ARN` substitution): fetch
|
|
61
|
+
the token with `aws secretsmanager get-secret-value`, export into the gateway env
|
|
62
|
+
(`KIROCREW_HOOK_TOKEN`), configure the gateway to serve `POST /api/hooks/agent` on
|
|
63
|
+
`WEBHOOK_PORT` (default 5477) on the routable address. Dashboard stays loopback on
|
|
64
|
+
`KIROCREW_PORT`. (RC3.1, RC3.3)
|
|
65
|
+
- [ ] RC3.4 Extend the WaitCondition/health check to also confirm the webhook route answers when
|
|
66
|
+
configured. (RC3.1)
|
|
67
|
+
- [ ] RC3.5 Tests: assertion test for the single-secret read grant; a bootstrap-render test that
|
|
68
|
+
with no webhook props the script equals the **RC0.2 golden bytes**, and with them present it
|
|
69
|
+
gains exactly the guarded token-fetch + webhook-serve lines (no token literal anywhere). (RC3.4, NFR)
|
|
70
|
+
|
|
71
|
+
**Done when:** the box serves the native webhook on its own port, authenticated by a Secrets-Manager
|
|
72
|
+
Bearer token fetched at boot, with the dashboard still loopback/SSM-only and the no-prop script
|
|
73
|
+
byte-identical.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## RC4. Always-on crew runtime (Autopilot / no-idle-close / roster) — PR: `feat/rci-crew-runtime`
|
|
78
|
+
|
|
79
|
+
- [ ] RC4.1 Add the `CrewRuntime` interface (`autopilot?`, `disableIdleClose?`, `roster?`) and the
|
|
80
|
+
`crewRuntime?: CrewRuntime` prop. (RC4.1, RC4.2, RC4.3)
|
|
81
|
+
- [ ] RC4.2 `bootstrap.sh` (guarded): when `autopilot` → set the Autopilot config/env flag; when
|
|
82
|
+
`disableIdleClose` → set the idle-close-disable setting. Default path unchanged. (RC4.1, RC4.2)
|
|
83
|
+
- [ ] RC4.3 Roster: implement the chosen mechanism (design leans **`source`-delivered manifest** —
|
|
84
|
+
bootstrap applies the roster the installed source carries via a `kirocrew agent`-equivalent
|
|
85
|
+
step). Keep `CrewRosterConfig` as the seam even if the first cut is source-delivered. (RC4.3)
|
|
86
|
+
- [ ] RC4.4 Tests: bootstrap-render tests that each flag appears only when its prop is set, and the
|
|
87
|
+
no-prop render matches the RC0.2 golden; a doc/assertion that the roster mechanism applies the
|
|
88
|
+
conductor + members. (RC4.4, NFR)
|
|
89
|
+
|
|
90
|
+
**Done when:** the hosted crew comes up in Autopilot, never idle-closes, and has its conductor +
|
|
91
|
+
member roster — all opt-in, with the default gateway behaviour intact when omitted.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## RC5. Docs — PR: `docs/rci-private-brain-usage`
|
|
96
|
+
|
|
97
|
+
- [ ] RC5.1 README: add a "Private dual-stack brain (55minutes posture)" usage example composing
|
|
98
|
+
`enableIpv6`, `associatePublicIp: false`, private `vpcSubnets`, `webhookIngress` (with an
|
|
99
|
+
imported ingest-Lambda SG), `webhookTokenSecretArn`, and `crewRuntime`.
|
|
100
|
+
- [ ] RC5.2 README: explicitly state what stays in the consumer VPC/app (EIGW, fck-nat, ingest
|
|
101
|
+
Lambda + SG, SCP), so a reader does not expect the construct to provision routing. (RC5.1–5.3)
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Out of scope (consumer owns — do NOT add to this library)
|
|
106
|
+
|
|
107
|
+
- **Egress-Only IGW** for IPv6 egress route (VPC/route-table). (RC5.1)
|
|
108
|
+
- **fck-nat** single-AZ IPv4 egress (consumer VPC instance + route). (RC5.2)
|
|
109
|
+
- **The ingest Lambda + its SG** — 55minutes `CrewIngress`; the construct only *accepts* the SG as
|
|
110
|
+
`webhookIngress.source`. (RC5.3)
|
|
111
|
+
|
|
112
|
+
## Definition of done (whole change set)
|
|
113
|
+
|
|
114
|
+
- `enableIpv6`, `webhookIngress`, `webhookTokenSecretArn`, `crewRuntime` all land as optional props,
|
|
115
|
+
each behind a test, each backward-compatible.
|
|
116
|
+
- No-prop synth (RC0.1) and no-prop `bootstrap.sh` (RC0.2) proven byte-unchanged.
|
|
117
|
+
- The 55minutes brain posture is achievable purely from props; EIGW/fck-nat/ingest-Lambda stay in
|
|
118
|
+
the consumer.
|
|
119
|
+
- `npx projen build` green; README carries the private-brain example.
|