@codyswann/lisa 4.26.4 → 4.27.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/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +10 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +19 -8
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +3 -3
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-analyze-claude-remote/SKILL.md +2 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +10 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-aws/SKILL.md +33 -2
- package/plugins/lisa/scripts/install-remote-agent-aws.mjs +52 -6
- package/plugins/lisa/scripts/remote-agent-aws-setup.sh +319 -15
- package/plugins/lisa/skills/lisa-analyze-claude-remote/SKILL.md +2 -2
- package/plugins/lisa/skills/lisa-implement/SKILL.md +10 -0
- package/plugins/lisa/skills/lisa-setup-remote-aws/SKILL.md +33 -2
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/install-remote-agent-aws.mjs +52 -6
- package/plugins/lisa-agy/scripts/remote-agent-aws-setup.sh +319 -15
- package/plugins/lisa-agy/skills/lisa-analyze-claude-remote/SKILL.md +2 -2
- package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +10 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-aws/SKILL.md +33 -2
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/scripts/install-remote-agent-aws.mjs +52 -6
- package/plugins/lisa-copilot/scripts/remote-agent-aws-setup.sh +319 -15
- package/plugins/lisa-copilot/skills/lisa-analyze-claude-remote/SKILL.md +2 -2
- package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +10 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-aws/SKILL.md +33 -2
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/scripts/install-remote-agent-aws.mjs +52 -6
- package/plugins/lisa-cursor/scripts/remote-agent-aws-setup.sh +319 -15
- package/plugins/lisa-cursor/skills/lisa-analyze-claude-remote/SKILL.md +2 -2
- package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +10 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-aws/SKILL.md +33 -2
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/scripts/install-remote-agent-aws.mjs +52 -6
- package/plugins/src/base/scripts/remote-agent-aws-setup.sh +319 -15
- package/plugins/src/base/skills/lisa-analyze-claude-remote/SKILL.md +2 -2
- package/plugins/src/base/skills/lisa-implement/SKILL.md +10 -0
- package/plugins/src/base/skills/lisa-setup-remote-aws/SKILL.md +33 -2
- package/rails/copy-contents/scripts/lisa-mutation.sh +8 -0
- package/rails/copy-overwrite/lefthook.yml +7 -2
- package/rails/copy-overwrite/scripts/lisa-scratch-run.sh +1030 -0
- package/scripts/two-channel-couplings.json +17 -4
|
@@ -30,14 +30,45 @@ Install the vendor-neutral AWS bootstrap into the current repository.
|
|
|
30
30
|
`.github/workflows/copilot-setup-steps.yml`; and it always writes
|
|
31
31
|
`docs/remote-agent-aws.md`.
|
|
32
32
|
4. Run `bash -n scripts/remote-agent-aws-setup.sh`.
|
|
33
|
-
5.
|
|
33
|
+
5. Confirm the operator knows which project component the profiles will carry.
|
|
34
|
+
The script resolves it from `LISA_AWS_PROFILE_NAMESPACE`, then the bundle's
|
|
35
|
+
`namespace`, then `<owner>-<repository>` from the git origin remote, and
|
|
36
|
+
refuses to write anything when none resolves. Set the variable explicitly
|
|
37
|
+
whenever the setup hook runs outside a checkout.
|
|
38
|
+
6. Never write or request the actual bootstrap bundle in repository files. Tell
|
|
34
39
|
the operator where to obtain it and where to put it — see **Where the bundle
|
|
35
40
|
lives** below. Pass `--secret-name` when the source names it something other
|
|
36
41
|
than `remote-agent-credentials`, so the generated runbook contains the exact
|
|
37
42
|
retrieval command.
|
|
38
|
-
|
|
43
|
+
7. Report the external step that remains: configure that one secret in the
|
|
39
44
|
remote environment and launch a smoke session.
|
|
40
45
|
|
|
46
|
+
## Profile naming and readiness
|
|
47
|
+
|
|
48
|
+
Every profile the script writes is scoped `<project>-agent-<stage>`, and the
|
|
49
|
+
private source profile is `<project>-agent-bootstrap`. Bare stage names state a
|
|
50
|
+
stage but not an owner, so on a workstation carrying more than one organisation
|
|
51
|
+
two bundles declaring the same stages overwrite each other in the shared
|
|
52
|
+
`~/.aws/config` — silently, because nothing about the surviving profile is
|
|
53
|
+
malformed. The name is the only place the owner can be recorded.
|
|
54
|
+
|
|
55
|
+
`default` cannot be namespaced. The script claims it only when nothing else
|
|
56
|
+
owns it, and otherwise stops and names the current owner;
|
|
57
|
+
`LISA_AWS_CLAIM_DEFAULT_PROFILE=1` takes it over deliberately.
|
|
58
|
+
|
|
59
|
+
Readiness is an account check, not a liveness check. `sts:GetCallerIdentity`
|
|
60
|
+
succeeding proves the credentials work, not that they reached the intended
|
|
61
|
+
account — a check that ignores the returned value passes on any credential that
|
|
62
|
+
authenticates anywhere. The script compares the returned account id against the
|
|
63
|
+
account named in the role ARN it configured and refuses to report ready on a
|
|
64
|
+
mismatch, quoting both. Do not substitute a hand-run `get-caller-identity` for
|
|
65
|
+
the script's own exit status.
|
|
66
|
+
|
|
67
|
+
Workstations bootstrapped under the previous unnamespaced convention keep their
|
|
68
|
+
old profiles. They are reported by name on every run and removed only when
|
|
69
|
+
`LISA_AWS_PRUNE_LEGACY_PROFILES=1` is passed, so the migration is explicit in
|
|
70
|
+
both directions.
|
|
71
|
+
|
|
41
72
|
## Where the bundle lives
|
|
42
73
|
|
|
43
74
|
`LISA_AWS_BOOTSTRAP_JSON` is **just another secret**, and it belongs wherever
|
|
@@ -313,14 +313,32 @@ Generated for: \`${platform}\`.
|
|
|
313
313
|
|
|
314
314
|
## Implementation
|
|
315
315
|
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
Profiles are named \`<project>-agent-<stage>\`, not bare \`dev\` or
|
|
317
|
+
\`production\`. The bundle supplies the stage; the project component comes from
|
|
318
|
+
\`LISA_AWS_PROFILE_NAMESPACE\`, then the bundle's \`namespace\`, then
|
|
319
|
+
\`<owner>-<repository>\` from this checkout's git origin. Select an account
|
|
320
|
+
explicitly, for example:
|
|
318
321
|
|
|
319
322
|
\`\`\`bash
|
|
320
|
-
aws --profile staging sts get-caller-identity
|
|
321
|
-
aws --profile production cloudformation describe-stacks
|
|
323
|
+
aws --profile <project>-agent-staging sts get-caller-identity
|
|
324
|
+
aws --profile <project>-agent-production cloudformation describe-stacks
|
|
322
325
|
\`\`\`
|
|
323
326
|
|
|
327
|
+
The setup line prints the exact names it wrote; use those.
|
|
328
|
+
|
|
329
|
+
A bare stage name states a stage but not an owner. Two organisations' bundles
|
|
330
|
+
declaring the same stages write the same names into the same shared
|
|
331
|
+
\`~/.aws/config\`, the last bootstrap silently wins, and every later command
|
|
332
|
+
runs in the other tenant's account while reporting success. Nothing about the
|
|
333
|
+
resulting profile is malformed, so no check on the profile can catch it — only
|
|
334
|
+
a name carrying the owner prevents it.
|
|
335
|
+
|
|
336
|
+
\`default\` is the one name that cannot be namespaced. The setup script writes
|
|
337
|
+
it for this project only when nothing else already owns it; if another Lisa
|
|
338
|
+
project or the operator's own configuration holds it, the script stops and says
|
|
339
|
+
so rather than repointing every unqualified \`aws\` command. Pass
|
|
340
|
+
\`LISA_AWS_CLAIM_DEFAULT_PROFILE=1\` to take it over deliberately.
|
|
341
|
+
|
|
324
342
|
Production and shared profiles are observer-only. Production repair remains a
|
|
325
343
|
human-driven local-workstation operation.
|
|
326
344
|
|
|
@@ -340,8 +358,36 @@ this project requires a named source profile to read the emission; \`shared\` is
|
|
|
340
358
|
not an implicit requirement.
|
|
341
359
|
|
|
342
360
|
Store that exact output as the masked secret \`LISA_AWS_BOOTSTRAP_JSON\` on each
|
|
343
|
-
remote-agent platform
|
|
344
|
-
|
|
361
|
+
remote-agent platform, then run the setup command.
|
|
362
|
+
|
|
363
|
+
## Readiness
|
|
364
|
+
|
|
365
|
+
\`sts:GetCallerIdentity\` succeeding proves only that the credentials work. It
|
|
366
|
+
does not prove they reached the intended account, and a check that never looks
|
|
367
|
+
at what the call returned passes on any credential that authenticates —
|
|
368
|
+
including one that authenticates into somebody else's account.
|
|
369
|
+
|
|
370
|
+
The setup script therefore compares the returned account id against the account
|
|
371
|
+
named in the role ARN it just configured, and refuses to report ready on a
|
|
372
|
+
mismatch, quoting both ids. A bundle may also declare \`expectedAccountId\` per
|
|
373
|
+
stage; a declared id that disagrees with its own role ARN fails before anything
|
|
374
|
+
is written. \`LISA_AWS_EXPECTED_ACCOUNT_ID\` states the same expectation from
|
|
375
|
+
outside the bundle, and \`LISA_AWS_VERIFY_ALL_PROFILES=1\` proves every stage
|
|
376
|
+
rather than only the default.
|
|
377
|
+
|
|
378
|
+
Treat the setup command's own exit status as the readiness gate. Do not
|
|
379
|
+
substitute a hand-run \`aws sts get-caller-identity\`: it answers "do these
|
|
380
|
+
credentials work", which is not the question.
|
|
381
|
+
|
|
382
|
+
## Migration
|
|
383
|
+
|
|
384
|
+
Workstations bootstrapped before profiles were namespaced still carry the old
|
|
385
|
+
bare names. The setup script detects them — by the retired
|
|
386
|
+
\`lisa-remote-agent-bootstrap\` source profile, so an operator's own \`dev\` is
|
|
387
|
+
never mistaken for one of ours — and names them on stderr on every run. They are
|
|
388
|
+
left in place, because deleting a profile somebody may still be using is its own
|
|
389
|
+
silent surprise. Re-run with \`LISA_AWS_PRUNE_LEGACY_PROFILES=1\` to delete the
|
|
390
|
+
ones Lisa wrote; it reports exactly what it removed.
|
|
345
391
|
|
|
346
392
|
## Notes
|
|
347
393
|
|
|
@@ -13,13 +13,40 @@
|
|
|
13
13
|
# contains.
|
|
14
14
|
# Optional plain variables:
|
|
15
15
|
# LISA_REMOTE_AGENT claude | codex | cursor | copilot | agy | opencode
|
|
16
|
-
#
|
|
16
|
+
# LISA_AWS_PROFILE_NAMESPACE
|
|
17
|
+
# The project component every written profile is
|
|
18
|
+
# scoped by. Falls back to the bundle's `namespace`
|
|
19
|
+
# (or `project`), then to `<owner>-<repo>` from the
|
|
20
|
+
# git origin remote. Required when none resolves.
|
|
21
|
+
# LISA_AWS_DEFAULT_PROFILE Stage selected as the default. Defaults to dev,
|
|
22
|
+
# then the first stage in the bundle.
|
|
23
|
+
# LISA_AWS_EXPECTED_ACCOUNT_ID
|
|
24
|
+
# Operator-declared account for the default stage.
|
|
25
|
+
# Checked against the bundle before anything is
|
|
26
|
+
# written, and against the live identity after.
|
|
27
|
+
# LISA_AWS_VERIFY_ALL_PROFILES
|
|
28
|
+
# 1 to prove every stage's account, not just the
|
|
29
|
+
# default's.
|
|
30
|
+
# LISA_AWS_CLAIM_DEFAULT_PROFILE
|
|
31
|
+
# 1 to take over a `[default]` this project does not
|
|
32
|
+
# already own. Off by default: silently repointing
|
|
33
|
+
# `default` is how one tenant's commands end up in
|
|
34
|
+
# another tenant's account.
|
|
35
|
+
# LISA_AWS_PRUNE_LEGACY_PROFILES
|
|
36
|
+
# 1 to delete the unnamespaced profiles an earlier
|
|
37
|
+
# Lisa bootstrap wrote. Off by default; they are
|
|
38
|
+
# reported either way, never silently orphaned.
|
|
17
39
|
# LISA_AWS_SKIP_VERIFY Set to 1 only for an offline image build.
|
|
18
40
|
|
|
19
41
|
set -euo pipefail
|
|
20
42
|
umask 077
|
|
21
43
|
|
|
22
|
-
|
|
44
|
+
# Profiles are scoped `<namespace>-agent-<stage>`, matching the convention every
|
|
45
|
+
# other profile on a multi-organisation workstation already follows. The old
|
|
46
|
+
# name is retained only so an earlier bootstrap's output can be recognised and
|
|
47
|
+
# migrated rather than left behind unexplained.
|
|
48
|
+
BOOTSTRAP_PROFILE_SUFFIX="-agent-bootstrap"
|
|
49
|
+
LEGACY_BOOTSTRAP_PROFILE="lisa-remote-agent-bootstrap"
|
|
23
50
|
AWS_CLI_VERSION="2.36.2"
|
|
24
51
|
|
|
25
52
|
fail() {
|
|
@@ -141,6 +168,84 @@ remove_profile_setting() {
|
|
|
141
168
|
mv "$temporary_file" "$credentials_file"
|
|
142
169
|
}
|
|
143
170
|
|
|
171
|
+
# Delete one whole ini section, header and body, from an AWS shared-config file.
|
|
172
|
+
#
|
|
173
|
+
# Used only for the migration path: profiles a PREVIOUS Lisa bootstrap wrote
|
|
174
|
+
# under the old unnamespaced convention. Sections this script never wrote are
|
|
175
|
+
# never candidates — see legacy_profile_sections, which identifies ours by the
|
|
176
|
+
# retired source_profile name rather than by guessing from the profile name.
|
|
177
|
+
remove_profile_section() {
|
|
178
|
+
local file section temporary_file
|
|
179
|
+
file="$1"
|
|
180
|
+
section="$2"
|
|
181
|
+
[ -f "$file" ] || return 0
|
|
182
|
+
|
|
183
|
+
temporary_file="$(mktemp "${file}.XXXXXX")"
|
|
184
|
+
awk -v section="$section" '
|
|
185
|
+
/^\[[^]]*\][[:space:]]*$/ {
|
|
186
|
+
current = $0
|
|
187
|
+
sub(/^\[[[:space:]]*/, "", current)
|
|
188
|
+
sub(/[[:space:]]*\][[:space:]]*$/, "", current)
|
|
189
|
+
in_section = current == section
|
|
190
|
+
if (in_section) next
|
|
191
|
+
}
|
|
192
|
+
!in_section { print }
|
|
193
|
+
' "$file" >"$temporary_file"
|
|
194
|
+
chmod 600 "$temporary_file"
|
|
195
|
+
mv "$temporary_file" "$file"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# Read one setting out of one ini section, or print nothing.
|
|
199
|
+
profile_setting_value() {
|
|
200
|
+
local file section setting
|
|
201
|
+
file="$1"
|
|
202
|
+
section="$2"
|
|
203
|
+
setting="$3"
|
|
204
|
+
[ -f "$file" ] || return 0
|
|
205
|
+
|
|
206
|
+
awk -v section="$section" -v setting="$setting" '
|
|
207
|
+
/^\[[^]]*\][[:space:]]*$/ {
|
|
208
|
+
current = $0
|
|
209
|
+
sub(/^\[[[:space:]]*/, "", current)
|
|
210
|
+
sub(/[[:space:]]*\][[:space:]]*$/, "", current)
|
|
211
|
+
in_section = current == section
|
|
212
|
+
next
|
|
213
|
+
}
|
|
214
|
+
in_section && $0 ~ "^[[:space:]]*" setting "[[:space:]]*=" {
|
|
215
|
+
sub(/^[^=]*=[[:space:]]*/, "")
|
|
216
|
+
sub(/[[:space:]]*$/, "")
|
|
217
|
+
print
|
|
218
|
+
exit
|
|
219
|
+
}
|
|
220
|
+
' "$file"
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# Every section written by the OLD, unnamespaced convention.
|
|
224
|
+
#
|
|
225
|
+
# Identified by the retired bootstrap profile name, which only Lisa ever wrote,
|
|
226
|
+
# so an operator's own `[profile dev]` can never be mistaken for ours.
|
|
227
|
+
legacy_profile_sections() {
|
|
228
|
+
local file
|
|
229
|
+
file="$1"
|
|
230
|
+
[ -f "$file" ] || return 0
|
|
231
|
+
|
|
232
|
+
awk -v bootstrap="$LEGACY_BOOTSTRAP_PROFILE" '
|
|
233
|
+
/^\[[^]]*\][[:space:]]*$/ {
|
|
234
|
+
current = $0
|
|
235
|
+
sub(/^\[[[:space:]]*/, "", current)
|
|
236
|
+
sub(/[[:space:]]*\][[:space:]]*$/, "", current)
|
|
237
|
+
section = current
|
|
238
|
+
next
|
|
239
|
+
}
|
|
240
|
+
$0 ~ "^[[:space:]]*source_profile[[:space:]]*=" {
|
|
241
|
+
value = $0
|
|
242
|
+
sub(/^[^=]*=[[:space:]]*/, "", value)
|
|
243
|
+
sub(/[[:space:]]*$/, "", value)
|
|
244
|
+
if (value == bootstrap && section != "") print section
|
|
245
|
+
}
|
|
246
|
+
' "$file"
|
|
247
|
+
}
|
|
248
|
+
|
|
144
249
|
sanitize_session_name() {
|
|
145
250
|
local candidate
|
|
146
251
|
candidate="$(printf '%s' "${LISA_REMOTE_AGENT:-remote-agent}" \
|
|
@@ -150,6 +255,125 @@ sanitize_session_name() {
|
|
|
150
255
|
printf '%s\n' "$candidate"
|
|
151
256
|
}
|
|
152
257
|
|
|
258
|
+
# Reduce any project identifier to the safe, stable component of a profile name.
|
|
259
|
+
sanitize_namespace() {
|
|
260
|
+
printf '%s' "$1" \
|
|
261
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
262
|
+
| sed -e 's/[^a-z0-9]\{1,\}/-/g' -e 's/^-*//' -e 's/-*$//' \
|
|
263
|
+
| cut -c1-48
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# `<owner>-<repository>` for the checkout this script was run from.
|
|
267
|
+
#
|
|
268
|
+
# The forge owner is what makes the result distinguishing: two organisations
|
|
269
|
+
# routinely have a repository of the same NAME, which is the collision this
|
|
270
|
+
# whole change exists to remove, and they cannot share an owner.
|
|
271
|
+
namespace_from_git() {
|
|
272
|
+
need git || return 0
|
|
273
|
+
|
|
274
|
+
local url slug
|
|
275
|
+
url="$(git config --get remote.origin.url 2>/dev/null || true)"
|
|
276
|
+
[ -n "$url" ] || return 0
|
|
277
|
+
url="${url%.git}"
|
|
278
|
+
url="${url%/}"
|
|
279
|
+
slug="$(printf '%s' "$url" \
|
|
280
|
+
| sed -e 's#^[a-zA-Z0-9+.-]*://##' -e 's#^[^@/]*@##' -e 's#^[^/:]*[:/]##')"
|
|
281
|
+
printf '%s' "$slug" | tr '/' '-'
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# The project component every profile this script writes is scoped by.
|
|
285
|
+
#
|
|
286
|
+
# Bare stage names (`dev`, `production`) name a stage but not an OWNER. On a
|
|
287
|
+
# workstation carrying more than one organisation two bundles then write the
|
|
288
|
+
# same names into the same shared `~/.aws/config`, the last bootstrap wins, and
|
|
289
|
+
# every later command resolves another tenant's account while reporting success.
|
|
290
|
+
# Nothing about the resulting profile is malformed, so no check on the profile
|
|
291
|
+
# can catch it; only a name that carries the owner can prevent it.
|
|
292
|
+
# Prints `<namespace>\t<where it came from>`. Both halves are returned together
|
|
293
|
+
# because this runs in a command substitution: a variable assigned inside a
|
|
294
|
+
# subshell never reaches the caller, and reporting the wrong provenance is how
|
|
295
|
+
# an operator ends up unable to explain why their profiles are named what they
|
|
296
|
+
# are named.
|
|
297
|
+
resolve_namespace() {
|
|
298
|
+
local candidate sanitized namespace_source
|
|
299
|
+
namespace_source="LISA_AWS_PROFILE_NAMESPACE"
|
|
300
|
+
candidate="${LISA_AWS_PROFILE_NAMESPACE:-}"
|
|
301
|
+
if [ -z "$candidate" ]; then
|
|
302
|
+
namespace_source="the bootstrap bundle"
|
|
303
|
+
candidate="$(printf '%s' "$bootstrap_json" | jq -r '.namespace // .project // empty')"
|
|
304
|
+
fi
|
|
305
|
+
if [ -z "$candidate" ]; then
|
|
306
|
+
namespace_source="git remote origin"
|
|
307
|
+
candidate="$(namespace_from_git)"
|
|
308
|
+
fi
|
|
309
|
+
[ -n "$candidate" ] || fail \
|
|
310
|
+
"cannot determine which project these AWS profiles belong to. Set LISA_AWS_PROFILE_NAMESPACE (for example the repository slug), add a \"namespace\" to the bootstrap bundle, or run this from a checkout with an origin remote. Unnamespaced profiles collide across organisations on a shared workstation."
|
|
311
|
+
|
|
312
|
+
sanitized="$(sanitize_namespace "$candidate")"
|
|
313
|
+
printf '%s' "$sanitized" | grep -qE '^[a-z0-9][a-z0-9-]{1,47}$' || fail \
|
|
314
|
+
"profile namespace from ${namespace_source} (\"${candidate}\") does not reduce to a usable name; set LISA_AWS_PROFILE_NAMESPACE to two or more alphanumeric characters"
|
|
315
|
+
printf '%s\t%s\n' "$sanitized" "$namespace_source"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# Prove which ACCOUNT a profile reaches, not merely that it authenticates.
|
|
319
|
+
#
|
|
320
|
+
# `sts:GetCallerIdentity` succeeding proves the credentials work. It says
|
|
321
|
+
# nothing about whose account they work in, and the old code never looked at
|
|
322
|
+
# what it returned — a lookup whose success was read as the answer. Comparing
|
|
323
|
+
# the returned account against the one named in the role ARN just configured is
|
|
324
|
+
# what turns a liveness probe into an identity check.
|
|
325
|
+
assert_profile_account() {
|
|
326
|
+
local profile expected stage identity actual
|
|
327
|
+
profile="$1"
|
|
328
|
+
expected="$2"
|
|
329
|
+
stage="$3"
|
|
330
|
+
|
|
331
|
+
identity="$(AWS_PAGER="" aws sts get-caller-identity --profile "$profile" --output json)" || fail \
|
|
332
|
+
"sts:GetCallerIdentity failed for profile ${profile} (stage ${stage})"
|
|
333
|
+
actual="$(printf '%s' "$identity" | jq -r '.Account // empty')"
|
|
334
|
+
[ -n "$actual" ] || fail \
|
|
335
|
+
"sts:GetCallerIdentity returned no account id for profile ${profile} (stage ${stage}); cannot prove which account it reached"
|
|
336
|
+
[ "$actual" = "$expected" ] || fail \
|
|
337
|
+
"profile ${profile} (stage ${stage}) resolved to AWS account ${actual}, but this project expects ${expected}. The credentials authenticate; they are not this project's account. Refusing to report ready."
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
# Who currently owns `[default]`.
|
|
341
|
+
#
|
|
342
|
+
# `default` is the one name that cannot be namespaced, so it is the one place
|
|
343
|
+
# two projects still contend. Taking it over silently is the whole defect in
|
|
344
|
+
# miniature: every unqualified `aws` command would change accounts with nothing
|
|
345
|
+
# said. An inert `[default]` holding only region/output is not a claim on it,
|
|
346
|
+
# and a container that ships one must still be able to bootstrap.
|
|
347
|
+
default_profile_owner() {
|
|
348
|
+
local source_value setting
|
|
349
|
+
source_value="$(profile_setting_value "$config_file" "default" "source_profile")"
|
|
350
|
+
if [ "$source_value" = "$BOOTSTRAP_PROFILE" ]; then
|
|
351
|
+
printf 'ours\n'
|
|
352
|
+
return 0
|
|
353
|
+
fi
|
|
354
|
+
if [ "$source_value" = "$LEGACY_BOOTSTRAP_PROFILE" ]; then
|
|
355
|
+
printf 'legacy\n'
|
|
356
|
+
return 0
|
|
357
|
+
fi
|
|
358
|
+
case "$source_value" in
|
|
359
|
+
*"$BOOTSTRAP_PROFILE_SUFFIX")
|
|
360
|
+
printf 'another Lisa project\n'
|
|
361
|
+
return 0
|
|
362
|
+
;;
|
|
363
|
+
esac
|
|
364
|
+
for setting in role_arn credential_process sso_start_url sso_session aws_access_key_id; do
|
|
365
|
+
if [ -n "$(profile_setting_value "$config_file" "default" "$setting")" ]; then
|
|
366
|
+
printf 'this workstation operator\n'
|
|
367
|
+
return 0
|
|
368
|
+
fi
|
|
369
|
+
done
|
|
370
|
+
if [ -n "$(profile_setting_value "$credentials_file" "default" "aws_access_key_id")" ]; then
|
|
371
|
+
printf 'this workstation operator\n'
|
|
372
|
+
return 0
|
|
373
|
+
fi
|
|
374
|
+
printf 'unclaimed\n'
|
|
375
|
+
}
|
|
376
|
+
|
|
153
377
|
install_base_tools
|
|
154
378
|
install_aws_cli
|
|
155
379
|
|
|
@@ -181,21 +405,89 @@ profiles_json="$(printf '%s' "$bootstrap_json" | jq -c '
|
|
|
181
405
|
end
|
|
182
406
|
')"
|
|
183
407
|
|
|
408
|
+
namespace_resolution="$(resolve_namespace)"
|
|
409
|
+
PROFILE_NAMESPACE="${namespace_resolution%%$'\t'*}"
|
|
410
|
+
namespace_source="${namespace_resolution#*$'\t'}"
|
|
411
|
+
BOOTSTRAP_PROFILE="${PROFILE_NAMESPACE}${BOOTSTRAP_PROFILE_SUFFIX}"
|
|
412
|
+
|
|
413
|
+
# The role ARN carries the account the role lives in, so every bundle already
|
|
414
|
+
# declares an expected account per stage whether or not it says so explicitly.
|
|
415
|
+
# Requiring the full ARN shape is what makes that field readable at all.
|
|
184
416
|
printf '%s' "$profiles_json" | jq -e --arg bootstrap_profile "$BOOTSTRAP_PROFILE" '
|
|
185
417
|
type == "object" and length > 0 and
|
|
186
418
|
all(to_entries[];
|
|
187
419
|
(.key != $bootstrap_profile) and
|
|
420
|
+
(.key != "bootstrap") and
|
|
188
421
|
(.key | test("^[A-Za-z0-9_-]+$")) and
|
|
189
|
-
(.value.roleArn | type == "string" and
|
|
422
|
+
(.value.roleArn | type == "string" and test("^arn:aws:iam::[0-9]{12}:role/.+")) and
|
|
190
423
|
(.value.region | type == "string" and length > 0)
|
|
191
424
|
)
|
|
192
|
-
' >/dev/null || fail "bootstrap profiles must map safe names to roleArn and region"
|
|
425
|
+
' >/dev/null || fail "bootstrap profiles must map safe names (not \"bootstrap\") to a full arn:aws:iam::<account>:role/<name> roleArn and a region"
|
|
426
|
+
|
|
427
|
+
# An explicitly declared account that disagrees with the role ARN is a bundle
|
|
428
|
+
# that cannot be believed. Fail here, before anything is written, naming both.
|
|
429
|
+
while IFS="$(printf '\t')" read -r stage declared_account arn_account; do
|
|
430
|
+
[ "$declared_account" = "$arn_account" ] || fail \
|
|
431
|
+
"bundle stage ${stage} declares expectedAccountId ${declared_account} but its roleArn names account ${arn_account}"
|
|
432
|
+
done < <(printf '%s' "$profiles_json" | jq -r '
|
|
433
|
+
to_entries[]
|
|
434
|
+
| select(.value.expectedAccountId != null)
|
|
435
|
+
| [.key, (.value.expectedAccountId | tostring), (.value.roleArn | split(":")[4])]
|
|
436
|
+
| @tsv
|
|
437
|
+
')
|
|
438
|
+
|
|
439
|
+
default_stage="${LISA_AWS_DEFAULT_PROFILE:-dev}"
|
|
440
|
+
if ! printf '%s' "$profiles_json" | jq -e --arg stage "$default_stage" 'has($stage)' >/dev/null; then
|
|
441
|
+
default_stage="$(printf '%s' "$profiles_json" | jq -r 'keys[0]')"
|
|
442
|
+
fi
|
|
443
|
+
|
|
444
|
+
default_role_arn="$(printf '%s' "$profiles_json" | jq -er --arg stage "$default_stage" '.[$stage].roleArn')"
|
|
445
|
+
default_region="$(printf '%s' "$profiles_json" | jq -er --arg stage "$default_stage" '.[$stage].region')"
|
|
446
|
+
default_expected_account="$(printf '%s' "$profiles_json" | jq -er --arg stage "$default_stage" '
|
|
447
|
+
(.[$stage].expectedAccountId // (.[$stage].roleArn | split(":")[4])) | tostring
|
|
448
|
+
')"
|
|
449
|
+
|
|
450
|
+
if [ -n "${LISA_AWS_EXPECTED_ACCOUNT_ID:-}" ] \
|
|
451
|
+
&& [ "$LISA_AWS_EXPECTED_ACCOUNT_ID" != "$default_expected_account" ]; then
|
|
452
|
+
fail "LISA_AWS_EXPECTED_ACCOUNT_ID is ${LISA_AWS_EXPECTED_ACCOUNT_ID} but the bundle's ${default_stage} role is in account ${default_expected_account}"
|
|
453
|
+
fi
|
|
193
454
|
|
|
194
455
|
credentials_file="${AWS_SHARED_CREDENTIALS_FILE:-$HOME/.aws/credentials}"
|
|
195
456
|
config_file="${AWS_CONFIG_FILE:-$HOME/.aws/config}"
|
|
196
457
|
mkdir -p "$HOME/.aws" "$(dirname "$credentials_file")" "$(dirname "$config_file")"
|
|
197
458
|
chmod 700 "$HOME/.aws"
|
|
198
459
|
|
|
460
|
+
profile_names="$(printf '%s' "$profiles_json" \
|
|
461
|
+
| jq -r --arg namespace "$PROFILE_NAMESPACE" 'keys | map($namespace + "-agent-" + .) | join(", ")')"
|
|
462
|
+
|
|
463
|
+
# Migration: the previous convention's profiles are still on this workstation.
|
|
464
|
+
#
|
|
465
|
+
# They are reported rather than removed, because deleting a profile an operator
|
|
466
|
+
# may still be using is its own silent surprise. Pruning is opt-in and says
|
|
467
|
+
# exactly what it removed.
|
|
468
|
+
legacy_sections="$(legacy_profile_sections "$config_file")"
|
|
469
|
+
legacy_names=""
|
|
470
|
+
if [ -n "$legacy_sections" ]; then
|
|
471
|
+
legacy_names="$(printf '%s\n' "$legacy_sections" | sed 's/^profile //' | paste -sd ',' - | sed 's/,/, /g')"
|
|
472
|
+
if [ "${LISA_AWS_PRUNE_LEGACY_PROFILES:-0}" = "1" ]; then
|
|
473
|
+
printf '%s\n' "$legacy_sections" | while IFS= read -r section; do
|
|
474
|
+
[ -n "$section" ] || continue
|
|
475
|
+
remove_profile_section "$config_file" "$section"
|
|
476
|
+
done
|
|
477
|
+
remove_profile_section "$config_file" "profile ${LEGACY_BOOTSTRAP_PROFILE}"
|
|
478
|
+
remove_profile_section "$credentials_file" "$LEGACY_BOOTSTRAP_PROFILE"
|
|
479
|
+
fi
|
|
480
|
+
fi
|
|
481
|
+
|
|
482
|
+
default_owner="$(default_profile_owner)"
|
|
483
|
+
case "$default_owner" in
|
|
484
|
+
ours | legacy | unclaimed) ;;
|
|
485
|
+
*)
|
|
486
|
+
[ "${LISA_AWS_CLAIM_DEFAULT_PROFILE:-0}" = "1" ] || fail \
|
|
487
|
+
"refusing to overwrite the [default] AWS profile in ${config_file}: it belongs to ${default_owner}, not to ${PROFILE_NAMESPACE}. Overwriting it would silently repoint every unqualified aws command at this project's accounts. Use the named profiles instead (${profile_names}), or set LISA_AWS_CLAIM_DEFAULT_PROFILE=1 to take it over deliberately."
|
|
488
|
+
;;
|
|
489
|
+
esac
|
|
490
|
+
|
|
199
491
|
aws configure set aws_access_key_id "$access_key_id" --profile "$BOOTSTRAP_PROFILE"
|
|
200
492
|
aws configure set aws_secret_access_key "$secret_access_key" --profile "$BOOTSTRAP_PROFILE"
|
|
201
493
|
if [ -n "$session_token" ]; then
|
|
@@ -208,7 +500,8 @@ else
|
|
|
208
500
|
fi
|
|
209
501
|
|
|
210
502
|
session_name="$(sanitize_session_name)"
|
|
211
|
-
while IFS
|
|
503
|
+
while IFS="$(printf '\t')" read -r stage role_arn region; do
|
|
504
|
+
profile_name="${PROFILE_NAMESPACE}-agent-${stage}"
|
|
212
505
|
aws configure set role_arn "$role_arn" --profile "$profile_name"
|
|
213
506
|
aws configure set source_profile "$BOOTSTRAP_PROFILE" --profile "$profile_name"
|
|
214
507
|
aws configure set external_id "$external_id" --profile "$profile_name"
|
|
@@ -216,13 +509,7 @@ while IFS=$'\t' read -r profile_name role_arn region; do
|
|
|
216
509
|
aws configure set region "$region" --profile "$profile_name"
|
|
217
510
|
done < <(printf '%s' "$profiles_json" | jq -r 'to_entries[] | [.key, .value.roleArn, .value.region] | @tsv')
|
|
218
511
|
|
|
219
|
-
default_profile="${
|
|
220
|
-
if ! printf '%s' "$profiles_json" | jq -e --arg profile "$default_profile" 'has($profile)' >/dev/null; then
|
|
221
|
-
default_profile="$(printf '%s' "$profiles_json" | jq -r 'keys[0]')"
|
|
222
|
-
fi
|
|
223
|
-
|
|
224
|
-
default_role_arn="$(printf '%s' "$profiles_json" | jq -er --arg profile "$default_profile" '.[$profile].roleArn')"
|
|
225
|
-
default_region="$(printf '%s' "$profiles_json" | jq -er --arg profile "$default_profile" '.[$profile].region')"
|
|
512
|
+
default_profile="${PROFILE_NAMESPACE}-agent-${default_stage}"
|
|
226
513
|
aws configure set role_arn "$default_role_arn" --profile default
|
|
227
514
|
aws configure set source_profile "$BOOTSTRAP_PROFILE" --profile default
|
|
228
515
|
aws configure set external_id "$external_id" --profile default
|
|
@@ -232,8 +519,25 @@ aws configure set region "$default_region" --profile default
|
|
|
232
519
|
chmod 600 "$credentials_file" "$config_file"
|
|
233
520
|
|
|
234
521
|
if [ "${LISA_AWS_SKIP_VERIFY:-0}" != "1" ]; then
|
|
235
|
-
|
|
522
|
+
if [ "${LISA_AWS_VERIFY_ALL_PROFILES:-0}" = "1" ]; then
|
|
523
|
+
while IFS="$(printf '\t')" read -r stage expected_account; do
|
|
524
|
+
assert_profile_account "${PROFILE_NAMESPACE}-agent-${stage}" "$expected_account" "$stage"
|
|
525
|
+
done < <(printf '%s' "$profiles_json" | jq -r '
|
|
526
|
+
to_entries[]
|
|
527
|
+
| [.key, ((.value.expectedAccountId // (.value.roleArn | split(":")[4])) | tostring)]
|
|
528
|
+
| @tsv
|
|
529
|
+
')
|
|
530
|
+
else
|
|
531
|
+
assert_profile_account "$default_profile" "$default_expected_account" "$default_stage"
|
|
532
|
+
fi
|
|
533
|
+
fi
|
|
534
|
+
|
|
535
|
+
if [ -n "$legacy_names" ]; then
|
|
536
|
+
if [ "${LISA_AWS_PRUNE_LEGACY_PROFILES:-0}" = "1" ]; then
|
|
537
|
+
echo "remote-agent-aws-setup: removed unnamespaced profiles left by an earlier bootstrap: ${legacy_names}" >&2
|
|
538
|
+
else
|
|
539
|
+
echo "remote-agent-aws-setup: ${config_file} still contains unnamespaced profiles written by an earlier Lisa bootstrap: ${legacy_names}. They name a stage but no owner, so on a workstation carrying more than one organisation they can resolve to another tenant's account. Review them, or re-run with LISA_AWS_PRUNE_LEGACY_PROFILES=1 to delete the ones Lisa wrote." >&2
|
|
540
|
+
fi
|
|
236
541
|
fi
|
|
237
542
|
|
|
238
|
-
|
|
239
|
-
echo "remote-agent-aws-setup: ready (${session_name}; default=${default_profile}; profiles=${profile_names})"
|
|
543
|
+
echo "remote-agent-aws-setup: ready (${session_name}; namespace=${PROFILE_NAMESPACE} via ${namespace_source}; default=${default_profile} in account ${default_expected_account}; profiles=${profile_names})"
|
|
@@ -369,9 +369,9 @@ so the generator can render acquisition comments into its template:
|
|
|
369
369
|
],
|
|
370
370
|
"awsProfiles": [
|
|
371
371
|
{
|
|
372
|
-
"name": "dev",
|
|
372
|
+
"name": "<project>-agent-dev",
|
|
373
373
|
"roleArn": "arn:aws:iam::<account-id>:role/<role-name>",
|
|
374
|
-
"credentialSource": "
|
|
374
|
+
"credentialSource": "<project>-agent-bootstrap",
|
|
375
375
|
"externalId": "<project-external-id>",
|
|
376
376
|
"region": "us-east-1"
|
|
377
377
|
}
|
|
@@ -346,6 +346,16 @@ Before shutting down the team, execute the Verify flow:
|
|
|
346
346
|
4. Record Implement usage on the originating work artifact via `lisa-usage-accounting` so the work item (or other implementation-owned artifact) gains a direct `lisa-implement` usage entry in the canonical `## Lisa Usage` section. If the parent / child graph is already known, prefer `record_and_rollup` so ancestor totals refresh in the same write; otherwise still write the direct entry, and if runtime usage is unavailable, use `source: unavailable` with nullable token/cost fields instead of skipping the row.
|
|
347
347
|
5. Commit ALL outstanding changes in logical batches on the branch (minus sensitive data/information) — not just changes made by the agent team. This includes pre-existing uncommitted changes that were on the branch before the plan started. Do NOT filter commits to only "task-related" files. If it shows up in git status, it gets committed (unless it contains secrets).
|
|
348
348
|
6. Push the changes - if any pre-push hook blocks you, create a task for the agent team to fix the error/problem whether it was pre-existing or not
|
|
349
|
+
6a. **Review the branch locally BEFORE opening the pull request, every time.** Invoke `lisa-review-local` over the branch diff, triage what it finds under the `convergent-review` rule (same contract as any other reviewer — fix what is valid, refute what is not, on the record), and commit the fixes. Only then proceed to step 7.
|
|
350
|
+
|
|
351
|
+
**Unconditional, not a fallback.** Do not gate this on whether a third-party reviewer is available, rate limited, or skipped. Measured on this repository, 47 of the last 60 merged pull requests carried a review context reporting `SUCCESS` while having read nothing — 29 rate limited, 16 skipped because the vendor does not auto-review public repositories, one skipped on size. A rule that fires only when the vendor is absent has to detect four distinct vacuity strings correctly, forever, and still leaves the vendor being *present but wrong* unguarded. Reviewing always needs no detection at all and makes the third-party review **additive**: its findings still arrive at step 8 and are handled there exactly as today.
|
|
352
|
+
|
|
353
|
+
**Before the pull request, not after it.** Auto-merge is armed at submit time, so the pull request can merge the moment CI goes green — a review that starts after step 7 is racing a latch that is already set. Running here has nothing to race.
|
|
354
|
+
|
|
355
|
+
**A local review is a weaker claim than an independent one, and the report must say so.** The agents reviewing the branch are the same team that wrote it. Record the outcome as *self-reviewed* — never as *reviewed* — and never describe it as satisfying the third-party review check, which is ruleset-required and can only be posted by that vendor's app. Collapsing the two would be the same vacuous-success failure this step exists to remove, one level up.
|
|
356
|
+
|
|
357
|
+
**If no subagent delegation is available in this runtime**, say so explicitly in the completion summary and proceed — but record that no local review was possible. "Could not review" must never be reported as "reviewed and found nothing"; an unavailable check is not a passing one.
|
|
358
|
+
|
|
349
359
|
7. Open a pull request with auto-merge on via `lisa-git-submit-pr`, targeting the **base branch resolved from the ticket's environment** (`target_branch=<base>`, per the branch step above), and including the mandatory work-item ref so the PR can be linked natively to the source issue.
|
|
350
360
|
7a. Confirm two-way linkage before treating PR submission as complete: the PR body/title/branch must reference the work item, and the work item must have either a verified native PR link or the single managed `[lisa-pr-link]` comment. Establish that comment with `node scripts/lisa-work-item.mjs backlink --ref <work_item_ref> --pr-url <url>` rather than posting it by hand — it is idempotent, and it is the same file that enforces the check, so producer and consumer cannot drift.
|
|
351
361
|
8. PR Watch Loop: Drive the PR to merge via the `drive-pr-to-merge` skill — the single source of truth for clearing every blocker (auto-merge with direct-merge fallback, `BEHIND` re-sync, conflict resolution, failing-check fixes, human + bot review-comment handling with thread resolution, stale `CHANGES_REQUESTED` dismissal, and post-merge ancestry verification). `git-submit-pr` already invokes it; if you reach this step with a PR already open, invoke `drive-pr-to-merge` directly with the PR number. For a large review backlog you may fan the code-fix work out to the agent team, but `drive-pr-to-merge` owns the loop and the terminal conditions — do not re-implement them.
|
|
@@ -30,14 +30,45 @@ Install the vendor-neutral AWS bootstrap into the current repository.
|
|
|
30
30
|
`.github/workflows/copilot-setup-steps.yml`; and it always writes
|
|
31
31
|
`docs/remote-agent-aws.md`.
|
|
32
32
|
4. Run `bash -n scripts/remote-agent-aws-setup.sh`.
|
|
33
|
-
5.
|
|
33
|
+
5. Confirm the operator knows which project component the profiles will carry.
|
|
34
|
+
The script resolves it from `LISA_AWS_PROFILE_NAMESPACE`, then the bundle's
|
|
35
|
+
`namespace`, then `<owner>-<repository>` from the git origin remote, and
|
|
36
|
+
refuses to write anything when none resolves. Set the variable explicitly
|
|
37
|
+
whenever the setup hook runs outside a checkout.
|
|
38
|
+
6. Never write or request the actual bootstrap bundle in repository files. Tell
|
|
34
39
|
the operator where to obtain it and where to put it — see **Where the bundle
|
|
35
40
|
lives** below. Pass `--secret-name` when the source names it something other
|
|
36
41
|
than `remote-agent-credentials`, so the generated runbook contains the exact
|
|
37
42
|
retrieval command.
|
|
38
|
-
|
|
43
|
+
7. Report the external step that remains: configure that one secret in the
|
|
39
44
|
remote environment and launch a smoke session.
|
|
40
45
|
|
|
46
|
+
## Profile naming and readiness
|
|
47
|
+
|
|
48
|
+
Every profile the script writes is scoped `<project>-agent-<stage>`, and the
|
|
49
|
+
private source profile is `<project>-agent-bootstrap`. Bare stage names state a
|
|
50
|
+
stage but not an owner, so on a workstation carrying more than one organisation
|
|
51
|
+
two bundles declaring the same stages overwrite each other in the shared
|
|
52
|
+
`~/.aws/config` — silently, because nothing about the surviving profile is
|
|
53
|
+
malformed. The name is the only place the owner can be recorded.
|
|
54
|
+
|
|
55
|
+
`default` cannot be namespaced. The script claims it only when nothing else
|
|
56
|
+
owns it, and otherwise stops and names the current owner;
|
|
57
|
+
`LISA_AWS_CLAIM_DEFAULT_PROFILE=1` takes it over deliberately.
|
|
58
|
+
|
|
59
|
+
Readiness is an account check, not a liveness check. `sts:GetCallerIdentity`
|
|
60
|
+
succeeding proves the credentials work, not that they reached the intended
|
|
61
|
+
account — a check that ignores the returned value passes on any credential that
|
|
62
|
+
authenticates anywhere. The script compares the returned account id against the
|
|
63
|
+
account named in the role ARN it configured and refuses to report ready on a
|
|
64
|
+
mismatch, quoting both. Do not substitute a hand-run `get-caller-identity` for
|
|
65
|
+
the script's own exit status.
|
|
66
|
+
|
|
67
|
+
Workstations bootstrapped under the previous unnamespaced convention keep their
|
|
68
|
+
old profiles. They are reported by name on every run and removed only when
|
|
69
|
+
`LISA_AWS_PRUNE_LEGACY_PROFILES=1` is passed, so the migration is explicit in
|
|
70
|
+
both directions.
|
|
71
|
+
|
|
41
72
|
## Where the bundle lives
|
|
42
73
|
|
|
43
74
|
`LISA_AWS_BOOTSTRAP_JSON` is **just another secret**, and it belongs wherever
|