ecr-scan-verifier 0.1.0 → 0.1.2
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/.claude/hooks/integ-docs-gate.sh +76 -0
- package/.claude/settings.json +18 -0
- package/.claude/skills/integ-test/SKILL.md +500 -0
- package/.claude/skills/verify-integ-docs/SKILL.md +145 -0
- package/.jsii +2 -2
- package/.markgate.yml +25 -0
- package/.mise.toml +9 -0
- package/assets/lambda/Dockerfile +49 -0
- package/lib/ecr-scan-verifier.js +1 -1
- package/lib/sbom-output.js +1 -1
- package/lib/scan-config.js +1 -1
- package/lib/scan-logs-output.js +1 -1
- package/lib/signature-verification.js +1 -1
- package/package.json +1 -1
- package/scripts/integ.sh +221 -0
- package/scripts/verify-integ-docs.sh +129 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# integ-docs-gate.sh
|
|
3
|
+
#
|
|
4
|
+
# PreToolUse hook. Blocks `gh pr create` and `gh pr merge` unless the
|
|
5
|
+
# `integ-docs` markgate marker is fresh for the current content of
|
|
6
|
+
# .claude/skills/integ-test/SKILL.md, test/integ/README.md, and
|
|
7
|
+
# scripts/integ.sh (see .markgate.yml).
|
|
8
|
+
#
|
|
9
|
+
# To unblock: edit the offending files into consistency, then run the
|
|
10
|
+
# /verify-integ-docs skill (which calls scripts/verify-integ-docs.sh
|
|
11
|
+
# and, on pass, `markgate set integ-docs`).
|
|
12
|
+
|
|
13
|
+
set -u
|
|
14
|
+
|
|
15
|
+
# Resolve repo root from script location (.claude/hooks/integ-docs-gate.sh -> repo root).
|
|
16
|
+
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
17
|
+
|
|
18
|
+
cmd=$(jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
|
|
19
|
+
|
|
20
|
+
# Only gate `gh pr create` and `gh pr merge` — any other Bash invocation
|
|
21
|
+
# passes through.
|
|
22
|
+
if ! printf '%s' "$cmd" | grep -qE '\bgh[[:space:]]+pr[[:space:]]+(create|merge)\b'; then
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
cd "$REPO" 2>/dev/null || exit 0
|
|
27
|
+
|
|
28
|
+
# Prefer mise-pinned markgate (.mise.toml) so the schema version matches
|
|
29
|
+
# what the rest of the repo expects. Fall back to PATH binary.
|
|
30
|
+
if command -v mise >/dev/null 2>&1; then
|
|
31
|
+
markgate=(mise exec -- markgate)
|
|
32
|
+
elif command -v markgate >/dev/null 2>&1; then
|
|
33
|
+
markgate=(markgate)
|
|
34
|
+
else
|
|
35
|
+
cat >&2 <<EOF
|
|
36
|
+
Blocked by integ-docs-gate: markgate is not installed.
|
|
37
|
+
|
|
38
|
+
Install via mise (preferred — matches the version pinned in .mise.toml):
|
|
39
|
+
mise install
|
|
40
|
+
|
|
41
|
+
Or install markgate via your packager (Homebrew, ubi, source).
|
|
42
|
+
EOF
|
|
43
|
+
exit 2
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if "${markgate[@]}" verify integ-docs >/dev/null 2>&1; then
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
cat >&2 <<EOF
|
|
51
|
+
Blocked by integ-docs-gate: \`integ-docs\` markgate marker is stale or missing.
|
|
52
|
+
|
|
53
|
+
The skill (.claude/skills/integ-test/SKILL.md) and README
|
|
54
|
+
(test/integ/README.md) document the same workflow for two different
|
|
55
|
+
audiences (Claude / human). If they drift, a human following the README
|
|
56
|
+
hits a different code path than Claude following the skill — and bugs in
|
|
57
|
+
one don't surface in the other.
|
|
58
|
+
|
|
59
|
+
To unblock:
|
|
60
|
+
|
|
61
|
+
1. Run the consistency check:
|
|
62
|
+
./scripts/verify-integ-docs.sh
|
|
63
|
+
|
|
64
|
+
2. Fix any drift it reports.
|
|
65
|
+
|
|
66
|
+
3. Flip the marker:
|
|
67
|
+
mise exec -- markgate set integ-docs
|
|
68
|
+
|
|
69
|
+
(Or invoke the /verify-integ-docs skill, which does steps 1+3.)
|
|
70
|
+
|
|
71
|
+
If you genuinely have an emergency PR that has nothing to do with the
|
|
72
|
+
integ docs (and you confirmed they didn't drift), you can also just run
|
|
73
|
+
\`markgate set integ-docs\` directly — the marker just records that the
|
|
74
|
+
files agree with whatever set-time content they had.
|
|
75
|
+
EOF
|
|
76
|
+
exit 2
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Bash",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": ".claude/hooks/integ-docs-gate.sh",
|
|
10
|
+
"if": "Bash(gh pr create*) or Bash(gh pr merge*)",
|
|
11
|
+
"timeout": 10,
|
|
12
|
+
"statusMessage": "Checking integ-docs marker..."
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: integ-test
|
|
3
|
+
description: Orchestrate ecr-scan-verifier integ tests against real AWS. Handles Inspector enable/disable + propagation waits, scan-on-push toggling, image signing (Notation / Cosign KMS / Cosign Public Key / ECR Managed Signing), and cleanup. Use whenever the user wants to run anything under `test/integ/`.
|
|
4
|
+
argument-hint: "<status|basic|enhanced|signature|signature-notation|signature-cosign-kms|signature-cosign-publickey|signature-ecr-signing|all|cleanup|cleanup-signature> [--snapshot-only] [--no-restore]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# integ-test
|
|
8
|
+
|
|
9
|
+
End-to-end orchestrator for `test/integ/` tests. Replaces the manual checklist in `test/integ/README.md`.
|
|
10
|
+
|
|
11
|
+
Most AWS-mutating primitives live in [`scripts/integ.sh`](../../../scripts/integ.sh) as shell functions. This skill **always sources that file first** and composes the functions per mode — keep primitives there, keep orchestration here.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
. scripts/integ.sh
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
After sourcing you have: `account_id`, `default_region`, `inspector_status`, `inspector_status_all`, `inspector_enable_all`, `inspector_disable_all`, `wait_inspector_status`, `wait_inspector_status_all`, `scan_on_push_set`, `wait_enhanced_engine_warmup`, `enhanced_run_with_retry`, `signer_profile_ensure`, `cosign_minimal_signing_config`, `cleanup_signature_artifacts`, `ecr_signing_setup`, `ecr_signing_teardown`.
|
|
18
|
+
|
|
19
|
+
In a worktree (or any clone without `node_modules`), run `pnpm install --frozen-lockfile` first — the `pnpm integ:*` scripts call `tsc` directly and will fail with `sh: tsc: command not found` otherwise.
|
|
20
|
+
|
|
21
|
+
## Integ suite layout
|
|
22
|
+
|
|
23
|
+
| Directory | Regions exercised | Required state |
|
|
24
|
+
| ----------- | ------------------------------- | ----------------------------------------------------------- |
|
|
25
|
+
| `basic/` | `us-east-1 us-east-2 us-west-2` | Enhanced scanning (Inspector) **DISABLED** in all regions |
|
|
26
|
+
| `enhanced/` | `us-east-1 us-east-2 us-west-2` | Enhanced scanning (Inspector) **ENABLED** in all regions |
|
|
27
|
+
| `signature/`| default region only | Pre-signed images + SSM/KMS prerequisites (state-agnostic) |
|
|
28
|
+
|
|
29
|
+
Signature modes are single-region (resolved from `aws configure get region`). `basic`/`enhanced` always operate on **all three** regions.
|
|
30
|
+
|
|
31
|
+
## Default behavior: real deploy
|
|
32
|
+
|
|
33
|
+
The skill **deploys to AWS by default**. The `pnpm integ:*` scripts wrap `integ-runner`; without `--update-on-failed`, integ-runner does a *snapshot template comparison only* and never touches AWS. With `--update-on-failed` (which the skill passes by default), it actually deploys.
|
|
34
|
+
|
|
35
|
+
If you only want the cheap snapshot comparison, pass `--snapshot-only`. The skill then degenerates to a single `pnpm integ:<mode>` call and **skips every AWS-mutating step** (Inspector toggle, scan-on-push toggle, signing setup).
|
|
36
|
+
|
|
37
|
+
## Arguments
|
|
38
|
+
|
|
39
|
+
- `status` — only triage current AWS state, propose no changes
|
|
40
|
+
- `basic` — switch Inspector if needed, run `pnpm integ:basic:update`, restore
|
|
41
|
+
- `enhanced` — switch Inspector if needed, warm engine, run `pnpm integ:enhanced:update`, restore
|
|
42
|
+
- `signature` — run all four signature sub-modes back-to-back
|
|
43
|
+
- `signature-notation` — Notation (AWS Signer) sign + run `integ.notation`
|
|
44
|
+
- `signature-cosign-kms` — Cosign with KMS sign + run `integ.cosign-kms`
|
|
45
|
+
- `signature-cosign-publickey` — Cosign keypair sign + run `integ.cosign-publickey`
|
|
46
|
+
- `signature-ecr-signing` — ECR Managed Signing setup + run `integ.ecr-signing`
|
|
47
|
+
- `all` — run **everything** (enhanced → all signatures → basic), then auto-run `cleanup`
|
|
48
|
+
- `cleanup` — full teardown: signature artifacts + ECR signing repo + scan-on-push reset. Inspector state left alone (flip via `basic` / `enhanced` if needed). Idempotent.
|
|
49
|
+
- `cleanup-signature` — narrower: only signature artifacts (SSM params, KMS key deletion, local cosign keypair). Subset of `cleanup`.
|
|
50
|
+
|
|
51
|
+
Flags:
|
|
52
|
+
|
|
53
|
+
- `--snapshot-only` — skip every AWS-mutating step; just run `pnpm integ:<mode>` (template comparison only). Mutually exclusive with the orchestration in each mode below.
|
|
54
|
+
- `--no-restore` — skip restoring Inspector state at the end (useful when running multiple modes back-to-back). **Ignored by `all`**, which always restores.
|
|
55
|
+
|
|
56
|
+
### When invoked without arguments
|
|
57
|
+
|
|
58
|
+
Use `AskUserQuestion` to elicit BOTH the mode and the snapshot flag:
|
|
59
|
+
|
|
60
|
+
1. **Target**:
|
|
61
|
+
- `basic`
|
|
62
|
+
- `enhanced`
|
|
63
|
+
- `signature` (all four signature sub-modes)
|
|
64
|
+
- `all` (everything end-to-end with auto-cleanup)
|
|
65
|
+
- `status` (just triage, no test run)
|
|
66
|
+
2. **Run mode**:
|
|
67
|
+
- **Deploy** (default) — real AWS deploy, refresh snapshots
|
|
68
|
+
- **Snapshot-only** — template comparison only, no AWS calls
|
|
69
|
+
|
|
70
|
+
Skip both prompts if the user already gave the target. If they gave the target but not the run-mode, only ask the second question.
|
|
71
|
+
|
|
72
|
+
## `--snapshot-only` (any mode)
|
|
73
|
+
|
|
74
|
+
Single command, no helpers, no setup. The skill exits after this:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pnpm integ # all directories
|
|
78
|
+
pnpm integ:basic # or one directory
|
|
79
|
+
pnpm integ:enhanced
|
|
80
|
+
pnpm integ:signature
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For per-test signature snapshots:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pnpm integ:signature --language javascript --test-regex "integ.notation.js$"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Nothing else in this document applies when `--snapshot-only` is set.
|
|
90
|
+
|
|
91
|
+
## Post-run cleanup (deploy runs only)
|
|
92
|
+
|
|
93
|
+
After ANY deploy-mode invocation (single mode or `all`), the skill must:
|
|
94
|
+
|
|
95
|
+
1. Confirm Inspector state has been restored as documented in the mode (or, with `--no-restore`, explicitly report what was left as-is).
|
|
96
|
+
2. For `all` and any `signature-*` deploy run, finish with `cleanup_signature_artifacts` from `scripts/integ.sh`. Single `signature-*` runs leave SSM/KMS in place by default so the user can chain modes — call cleanup at the very end of the session.
|
|
97
|
+
3. Print the full reporting summary (see [Reporting](#reporting)) — never end silently.
|
|
98
|
+
|
|
99
|
+
## Common preamble (deploy runs)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
. scripts/integ.sh
|
|
103
|
+
ACCOUNT="$(account_id)"
|
|
104
|
+
ORIGINAL_STATE_EAST1="$(inspector_status us-east-1)"
|
|
105
|
+
ORIGINAL_STATE_EAST2="$(inspector_status us-east-2)"
|
|
106
|
+
ORIGINAL_STATE_WEST2="$(inspector_status us-west-2)"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If the three states differ, surface it — that itself is worth flagging before running anything.
|
|
110
|
+
|
|
111
|
+
Build the Lambda once up front (every `pnpm integ:*` script chains this, but doing it once avoids repeating across modes):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pnpm tsc -p tsconfig.dev.json
|
|
115
|
+
(cd assets/lambda && pnpm install --frozen-lockfile && pnpm build)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Mode: `status`
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
inspector_status_all
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Then recommend:
|
|
125
|
+
|
|
126
|
+
- All ENABLED → `enhanced/` is ready. `basic/` requires a disable cycle.
|
|
127
|
+
- All DISABLED → `basic/` is ready. `enhanced/` requires an enable cycle.
|
|
128
|
+
- Mixed → flag as anomaly; ask before proceeding.
|
|
129
|
+
|
|
130
|
+
Exit without changing any state.
|
|
131
|
+
|
|
132
|
+
## Mode: `basic` (deploy)
|
|
133
|
+
|
|
134
|
+
`pnpm integ:basic:update` runs **all** basic tests including `integ.scan-on-push`, so scan-on-push must be on **before** the run.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
inspector_disable_all
|
|
138
|
+
wait_inspector_status_all DISABLED || exit 1
|
|
139
|
+
scan_on_push_set true
|
|
140
|
+
|
|
141
|
+
# Run, then ALWAYS restore scan-on-push (use a trap or explicit if/then)
|
|
142
|
+
if pnpm integ:basic:update; then status=0; else status=$?; fi
|
|
143
|
+
scan_on_push_set false
|
|
144
|
+
[ "$status" -eq 0 ] || exit "$status"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Restore Inspector (unless `--no-restore`)
|
|
148
|
+
|
|
149
|
+
If any region's `ORIGINAL_STATE_*` was `ENABLED`, restore that region. Simplest correct approach when all three were originally identical: re-enable everywhere if any was originally ENABLED.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
if [ "$ORIGINAL_STATE_EAST1" = "ENABLED" ] || \
|
|
153
|
+
[ "$ORIGINAL_STATE_EAST2" = "ENABLED" ] || \
|
|
154
|
+
[ "$ORIGINAL_STATE_WEST2" = "ENABLED" ]; then
|
|
155
|
+
inspector_enable_all
|
|
156
|
+
wait_inspector_status_all ENABLED || exit 1
|
|
157
|
+
fi
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Mode: `enhanced` (deploy)
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Capture the "was DISABLED in any region" condition BEFORE we flip,
|
|
164
|
+
# because wait_enhanced_engine_warmup needs to know whether a real
|
|
165
|
+
# transition is happening (the engine only lags on fresh enable).
|
|
166
|
+
TRANSITION="ENABLED"
|
|
167
|
+
if [ "$ORIGINAL_STATE_EAST1" != "ENABLED" ] || \
|
|
168
|
+
[ "$ORIGINAL_STATE_EAST2" != "ENABLED" ] || \
|
|
169
|
+
[ "$ORIGINAL_STATE_WEST2" != "ENABLED" ]; then
|
|
170
|
+
TRANSITION="DISABLED"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
inspector_enable_all
|
|
174
|
+
wait_inspector_status_all ENABLED || exit 1
|
|
175
|
+
|
|
176
|
+
# Engine warmup: empirically 20-30 min on a fresh enable.
|
|
177
|
+
wait_enhanced_engine_warmup "$TRANSITION" 1200
|
|
178
|
+
|
|
179
|
+
# Up to 3 attempts × 10 min gap. Calibrated to the warmup tail —
|
|
180
|
+
# NOT to flaky tests. If 3 fail, stop.
|
|
181
|
+
MAX_ATTEMPTS=3 RETRY_GAP_SECS=600 \
|
|
182
|
+
enhanced_run_with_retry pnpm integ:enhanced:update || exit 1
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Restore (unless `--no-restore`)
|
|
186
|
+
|
|
187
|
+
If all three `ORIGINAL_STATE_*` were `DISABLED`, disable in all regions and poll until DISABLED. Otherwise leave as-is.
|
|
188
|
+
|
|
189
|
+
## Signature modes — shared preamble (deploy)
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
rm -rf cdk.out/ # avoid stale assets manifests from prior signature runs
|
|
193
|
+
REGION="$(default_region)"
|
|
194
|
+
REGISTRY="${ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com"
|
|
195
|
+
REPO="cdk-hnb659fds-container-assets-${ACCOUNT}-${REGION}"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`signature-ecr-signing` uses its own dedicated repo (set up via `ecr_signing_setup`).
|
|
199
|
+
|
|
200
|
+
## Mode: `signature-notation` (deploy)
|
|
201
|
+
|
|
202
|
+
**Profile reuse**: AWS Signer profiles cannot be deleted (only canceled), and a canceled profile cannot be reused. We intentionally keep `EcrScanVerifierTestProfile` `Active` across runs. **`put-signing-profile` is NOT actually idempotent** — calling it for an existing Active profile returns `ProfileAlreadyExists`. Use `signer_profile_ensure` from `scripts/integ.sh` (or check with `get-signing-profile` first). If you find it `Canceled`, use a new name (e.g. `EcrScanVerifierTestProfile2`) and substitute throughout.
|
|
203
|
+
|
|
204
|
+
Pre-flight: `notation version` should succeed. If absent, install via the AWS Signer installer pkg (see `test/integ/README.md` → Notation install — do not improvise URLs).
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# 1. Ensure profile exists (idempotent wrapper; raw put-signing-profile is NOT)
|
|
208
|
+
PROFILE_ARN="$(signer_profile_ensure)" || exit 1
|
|
209
|
+
|
|
210
|
+
# 2. Synth + publish only the Docker asset (no stack deploy yet)
|
|
211
|
+
npx cdk synth --app 'node test/integ/signature/integ.notation.js' -o cdk.out
|
|
212
|
+
npx cdk-assets -p cdk.out/NotationSignatureStack.assets.json publish
|
|
213
|
+
|
|
214
|
+
# 3. Resolve the test fixture digest (NOT the Lambda function image)
|
|
215
|
+
ASSET_HASH=$(cat cdk.out/NotationSignatureStack.assets.json | tr ',' '\n' | \
|
|
216
|
+
grep '"imageTag"' | head -1 | cut -d'"' -f4)
|
|
217
|
+
DIGEST=$(aws ecr describe-images --repository-name "${REPO}" \
|
|
218
|
+
--image-ids imageTag="${ASSET_HASH}" \
|
|
219
|
+
--query 'imageDetails[0].imageDigest' --output text)
|
|
220
|
+
|
|
221
|
+
# 4. Sign
|
|
222
|
+
aws ecr get-login-password | notation login --username AWS --password-stdin "${REGISTRY}"
|
|
223
|
+
notation sign \
|
|
224
|
+
--plugin com.amazonaws.signer.notation.plugin \
|
|
225
|
+
--id "${PROFILE_ARN}" \
|
|
226
|
+
"${REGISTRY}/${REPO}@${DIGEST}"
|
|
227
|
+
|
|
228
|
+
# 5. Run
|
|
229
|
+
pnpm integ:signature:update --language javascript --test-regex "integ.notation.js$"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Immutable-tag recovery**: If `notation sign` fails with `tag invalid: ... already exists ... cannot be overwritten because the tag is immutable`, a previous attempt left a partial referrers index tag. The bootstrap repo uses immutable tags, so the leftover must be deleted before retrying:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
REFERRER_TAG="sha256-${DIGEST#sha256:}"
|
|
236
|
+
aws ecr batch-delete-image --repository-name "${REPO}" \
|
|
237
|
+
--image-ids imageTag="${REFERRER_TAG}"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Then retry the `notation sign` step.
|
|
241
|
+
|
|
242
|
+
## Mode: `signature-cosign-kms` (deploy)
|
|
243
|
+
|
|
244
|
+
**Rekor note**: the Lambda verifier always skips Rekor. Sign with the same skip so the test matches Lambda behavior — verification then works offline / inside VPC without internet.
|
|
245
|
+
|
|
246
|
+
Pre-flight: `cosign version` and `jq --version` should both succeed. Do NOT auto-install via brew — the user may not use brew. If either is missing, abort and tell the user which package + suggested install commands (`brew install cosign jq`, `apt install jq`, sigstore release page for cosign, etc.).
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
command -v cosign >/dev/null || { echo "cosign not installed. See https://docs.sigstore.dev/cosign/installation/" >&2; exit 1; }
|
|
250
|
+
command -v jq >/dev/null || { echo "jq not installed." >&2; exit 1; }
|
|
251
|
+
|
|
252
|
+
KMS_KEY_ID=$(aws kms create-key \
|
|
253
|
+
--key-usage SIGN_VERIFY --key-spec ECC_NIST_P256 \
|
|
254
|
+
--query 'KeyMetadata.KeyId' --output text)
|
|
255
|
+
aws ssm put-parameter \
|
|
256
|
+
--name /ecr-scan-verifier/cosign-kms-key-id \
|
|
257
|
+
--value "${KMS_KEY_ID}" --type String --overwrite
|
|
258
|
+
KMS_KEY_ARN=$(aws kms describe-key --key-id "${KMS_KEY_ID}" \
|
|
259
|
+
--query 'KeyMetadata.Arn' --output text)
|
|
260
|
+
|
|
261
|
+
npx cdk synth --app 'node test/integ/signature/integ.cosign-kms.js' -o cdk.out
|
|
262
|
+
npx cdk-assets -p cdk.out/CosignKmsSignatureStack.assets.json publish
|
|
263
|
+
|
|
264
|
+
ASSET_HASH=$(cat cdk.out/CosignKmsSignatureStack.assets.json | tr ',' '\n' | \
|
|
265
|
+
grep '"imageTag"' | head -1 | cut -d'"' -f4)
|
|
266
|
+
DIGEST=$(aws ecr describe-images --repository-name "${REPO}" \
|
|
267
|
+
--image-ids imageTag="${ASSET_HASH}" \
|
|
268
|
+
--query 'imageDetails[0].imageDigest' --output text)
|
|
269
|
+
|
|
270
|
+
aws ecr get-login-password --region "${REGION}" | cosign login --username AWS --password-stdin "${REGISTRY}"
|
|
271
|
+
# cosign 3.x requires stripping rekor + oidc + ca + tsa from the signing-config
|
|
272
|
+
# when using --key (keyful). Leaving any of them in causes a silent hang on
|
|
273
|
+
# "Signing artifact...". `cosign_minimal_signing_config` does the strip.
|
|
274
|
+
cosign_minimal_signing_config /tmp/signing-config.json
|
|
275
|
+
cosign sign --signing-config /tmp/signing-config.json \
|
|
276
|
+
--key "awskms:///${KMS_KEY_ARN}" "${REGISTRY}/${REPO}@${DIGEST}"
|
|
277
|
+
|
|
278
|
+
pnpm integ:signature:update --language javascript --test-regex "integ.cosign-kms.js$"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Mode: `signature-cosign-publickey` (deploy)
|
|
282
|
+
|
|
283
|
+
Same Rekor-skip rule. `COSIGN_PASSWORD=""` required on `generate-key-pair` and `sign` to avoid the interactive password prompt blocking unattended runs.
|
|
284
|
+
|
|
285
|
+
Same pre-flight as `signature-cosign-kms`: check `cosign` and `jq`, abort with install hint if missing — do not auto-brew-install.
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
command -v cosign >/dev/null || { echo "cosign not installed. See https://docs.sigstore.dev/cosign/installation/" >&2; exit 1; }
|
|
289
|
+
command -v jq >/dev/null || { echo "jq not installed." >&2; exit 1; }
|
|
290
|
+
|
|
291
|
+
COSIGN_PASSWORD="" cosign generate-key-pair
|
|
292
|
+
aws ssm put-parameter \
|
|
293
|
+
--name /ecr-scan-verifier/cosign-public-key \
|
|
294
|
+
--value "$(cat cosign.pub)" --type String --overwrite
|
|
295
|
+
|
|
296
|
+
npx cdk synth --app 'node test/integ/signature/integ.cosign-publickey.js' -o cdk.out
|
|
297
|
+
npx cdk-assets -p cdk.out/CosignPublicKeySignatureStack.assets.json publish
|
|
298
|
+
|
|
299
|
+
ASSET_HASH=$(cat cdk.out/CosignPublicKeySignatureStack.assets.json | tr ',' '\n' | \
|
|
300
|
+
grep '"imageTag"' | head -1 | cut -d'"' -f4)
|
|
301
|
+
DIGEST=$(aws ecr describe-images --repository-name "${REPO}" \
|
|
302
|
+
--image-ids imageTag="${ASSET_HASH}" \
|
|
303
|
+
--query 'imageDetails[0].imageDigest' --output text)
|
|
304
|
+
|
|
305
|
+
aws ecr get-login-password --region "${REGION}" | cosign login --username AWS --password-stdin "${REGISTRY}"
|
|
306
|
+
# cosign 3.x requires stripping rekor + oidc + ca + tsa from the signing-config
|
|
307
|
+
# when using --key (keyful). Leaving any of them in causes a silent hang on
|
|
308
|
+
# "Signing artifact...". `cosign_minimal_signing_config` does the strip.
|
|
309
|
+
cosign_minimal_signing_config /tmp/signing-config.json
|
|
310
|
+
COSIGN_PASSWORD="" cosign sign --signing-config /tmp/signing-config.json \
|
|
311
|
+
--key cosign.key "${REGISTRY}/${REPO}@${DIGEST}"
|
|
312
|
+
|
|
313
|
+
pnpm integ:signature:update --language javascript --test-regex "integ.cosign-publickey.js$"
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`cosign.key` / `cosign.pub` are git-ignored. Do NOT commit them, and remove them via `cleanup-signature`.
|
|
317
|
+
|
|
318
|
+
## Mode: `signature-ecr-signing` (deploy)
|
|
319
|
+
|
|
320
|
+
ECR Managed Signing auto-signs on push when a signing-configuration matches the repository. The integ uses `ECRDeployment` to copy a CDK asset into a signing-enabled repo so that the push triggers a signature.
|
|
321
|
+
|
|
322
|
+
**Regional gotcha**: `aws ecr put-signing-configuration` is not available in every region. If the API call returns `UnknownOperationException` or similar, abort and report — do not silently fall back.
|
|
323
|
+
|
|
324
|
+
**Permissions gotcha**: the caller needs `signer:*` including `signer:SignPayload`. Surface a friendly error on `AccessDenied`.
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
ecr_signing_setup
|
|
328
|
+
|
|
329
|
+
if pnpm integ:signature:update --language javascript --test-regex "integ.ecr-signing.js$"; then
|
|
330
|
+
status=0
|
|
331
|
+
else
|
|
332
|
+
status=$?
|
|
333
|
+
fi
|
|
334
|
+
ecr_signing_teardown
|
|
335
|
+
exit "$status"
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Mode: `signature` (deploy)
|
|
339
|
+
|
|
340
|
+
Runs all four signature sub-modes back-to-back. They are state-agnostic w.r.t. Inspector, but each mutates SSM/KMS/ECR and synths into the shared `cdk.out/`, so order matters.
|
|
341
|
+
|
|
342
|
+
Failures in earlier modes do NOT short-circuit later ones — capture per-mode status and surface them all in the final report. Each mode's preamble (`rm -rf cdk.out/`, REGION/REGISTRY/REPO export) must run anew per iteration.
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
sig_status=()
|
|
346
|
+
for mode in signature-notation signature-cosign-kms signature-cosign-publickey signature-ecr-signing; do
|
|
347
|
+
echo "=== Running mode: $mode ==="
|
|
348
|
+
if run_mode "$mode"; then
|
|
349
|
+
sig_status+=("$mode: PASS")
|
|
350
|
+
else
|
|
351
|
+
sig_status+=("$mode: FAIL")
|
|
352
|
+
fi
|
|
353
|
+
done
|
|
354
|
+
printf '%s\n' "${sig_status[@]}"
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
After the loop, finish with `cleanup_signature_artifacts`.
|
|
358
|
+
|
|
359
|
+
## Mode: `all` (deploy)
|
|
360
|
+
|
|
361
|
+
Runs **every** integ test end-to-end with optimal Inspector state transitions.
|
|
362
|
+
|
|
363
|
+
State-transition strategy (minimizes Inspector flips, which each cost a ~5 min poll + the ~20 min engine warmup on enable):
|
|
364
|
+
|
|
365
|
+
1. **Enable Inspector** (if not already) and absorb the engine warmup. Serves both `enhanced` AND positions us for `signature-*` (state-agnostic but cheaper to keep ENABLED across them).
|
|
366
|
+
2. **Run `enhanced`** with the 3-attempt retry loop.
|
|
367
|
+
3. **Run all four `signature-*` modes**.
|
|
368
|
+
4. **Flip Inspector to DISABLED** + poll.
|
|
369
|
+
5. **Run `basic`** (proactive scan-on-push enable + unconditional restore).
|
|
370
|
+
6. **Restore Inspector to `ORIGINAL_STATE_*`** — `all` IGNORES `--no-restore` because a both-directions sequence has no obvious "as-found" target.
|
|
371
|
+
7. **Run `cleanup_signature_artifacts`** unconditionally.
|
|
372
|
+
|
|
373
|
+
Pseudocode:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
. scripts/integ.sh
|
|
377
|
+
ACCOUNT="$(account_id)"
|
|
378
|
+
ORIGINAL_STATE_EAST1="$(inspector_status us-east-1)"
|
|
379
|
+
ORIGINAL_STATE_EAST2="$(inspector_status us-east-2)"
|
|
380
|
+
ORIGINAL_STATE_WEST2="$(inspector_status us-west-2)"
|
|
381
|
+
|
|
382
|
+
pnpm tsc -p tsconfig.dev.json
|
|
383
|
+
(cd assets/lambda && pnpm install --frozen-lockfile && pnpm build)
|
|
384
|
+
|
|
385
|
+
# --- 1+2: enhanced ---
|
|
386
|
+
TRANSITION="ENABLED"
|
|
387
|
+
if [ "$ORIGINAL_STATE_EAST1" != "ENABLED" ] || \
|
|
388
|
+
[ "$ORIGINAL_STATE_EAST2" != "ENABLED" ] || \
|
|
389
|
+
[ "$ORIGINAL_STATE_WEST2" != "ENABLED" ]; then
|
|
390
|
+
TRANSITION="DISABLED"
|
|
391
|
+
fi
|
|
392
|
+
inspector_enable_all
|
|
393
|
+
wait_inspector_status_all ENABLED || exit 1
|
|
394
|
+
wait_enhanced_engine_warmup "$TRANSITION" 1200
|
|
395
|
+
|
|
396
|
+
results=()
|
|
397
|
+
if MAX_ATTEMPTS=3 RETRY_GAP_SECS=600 \
|
|
398
|
+
enhanced_run_with_retry pnpm integ:enhanced:update; then
|
|
399
|
+
results+=("enhanced: PASS")
|
|
400
|
+
else
|
|
401
|
+
results+=("enhanced: FAIL")
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
# --- 3: all signatures ---
|
|
405
|
+
for mode in signature-notation signature-cosign-kms signature-cosign-publickey signature-ecr-signing; do
|
|
406
|
+
if run_signature_mode "$mode"; then
|
|
407
|
+
results+=("$mode: PASS")
|
|
408
|
+
else
|
|
409
|
+
results+=("$mode: FAIL")
|
|
410
|
+
fi
|
|
411
|
+
done
|
|
412
|
+
|
|
413
|
+
# --- 4+5: basic ---
|
|
414
|
+
inspector_disable_all
|
|
415
|
+
wait_inspector_status_all DISABLED || exit 1
|
|
416
|
+
scan_on_push_set true
|
|
417
|
+
if pnpm integ:basic:update; then
|
|
418
|
+
results+=("basic: PASS")
|
|
419
|
+
else
|
|
420
|
+
results+=("basic: FAIL")
|
|
421
|
+
fi
|
|
422
|
+
scan_on_push_set false
|
|
423
|
+
|
|
424
|
+
# --- 6: restore Inspector (always for `all`) ---
|
|
425
|
+
if [ "$ORIGINAL_STATE_EAST1" = "ENABLED" ] || \
|
|
426
|
+
[ "$ORIGINAL_STATE_EAST2" = "ENABLED" ] || \
|
|
427
|
+
[ "$ORIGINAL_STATE_WEST2" = "ENABLED" ]; then
|
|
428
|
+
inspector_enable_all
|
|
429
|
+
wait_inspector_status_all ENABLED || exit 1
|
|
430
|
+
fi
|
|
431
|
+
|
|
432
|
+
# --- 7: cleanup (full) ---
|
|
433
|
+
cleanup_signature_artifacts
|
|
434
|
+
ecr_signing_teardown
|
|
435
|
+
scan_on_push_set false
|
|
436
|
+
|
|
437
|
+
printf '%s\n' "${results[@]}"
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Wall-clock budget for a clean `all` run: roughly 45–80 minutes depending on Inspector warmup and signature retries. Plan accordingly.
|
|
441
|
+
|
|
442
|
+
## Mode: `cleanup`
|
|
443
|
+
|
|
444
|
+
Full teardown of everything this skill can leave behind. Idempotent — safe to run any number of times, even when nothing was set up. Used as the final step of `all`, and useful on its own when interrupting a partial signature run.
|
|
445
|
+
|
|
446
|
+
What it touches:
|
|
447
|
+
|
|
448
|
+
- Signature artifacts (delegates to `cleanup_signature_artifacts`): SSM params, KMS key 7-day deletion schedule, local cosign keypair
|
|
449
|
+
- ECR signing dedicated repo + signing-configuration (delegates to `ecr_signing_teardown`)
|
|
450
|
+
- Bootstrap-repo scan-on-push reset to `false` in all three regions
|
|
451
|
+
|
|
452
|
+
What it does NOT touch:
|
|
453
|
+
|
|
454
|
+
- Inspector enable/disable state (per-account decision; flip via `basic` / `enhanced` if needed)
|
|
455
|
+
- `EcrScanVerifierTestProfile` AWS Signer profile (cancellation is permanent — left Active by design)
|
|
456
|
+
- Pushed Docker / signature artifacts in the bootstrap repo (immutable, cannot be cleaned)
|
|
457
|
+
|
|
458
|
+
```bash
|
|
459
|
+
. scripts/integ.sh
|
|
460
|
+
cleanup_signature_artifacts
|
|
461
|
+
ecr_signing_teardown # idempotent (|| true on each step)
|
|
462
|
+
scan_on_push_set false
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Report what was actually removed vs what was already absent.
|
|
466
|
+
|
|
467
|
+
## Mode: `cleanup-signature`
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
cleanup_signature_artifacts
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
The function resolves the KMS key id from SSM before deleting the param. If SSM is already gone and the id can't be recovered, it prints a WARN and skips KMS deletion — ask the user for the key id rather than guess.
|
|
474
|
+
|
|
475
|
+
## Reporting
|
|
476
|
+
|
|
477
|
+
At the end of every run, print:
|
|
478
|
+
|
|
479
|
+
- Mode invoked + whether `--snapshot-only` was set + duration
|
|
480
|
+
- `ORIGINAL_STATE_*` per region and whether each was restored (or "n/a" for snapshot-only)
|
|
481
|
+
- Test pass/fail counts (parse from `integ-runner` output)
|
|
482
|
+
- Any leftover side effects (e.g. `EcrScanVerifierTestProfile` left Active intentionally; signing-configuration disabled; `cosign.key` removed)
|
|
483
|
+
|
|
484
|
+
Never end with stale state silently. If a restore step was skipped or failed, say so explicitly.
|
|
485
|
+
|
|
486
|
+
## Important
|
|
487
|
+
|
|
488
|
+
- **Deploy is the default.** Snapshot-only is opt-in via `--snapshot-only`, which collapses every mode to a single `pnpm integ:<mode>` call with no AWS calls and no state changes.
|
|
489
|
+
- **Always operate on all three regions** (`us-east-1 us-east-2 us-west-2`) when toggling Inspector — partial toggles cause hard-to-debug test failures.
|
|
490
|
+
- **Poll with a bounded loop** (`wait_inspector_status` from `scripts/integ.sh`) — never use a bare `sleep` for state convergence.
|
|
491
|
+
- **`enhanced` engine lag is long**: status flipping to `ENABLED` is not the same as the scanning engine being ready. On a fresh DISABLED→ENABLED transition, sleep 1200s (20 min) before the first test attempt, then allow up to 3 total attempts with 600s (10 min) gaps. This worst-case ~40 min budget matches observed warmup. If all 3 fail, the cause is not propagation lag — stop waiting and investigate.
|
|
492
|
+
- **Always enable scan-on-push proactively for `basic`** — `pnpm integ:basic:update` runs the `integ.scan-on-push` test in the same suite, so toggling on after a failure is too late.
|
|
493
|
+
- **Never cancel `EcrScanVerifierTestProfile`** — cancellation is permanent and blocks future runs under the same name.
|
|
494
|
+
- **`cosign generate-key-pair` and `cosign sign` need `COSIGN_PASSWORD=""`** when run unattended — otherwise they hang on a password prompt.
|
|
495
|
+
- **Never commit `cosign.key` / `cosign.pub`** — they're git-ignored, keep it that way.
|
|
496
|
+
- **Bootstrap repo uses immutable tags** — leftover Notation referrer tags from a failed `notation sign` MUST be deleted before retrying; do not work around with a fresh tag.
|
|
497
|
+
- **`signature-ecr-signing` teardown is not optional** — wrap the test runner so `ecr_signing_teardown` runs even on failure.
|
|
498
|
+
- **`cosign sign` looks hung** — on success it emits only `Signing artifact...` to stderr and then goes silent until the OCI referrer push completes. Use `cosign sign -d` for verbose HTTP logging when debugging.
|
|
499
|
+
- **worktrees need `pnpm install --frozen-lockfile` first** — the `pnpm integ:*` scripts call `tsc` directly (not via `npx tsc`); without local `node_modules` the script fails with `sh: tsc: command not found`.
|
|
500
|
+
- When in doubt about which test to run, call `AskUserQuestion` rather than guess.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify-integ-docs
|
|
3
|
+
description: Verify the integ-test skill, test/integ/README.md, and scripts/integ.sh stay consistent. Runs the mechanical script checks PLUS LLM-only semantic checks (mode-description agreement, code-example coherence, cross-reference resolution, depth parity, helper-arg correctness) that a regex can't catch. Sets the `integ-docs` markgate marker on full pass.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# verify-integ-docs
|
|
7
|
+
|
|
8
|
+
The integ workflow is documented in three places that must agree:
|
|
9
|
+
|
|
10
|
+
| Source | Audience |
|
|
11
|
+
| ----------------------------------- | ---------------------- |
|
|
12
|
+
| `.claude/skills/integ-test/SKILL.md`| Claude Code (automated)|
|
|
13
|
+
| `test/integ/README.md` | humans (manual runs) |
|
|
14
|
+
| `scripts/integ.sh` | shared shell helpers |
|
|
15
|
+
|
|
16
|
+
Both docs are intentionally redundant — the skill drives Claude through the workflow, the README lets a human run the same workflow by hand. This skill keeps them in lockstep using **two layers**:
|
|
17
|
+
|
|
18
|
+
1. **Mechanical** (`scripts/verify-integ-docs.sh`) — name parity, dead-helper detection, anti-pattern grep. Cheap, deterministic, runnable in CI.
|
|
19
|
+
2. **Semantic** (this skill) — does each mode actually mean the same thing in both docs? Do code examples produce equivalent results? Are cross-references valid? Only an LLM that reads both docs side-by-side can answer these.
|
|
20
|
+
|
|
21
|
+
Both layers must pass before the marker flips. If only the mechanical layer matters, use `markgate run integ-docs -- ./scripts/verify-integ-docs.sh` directly instead of this skill — but you'll miss the kinds of drift listed under "Semantic checks" below.
|
|
22
|
+
|
|
23
|
+
## Layer 1: mechanical (script)
|
|
24
|
+
|
|
25
|
+
`scripts/verify-integ-docs.sh` enforces:
|
|
26
|
+
|
|
27
|
+
1. **All three source files exist** — deleting one without the other would leave stale references.
|
|
28
|
+
2. **Mode parity** — every mode in the SKILL's `argument-hint:` frontmatter must appear in both the SKILL's `## Arguments` list AND the README's `### Modes` table.
|
|
29
|
+
3. **Helper parity** — every function defined in `scripts/integ.sh` must be referenced from at least one of the two docs (catches dead helpers and rename drift).
|
|
30
|
+
4. **Both docs source the helpers** — both must reference `scripts/integ.sh`.
|
|
31
|
+
5. **No raw `aws signer put-signing-profile`** — it's NOT idempotent (returns `ProfileAlreadyExists` on existing Active profile). Must use `signer_profile_ensure`.
|
|
32
|
+
6. **No insufficient cosign signing-config strip** — `del(.rekorTlogUrls)` alone causes cosign 3.x to silently hang on `--key` signing. Must use `cosign_minimal_signing_config` (also strips oidc / ca / tsa).
|
|
33
|
+
7. **`signature-*` mode test sources exist** — every `signature-<name>` mode must have a corresponding `test/integ/signature/integ.<name>.ts`.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
./scripts/verify-integ-docs.sh
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
If it fails, fix the reported drift first; do NOT proceed to layer 2 or set the marker.
|
|
40
|
+
|
|
41
|
+
## Layer 2: semantic (this skill, LLM-only)
|
|
42
|
+
|
|
43
|
+
Run these AFTER layer 1 passes. Each catches a class of drift the script cannot.
|
|
44
|
+
|
|
45
|
+
### S1. Per-mode description agreement
|
|
46
|
+
|
|
47
|
+
For each mode in `argument-hint:`, compare:
|
|
48
|
+
|
|
49
|
+
- the SKILL's `## Arguments` line: `` - `mode` — <SKILL description> ``
|
|
50
|
+
- the README's `### Modes` table row: `` | `mode` | <README description> | ``
|
|
51
|
+
|
|
52
|
+
These are written for different audiences (LLM vs human), so they should NOT be byte-identical. They MUST claim the same behavior. Flag drift such as:
|
|
53
|
+
|
|
54
|
+
- **Contradictory claims**: SKILL says "switch Inspector if needed, run …, restore" but README says "Run …; no Inspector toggle" → one is wrong.
|
|
55
|
+
- **Coverage mismatch**: SKILL describes 3 sub-steps, README describes only 1 → README is misleading; either expand it or compress the SKILL.
|
|
56
|
+
- **Out-of-date defaults**: SKILL says "deploys by default" but README says "snapshot-only by default" → polarity skew.
|
|
57
|
+
|
|
58
|
+
### S2. Code-example coherence
|
|
59
|
+
|
|
60
|
+
For each mode that has a code example in BOTH docs (notation, cosign-kms, cosign-publickey, ecr-signing), read the SKILL and README versions side-by-side and verify they would produce the same result:
|
|
61
|
+
|
|
62
|
+
- Same commands in the same order (allow trivial reordering only when results are independent).
|
|
63
|
+
- Same flags (e.g., `--region "${REGION}"` vs no region).
|
|
64
|
+
- Same helper invocations (e.g., both use `signer_profile_ensure`, not one with `aws signer put-signing-profile`).
|
|
65
|
+
- Same env vars (`COSIGN_PASSWORD=""` present in both, etc.).
|
|
66
|
+
|
|
67
|
+
If they diverge, ask: which is right? Usually the SKILL (more recently audited) — port the change to the README, or vice versa.
|
|
68
|
+
|
|
69
|
+
### S3. Cross-reference resolution
|
|
70
|
+
|
|
71
|
+
Every markdown link of the form `[text](#anchor)` in SKILL.md or README.md must resolve to an actual heading in the same document. Common breaks:
|
|
72
|
+
|
|
73
|
+
- Heading renamed but the anchor link not updated.
|
|
74
|
+
- Anchor uses underscore where the heading slug uses hyphen.
|
|
75
|
+
- Link refers to a section that exists in the OTHER doc (not the same one).
|
|
76
|
+
|
|
77
|
+
Also check that `[text](../../scripts/integ.sh)` and similar relative paths actually resolve from the doc's location.
|
|
78
|
+
|
|
79
|
+
### S4. Helper-argument correctness
|
|
80
|
+
|
|
81
|
+
For each helper from `scripts/integ.sh` that appears in either doc, confirm the call sites use valid arguments:
|
|
82
|
+
|
|
83
|
+
- `scan_on_push_set true|false` — not `enable` / `on` / `yes`.
|
|
84
|
+
- `wait_inspector_status_all ENABLED|DISABLED` — uppercase string literals.
|
|
85
|
+
- `wait_enhanced_engine_warmup ENABLED|DISABLED [secs]` — same.
|
|
86
|
+
- `signer_profile_ensure` — no args.
|
|
87
|
+
|
|
88
|
+
The script's helper-parity check (mechanical #3) only verifies the **name** is mentioned; it cannot check the **arguments**.
|
|
89
|
+
|
|
90
|
+
### S5. Depth parity
|
|
91
|
+
|
|
92
|
+
For each mode, neither doc's explanation should be more than ~3× the length of the other. The two are for different audiences, but a 1-line README row plus a 50-line SKILL section means the README is silently lying about how complex the mode is. Flag and either expand the shorter side or trim the longer.
|
|
93
|
+
|
|
94
|
+
### S6. Drift in "Important" / gotcha lists
|
|
95
|
+
|
|
96
|
+
Compare the bullet lists at the end of each doc ("Important", "Important Note", trailing tips). Any caveat present in one but missing from the other is a candidate for porting. Examples that have bitten us before:
|
|
97
|
+
|
|
98
|
+
- "Bootstrap repo uses immutable tags — must delete leftover Notation referrer tag before retrying"
|
|
99
|
+
- "cosign 3.x signing-config needs oidc/ca/tsa stripped, not just rekor"
|
|
100
|
+
- "AWS Signer profile cancellation is permanent"
|
|
101
|
+
|
|
102
|
+
If any of those appear in one doc but not the other, port them.
|
|
103
|
+
|
|
104
|
+
## When to run
|
|
105
|
+
|
|
106
|
+
- After editing any of `.claude/skills/integ-test/SKILL.md`, `test/integ/README.md`, or `scripts/integ.sh`.
|
|
107
|
+
- The `integ-docs-gate.sh` PreToolUse hook blocks `gh pr create` / `gh pr merge` when the marker is stale, so a missed run surfaces at PR time.
|
|
108
|
+
- The `.github/workflows/verify-integ-docs.yml` workflow runs the mechanical script on every PR. **It does NOT run the semantic checks above** — those still require this skill (an LLM). CI catches the cheap drift; the skill catches the expensive drift.
|
|
109
|
+
|
|
110
|
+
## Setting the marker
|
|
111
|
+
|
|
112
|
+
ONLY after both layer 1 (script) and layer 2 (S1–S6) pass:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
if command -v mise >/dev/null 2>&1; then
|
|
116
|
+
mise exec -- markgate set integ-docs
|
|
117
|
+
else
|
|
118
|
+
markgate set integ-docs
|
|
119
|
+
fi
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If layer 1 failed → fix mechanical drift, re-run. If any semantic check (S1–S6) flagged something → fix the underlying doc drift (or surface to the user when the right answer isn't obvious), THEN set the marker. Setting the marker on a failed run defeats the entire gate.
|
|
123
|
+
|
|
124
|
+
## Recovery from drift
|
|
125
|
+
|
|
126
|
+
The most common failures and their fixes:
|
|
127
|
+
|
|
128
|
+
- **Mode missing from one doc** (mech #2): add it to the missing place. Don't remove it from the other.
|
|
129
|
+
- **Helper not referenced** (mech #3): either reference it from the appropriate doc OR delete it from `scripts/integ.sh` if truly unused.
|
|
130
|
+
- **Raw `put-signing-profile`** (mech #5): replace with `signer_profile_ensure`.
|
|
131
|
+
- **Raw `del(.rekorTlogUrls)`** (mech #6): replace with `cosign_minimal_signing_config /tmp/signing-config.json`.
|
|
132
|
+
- **Signature mode without matching test** (mech #7): either add the test (`test/integ/signature/integ.<name>.ts` + build), or remove the mode from the skill and README.
|
|
133
|
+
- **Per-mode description disagreement** (sem S1): pick the audience-appropriate wording for each, but make sure they describe the same behavior. Don't silently delete claims to "fix" the diff.
|
|
134
|
+
- **Code example divergence** (sem S2): identify which version was changed last (git blame) and port to the other.
|
|
135
|
+
- **Broken cross-reference** (sem S3): fix the anchor or rename the heading consistently.
|
|
136
|
+
- **Wrong helper arg** (sem S4): fix the call site — the helper's contract is authoritative, not the doc.
|
|
137
|
+
- **Depth imbalance** (sem S5): usually expand the README (the human audience needs MORE detail, not less, even though the SKILL can be terse with LLM context).
|
|
138
|
+
- **Caveat in one doc only** (sem S6): port it.
|
|
139
|
+
|
|
140
|
+
## Important
|
|
141
|
+
|
|
142
|
+
- **Layer 2 is the skill's reason to exist.** If you only run the script, prefer `markgate run integ-docs -- ./scripts/verify-integ-docs.sh` and skip this skill entirely. The skill's value is doing what `grep` can't.
|
|
143
|
+
- **Never set the marker on a failed run.** The gate's whole point is that the marker is an audit trail saying "yes, these files agree, and I personally read them." Setting it to silence a failure is the worst possible move.
|
|
144
|
+
- **Adding a new helper to `scripts/integ.sh`** requires using it from at least one doc in the same change (mechanical check), AND documenting its arg contract (semantic check S4).
|
|
145
|
+
- **Adding a new integ test file** alone does NOT invalidate the marker (only SKILL.md / README.md / scripts/integ.sh do, per `.markgate.yml` scope). New tests typically come with README changes anyway, which trip the marker naturally.
|
package/.jsii
CHANGED
|
@@ -5622,6 +5622,6 @@
|
|
|
5622
5622
|
"symbolId": "src/signature-verification:VerificationOptions"
|
|
5623
5623
|
}
|
|
5624
5624
|
},
|
|
5625
|
-
"version": "0.1.
|
|
5626
|
-
"fingerprint": "
|
|
5625
|
+
"version": "0.1.2",
|
|
5626
|
+
"fingerprint": "EPuVTblfXSkiSlj9KwfRbHNKkJE9igTQ8Rr0HbSceZQ="
|
|
5627
5627
|
}
|
package/.markgate.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# markgate configuration — https://github.com/go-to-k/markgate
|
|
2
|
+
#
|
|
3
|
+
# Gates:
|
|
4
|
+
# integ-docs — keeps the three integ-workflow sources in lockstep:
|
|
5
|
+
# .claude/skills/integ-test/SKILL.md (automated),
|
|
6
|
+
# test/integ/README.md (manual),
|
|
7
|
+
# scripts/integ.sh (shared helpers).
|
|
8
|
+
# Set by the /verify-integ-docs skill after
|
|
9
|
+
# scripts/verify-integ-docs.sh passes. The PreToolUse
|
|
10
|
+
# hook .claude/hooks/integ-docs-gate.sh blocks
|
|
11
|
+
# `gh pr create` / `gh pr merge` when stale.
|
|
12
|
+
#
|
|
13
|
+
# Scope is intentionally narrow — only the three files above. Integ
|
|
14
|
+
# test files under test/integ/{basic,enhanced,signature}/ are NOT in
|
|
15
|
+
# scope: adding a new test typically comes with a README/SKILL update
|
|
16
|
+
# anyway, which trips the marker naturally. Gating on test file edits
|
|
17
|
+
# directly would force re-verification on every test snapshot refresh.
|
|
18
|
+
|
|
19
|
+
gates:
|
|
20
|
+
integ-docs:
|
|
21
|
+
hash: files
|
|
22
|
+
include:
|
|
23
|
+
- ".claude/skills/integ-test/SKILL.md"
|
|
24
|
+
- "test/integ/README.md"
|
|
25
|
+
- "scripts/integ.sh"
|
package/.mise.toml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# mise tool pinning — https://mise.jdx.dev/
|
|
2
|
+
#
|
|
3
|
+
# Pinning markgate via mise ensures every contributor uses the same
|
|
4
|
+
# schema version. Mixing 0.3.0 and 0.3.1+ binaries silently invalidates
|
|
5
|
+
# each other's markers (the schema bumped in 0.3.1), so Homebrew vs
|
|
6
|
+
# mise drift would constantly trip the integ-docs gate for no reason.
|
|
7
|
+
|
|
8
|
+
[tools]
|
|
9
|
+
"ubi:go-to-k/markgate" = "0.3.3"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
FROM public.ecr.aws/amazonlinux/amazonlinux:2023 AS builder
|
|
2
|
+
|
|
3
|
+
# Notation CLI + AWS Signer plugin
|
|
4
|
+
RUN curl -sLo /tmp/signer.rpm \
|
|
5
|
+
"https://d2hvyiie56hcat.cloudfront.net/linux/arm64/installer/rpm/latest/aws-signer-notation-cli_arm64.rpm" \
|
|
6
|
+
&& rpm -ivh /tmp/signer.rpm && rm /tmp/signer.rpm
|
|
7
|
+
|
|
8
|
+
# Cosign for ARM64 (Sigstore)
|
|
9
|
+
ARG COSIGN_VERSION=3.0.5
|
|
10
|
+
RUN curl -sLo /tmp/cosign \
|
|
11
|
+
"https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-arm64" \
|
|
12
|
+
&& chmod +x /tmp/cosign
|
|
13
|
+
|
|
14
|
+
FROM public.ecr.aws/lambda/nodejs:24-arm64
|
|
15
|
+
|
|
16
|
+
# Notation binary
|
|
17
|
+
COPY --from=builder /usr/local/bin/notation /var/task/bin/notation
|
|
18
|
+
|
|
19
|
+
# Notation plugin (must be at plugins/<plugin-name>/<binary>)
|
|
20
|
+
COPY --from=builder \
|
|
21
|
+
/opt/com.amazonaws.signer.notation.installer.rpm/notation_libexec/notation-com.amazonaws.signer.notation.plugin \
|
|
22
|
+
/var/task/notation-config/plugins/com.amazonaws.signer.notation.plugin/notation-com.amazonaws.signer.notation.plugin
|
|
23
|
+
|
|
24
|
+
# Notation trust store (must be at truststore/x509/signingAuthority/<store-name>/<certs>)
|
|
25
|
+
# Commercial regions
|
|
26
|
+
COPY --from=builder \
|
|
27
|
+
/opt/com.amazonaws.signer.notation.installer.rpm/notation_config/aws-signer-notation-root.crt \
|
|
28
|
+
/var/task/notation-config/truststore/x509/signingAuthority/aws-signer-ts/aws-signer-notation-root.crt
|
|
29
|
+
# GovCloud regions
|
|
30
|
+
COPY --from=builder \
|
|
31
|
+
/opt/com.amazonaws.signer.notation.installer.rpm/notation_config/aws-us-gov-signer-notation-root.crt \
|
|
32
|
+
/var/task/notation-config/truststore/x509/signingAuthority/aws-us-gov-signer-ts/aws-us-gov-signer-notation-root.crt
|
|
33
|
+
|
|
34
|
+
# Fix permissions (RPM installs with root-only 700/600 permissions)
|
|
35
|
+
# Lambda base image doesn't have 'find', so use chmod -R and then fix executables
|
|
36
|
+
RUN chmod -R 755 /var/task/notation-config && \
|
|
37
|
+
chmod 644 /var/task/notation-config/truststore/x509/signingAuthority/aws-signer-ts/*.crt && \
|
|
38
|
+
chmod 644 /var/task/notation-config/truststore/x509/signingAuthority/aws-us-gov-signer-ts/*.crt && \
|
|
39
|
+
chmod +x /var/task/bin/notation && \
|
|
40
|
+
chmod +x /var/task/notation-config/plugins/com.amazonaws.signer.notation.plugin/notation-com.amazonaws.signer.notation.plugin
|
|
41
|
+
|
|
42
|
+
# Cosign
|
|
43
|
+
COPY --from=builder /tmp/cosign /var/task/bin/cosign
|
|
44
|
+
RUN chmod +x /var/task/bin/cosign
|
|
45
|
+
|
|
46
|
+
# Lambda handler
|
|
47
|
+
COPY dist/index.js ${LAMBDA_TASK_ROOT}/index.js
|
|
48
|
+
|
|
49
|
+
CMD ["index.handler"]
|
package/lib/ecr-scan-verifier.js
CHANGED
|
@@ -156,5 +156,5 @@ class EcrScanVerifier extends constructs_1.Construct {
|
|
|
156
156
|
}
|
|
157
157
|
exports.EcrScanVerifier = EcrScanVerifier;
|
|
158
158
|
_a = JSII_RTTI_SYMBOL_1;
|
|
159
|
-
EcrScanVerifier[_a] = { fqn: "ecr-scan-verifier.EcrScanVerifier", version: "0.1.
|
|
159
|
+
EcrScanVerifier[_a] = { fqn: "ecr-scan-verifier.EcrScanVerifier", version: "0.1.2" };
|
|
160
160
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWNyLXNjYW4tdmVyaWZpZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZWNyLXNjYW4tdmVyaWZpZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSwrQkFBNEI7QUFDNUIsNkNBQWdHO0FBRWhHLCtEQUFzRDtBQUN0RCxpREFBc0Q7QUFDdEQsdURBTWdDO0FBR2hDLG1FQUF3RDtBQUN4RCwyQ0FBbUQ7QUFLbkQsbUNBQW1DO0FBa0huQzs7OztHQUlHO0FBQ0gsTUFBYSxlQUFnQixTQUFRLHNCQUFTO0lBRzVDLFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBMkI7UUFDbkUsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUVqQixJQUFJLENBQUMsZUFBZSxHQUFHLEtBQUssQ0FBQyxlQUFlLENBQUM7UUFDN0MsTUFBTSxhQUFhLEdBQUcsNkNBQTZDLENBQUM7UUFFcEUsTUFBTSxvQkFBb0IsR0FBRyxJQUFJLDhCQUFpQixDQUFDLElBQUksRUFBRSxzQkFBc0IsRUFBRTtZQUMvRSxJQUFJLEVBQUUsc0NBQXNDO1lBQzVDLGFBQWE7WUFDYixPQUFPLEVBQUUsb0JBQU8sQ0FBQyxVQUFVO1lBQzNCLE9BQU8sRUFBRSxvQkFBTyxDQUFDLFVBQVU7WUFDM0IsSUFBSSxFQUFFLHNCQUFTLENBQUMsY0FBYyxDQUFDLElBQUEsV0FBSSxFQUFDLFNBQVMsRUFBRSxrQkFBa0IsQ0FBQyxFQUFFO2dCQUNsRSxRQUFRLEVBQUUseUJBQVEsQ0FBQyxXQUFXO2dCQUM5QixVQUFVLEVBQUUsd0JBQVUsQ0FBQyxNQUFNO2FBQzlCLENBQUM7WUFDRixZQUFZLEVBQUUseUJBQVksQ0FBQyxNQUFNO1lBQ2pDLE9BQU8sRUFBRSxzQkFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUM7WUFDOUIsYUFBYSxFQUFFLENBQUM7WUFDaEIsUUFBUSxFQUFFLElBQUksQ0FBQyxlQUFlO1NBQy9CLENBQUMsQ0FBQztRQUVILE1B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RUYsSUFBSSw0QkFBYyxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUU7WUFDbkMsWUFBWSxFQUFFLHlCQUF5QjtZQUN2QyxVQUFVLEVBQUUsa0JBQWtCO1lBQzlCLFlBQVksRUFBRSxnQkFBZ0IsQ0FBQyxZQUFZO1NBQzVDLENBQUMsQ0FBQztRQUVILEtBQUssQ0FBQyxlQUFlLEVBQUUsT0FBTyxDQUFDLENBQUMsU0FBUyxFQUFFLEVBQUU7WUFDM0MsU0FBUyxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDckMsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQsZ0JBQWdCO0lBQ2hCLElBQUksZ0JBQWdCO1FBQ2xCLE9BQU8sSUFBSSxDQUFDLGVBQWUsQ0FBQztJQUM5QixDQUFDOztBQXRMSCwwQ0F1TEMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBqb2luIH0gZnJvbSAncGF0aCc7XG5pbXBvcnQgeyBBbm5vdGF0aW9ucywgQXNwZWN0cywgQ3VzdG9tUmVzb3VyY2UsIER1cmF0aW9uLCBJZ25vcmVNb2RlLCBTdGFjayB9IGZyb20gJ2F3cy1jZGstbGliJztcbmltcG9ydCB7IElSZXBvc2l0b3J5IH0gZnJvbSAnYXdzLWNkay1saWIvYXdzLWVjcic7XG5pbXBvcnQgeyBQbGF0Zm9ybSB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1lY3ItYXNzZXRzJztcbmltcG9ydCB7IFBvbGljeVN0YXRlbWVudCB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1pYW0nO1xuaW1wb3J0IHtcbiAgQXJjaGl0ZWN0dXJlLFxuICBBc3NldENvZGUsXG4gIEhhbmRsZXIsXG4gIFJ1bnRpbWUsXG4gIFNpbmdsZXRvbkZ1bmN0aW9uLFxufSBmcm9tICdhd3MtY2RrLWxpYi9hd3MtbGFtYmRhJztcbmltcG9ydCB7IElMb2dHcm91cCB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1sb2dzJztcbmltcG9ydCB7IElUb3BpYyB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1zbnMnO1xuaW1wb3J0IHsgUHJvdmlkZXIgfSBmcm9tICdhd3MtY2RrLWxpYi9jdXN0b20tcmVzb3VyY2VzJztcbmltcG9ydCB7IENvbnN0cnVjdCwgSUNvbnN0cnVjdCB9IGZyb20gJ2NvbnN0cnVjdHMnO1xuaW1wb3J0IHsgU2Nhbm5lckN1c3RvbVJlc291cmNlUHJvcHMgfSBmcm9tICcuL2N1c3RvbS1yZXNvdXJjZS1wcm9wcyc7XG5pbXBvcnQgeyBTY2FuQ29uZmlnIH0gZnJvbSAnLi9zY2FuLWNvbmZpZyc7XG5pbXBvcnQgeyBTY2FuTG9nc091dHB1dCB9IGZyb20gJy4vc2Nhbi1sb2dzLW91dHB1dCc7XG5pbXBvcnQgeyBTaWduYXR1cmVWZXJpZmljYXRpb24gfSBmcm9tICcuL3NpZ25hdHVyZS12ZXJpZmljYXRpb24nO1xuaW1wb3J0IHsgU2V2ZXJpdHkgfSBmcm9tICcuL3R5cGVzJztcblxuLyoqXG4gKiBQcm9wZXJ0aWVzIGZvciBFY3JTY2FuVmVyaWZpZXIgQ29uc3RydWN0LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEVjclNjYW5WZXJpZmllclByb3BzIHtcbiAgLyoqXG4gICAqIEVDUiBSZXBvc2l0b3J5IHRvIHNjYW4uXG4gICAqL1xuICByZWFkb25seSByZXBvc2l0b3J5OiBJUmVwb3NpdG9yeTtcblxuICAvKipcbiAgICogSW1hZ2UgdGFnIG9yIGRpZ2VzdCB0byBzY2FuLlxuICAgKlxuICAgKiBZb3UgY2FuIHNwZWNpZnkgYSB0YWcgKGUuZy4sICd2MS4wJywgJ2xhdGVzdCcpIG9yIGEgZGlnZXN0IChlLmcuLCAnc2hhMjU2OmFiYzEyMy4uLicpLlxuICAgKiBJZiB0aGUgdmFsdWUgc3RhcnRzIHdpdGggJ3NoYTI1NjonLCBpdCBpcyB0cmVhdGVkIGFzIGEgZGlnZXN0LlxuICAgKlxuICAgKiBAZGVmYXVsdCAnbGF0ZXN0J1xuICAgKi9cbiAgcmVhZG9ubHkgaW1hZ2VUYWc/OiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFNjYW4gY29uZmlndXJhdGlvbiDigJQgY2hvb3NlIGJhc2VkIG9uIHlvdXIgRUNSIHJlcG9zaXRvcnkvYWNjb3VudCBzZXR0aW5nczpcbiAgICpcbiAgICogLSBgU2NhbkNvbmZpZy5iYXNpYygpYCAoZGVmYXVsdDogYHN0YXJ0U2NhbjogdHJ1ZWApIOKAlCBzdGFydHMgYSBzY2FuIHZpYSB0aGUgRUNSIEFQSS5cbiAgICogICBObyBhZGRpdGlvbmFsIEVDUiBjb25maWd1cmF0aW9uIHJlcXVpcmVkLlxuICAgKiAtIGBTY2FuQ29uZmlnLmJhc2ljKHsgc3RhcnRTY2FuOiBmYWxzZSB9KWAg4oCUIHBvbGxzIGZvciBleGlzdGluZyByZXN1bHRzLlxuICAgKiAgIFJlcXVpcmVzIEJhc2ljIHNjYW4tb24tcHVzaCB0byBiZSBlbmFibGVkIG9uIHRoZSByZXBvc2l0b3J5LlxuICAgKiAtIGBTY2FuQ29uZmlnLmVuaGFuY2VkKClgIOKAlCB1c2VzIEFtYXpvbiBJbnNwZWN0b3IgZW5oYW5jZWQgc2Nhbm5pbmcuXG4gICAqICAgUmVxdWlyZXMgRW5oYW5jZWQgc2Nhbm5pbmcgdG8gYmUgZW5hYmxlZCBvbiB0aGUgYWNjb3VudC5cbiAgICpcbiAgICogSWYgdGhlIHJlcXVpcmVkIHNjYW5uaW5nIGNvbmZpZ3VyYXRpb24gaXMgbm90IGluIHBsYWNlIGFuZCBubyBwcmlvciBzY2FuIHJlc3VsdHMgZXhpc3QsXG4gICAqIHRoZSBkZXBsb3ltZW50IHdpbGwgZmFpbC5cbiAgICovXG4gIHJlYWRvbmx5IHNjYW5Db25maWc6IFNjYW5Db25maWc7XG5cbiAgLyoqXG4gICAqIFNldmVyaXR5IHRocmVzaG9sZCBmb3IgdnVsbmVyYWJpbGl0eSBkZXRlY3Rpb24uXG4gICAqXG4gICAqIElmIHZ1bG5lcmFiaWxpdGllcyBhdCBvciBhYm92ZSBhbnkgb2YgdGhlIHNwZWNpZmllZCBzZXZlcml0eSBsZXZlbHMgYXJlIGZvdW5kLFxuICAgKiB0aGUgc2NhbiB3aWxsIGJlIGNvbnNpZGVyZWQgYXMgaGF2aW5nIGZvdW5kIHZ1bG5lcmFiaWxpdGllcy5cbiAgICpcbiAgICogQGRlZmF1bHQgW1NldmVyaXR5LkNSSVRJQ0FMXVxuICAgKi9cbiAgcmVhZG9ubHkgc2V2ZXJpdHk/OiBTZXZlcml0eVtdO1xuXG4gIC8qKlxuICAgKiBXaGV0aGVyIHRvIGZhaWwgdGhlIENsb3VkRm9ybWF0aW9uIGRlcGxveW1lbnQgaWYgdnVsbmVyYWJpbGl0aWVzIGFyZSBkZXRlY3RlZFxuICAgKiBhYm92ZSB0aGUgc2V2ZXJpdHkgdGhyZXNob2xkLlxuICAgKlxuICAgKiBAZGVmYXVsdCB0cnVlXG4gICAqL1xuICByZWFkb25seSBmYWlsT25WdWxuZXJhYmlsaXR5PzogYm9vbGVhbjtcblxuICAvKipcbiAgICogRmluZGluZyBJRHMgdG8gaWdub3JlIGR1cmluZyB2dWxuZXJhYmlsaXR5IGV2YWx1YXRpb24uXG4gICAqXG4gICAqIEZvciBiYXNpYyBzY2FubmluZzogQ1ZFIElEcyAoZS5nLiwgJ0NWRS0yMDIzLTM3OTIwJylcbiAgICogRm9yIGVuaGFuY2VkIHNjYW5uaW5nOiBmaW5kaW5nIEFSTnMgb3IgQ1ZFIElEc1xuICAgKlxuICAgKiBAZGVmYXVsdCAtIG5vIGZpbmRpbmdzIGlnbm9yZWRcbiAgICovXG4gIHJlYWRvbmx5IGlnbm9yZUZpbmRpbmdzPzogc3RyaW5nW107XG5cbiAgLyoqXG4gICAqIENvbmZpZ3VyYXRpb24gZm9yIHNjYW4gbG9ncyBvdXRwdXQuXG4gICAqXG4gICAqIEBkZWZhdWx0IC0gc2NhbiBsb2dzIG91dHB1dCB0byBkZWZhdWx0IGxvZyBncm91cCBjcmVhdGVkIGJ5IFNjYW5uZXIgTGFtYmRhLlxuICAgKi9cbiAgcmVhZG9ubHkgc2NhbkxvZ3NPdXRwdXQ/OiBTY2FuTG9nc091dHB1dDtcblxuICAvKipcbiAgICogU2lnbmF0dXJlIHZlcmlmaWNhdGlvbiBjb25maWd1cmF0aW9uIGZvciB0aGUgY29udGFpbmVyIGltYWdlLlxuICAgKlxuICAgKiBWZXJpZmllcyB0aGUgaW1hZ2Ugc2lnbmF0dXJlIGJlZm9yZSBzY2FubmluZyB1c2luZyBOb3RhdGlvbiAoQVdTIFNpZ25lcikgb3IgQ29zaWduIChTaWdzdG9yZSkuXG4gICAqXG4gICAqIEBkZWZhdWx0IC0gbm8gc2lnbmF0dXJlIHZlcmlmaWNhdGlvblxuICAgKi9cbiAgcmVhZG9ubHkgc2lnbmF0dXJlVmVyaWZpY2F0aW9uPzogU2lnbmF0dXJlVmVyaWZpY2F0aW9uO1xuXG4gIC8qKlxuICAgKiBUaGUgU2Nhbm5lciBMYW1iZGEgZnVuY3Rpb24ncyBkZWZhdWx0IGxvZyBncm91cC5cbiAgICpcbiAgICogSWYgeW91IHVzZSBFY3JTY2FuVmVyaWZpZXIgY29uc3RydWN0IG11bHRpcGxlIHRpbWVzIGluIHRoZSBzYW1lIHN0YWNrLFxuICAgKiB5b3UgbXVzdCBzcGVjaWZ5IHRoZSBzYW1lIGxvZyBncm91cCBmb3IgZWFjaCBjb25zdHJ1Y3QuXG4gICAqXG4gICAqIEBkZWZhdWx0IC0gU2Nhbm5lciBMYW1iZGEgY3JlYXRlcyB0aGUgZGVmYXVsdCBsb2cgZ3JvdXAuXG4gICAqL1xuICByZWFkb25seSBkZWZhdWx0TG9nR3JvdXA/OiBJTG9nR3JvdXA7XG5cbiAgLyoqXG4gICAqIFN1cHByZXNzIGVycm9ycyBkdXJpbmcgcm9sbGJhY2sgc2Nhbm5lciBMYW1iZGEgZXhlY3V0aW9uLlxuICAgKlxuICAgKiBAZGVmYXVsdCB0cnVlXG4gICAqL1xuICByZWFkb25seSBzdXBwcmVzc0Vycm9yT25Sb2xsYmFjaz86IGJvb2xlYW47XG5cbiAgLyoqXG4gICAqIFNOUyB0b3BpYyBmb3IgdnVsbmVyYWJpbGl0eSBub3RpZmljYXRpb24uXG4gICAqXG4gICAqIFN1cHBvcnRzIEFXUyBDaGF0Ym90IG1lc3NhZ2UgZm9ybWF0LlxuICAgKlxuICAgKiBAZGVmYXVsdCAtIG5vIG5vdGlmaWNhdGlvblxuICAgKi9cbiAgcmVhZG9ubHkgdnVsbnNOb3RpZmljYXRpb25Ub3BpYz86IElUb3BpYztcblxuICAvKipcbiAgICogQ29uc3RydWN0cyB0byBibG9jayBpZiB2dWxuZXJhYmlsaXRpZXMgYXJlIGRldGVjdGVkLlxuICAgKlxuICAgKiBAZGVmYXVsdCAtIG5vIGNvbnN0cnVjdHMgdG8gYmxvY2tcbiAgICovXG4gIHJlYWRvbmx5IGJsb2NrQ29uc3RydWN0cz86IElDb25zdHJ1Y3RbXTtcbn1cblxuLyoqXG4gKiBBIENvbnN0cnVjdCB0aGF0IHZlcmlmaWVzIGNvbnRhaW5lciBpbWFnZSBzY2FuIGZpbmRpbmdzIHdpdGggRUNSIGltYWdlIHNjYW5uaW5nLlxuICogSXQgdXNlcyBhIExhbWJkYSBmdW5jdGlvbiBhcyBhIEN1c3RvbSBSZXNvdXJjZSBwcm92aWRlciB0byBjYWxsIEVDUiBzY2FuIEFQSXNcbiAqIGFuZCBldmFsdWF0ZSBzY2FuIGZpbmRpbmdzLlxuICovXG5leHBvcnQgY2xhc3MgRWNyU2NhblZlcmlmaWVyIGV4dGVuZHMgQ29uc3RydWN0IHtcbiAgcHJpdmF0ZSByZWFkb25seSBkZWZhdWx0TG9nR3JvdXA/OiBJTG9nR3JvdXA7XG5cbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IEVjclNjYW5WZXJpZmllclByb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkKTtcblxuICAgIHRoaXMuZGVmYXVsdExvZ0dyb3VwID0gcHJvcHMuZGVmYXVsdExvZ0dyb3VwO1xuICAgIGNvbnN0IGxhbWJkYVB1cnBvc2UgPSAnQ3VzdG9tOjpFY3JTY2FuVmVyaWZpZXJDdXN0b21SZXNvdXJjZUxhbWJkYSc7XG5cbiAgICBjb25zdCBjdXN0b21SZXNvdXJjZUxhbWJkYSA9IG5ldyBTaW5nbGV0b25GdW5jdGlvbih0aGlzLCAnQ3VzdG9tUmVzb3VyY2VMYW1iZGEnLCB7XG4gICAgICB1dWlkOiAnYzU2Y2VlNmItNjc3NS01NDFiLWQxNzktYzE1MzVkODhhMGM4JyxcbiAgICAgIGxhbWJkYVB1cnBvc2UsXG4gICAgICBydW50aW1lOiBSdW50aW1lLkZST01fSU1BR0UsXG4gICAgICBoYW5kbGVyOiBIYW5kbGVyLkZST01fSU1BR0UsXG4gICAgICBjb2RlOiBBc3NldENvZGUuZnJvbUFzc2V0SW1hZ2Uoam9pbihfX2Rpcm5hbWUsICcuLi9hc3NldHMvbGFtYmRhJyksIHtcbiAgICAgICAgcGxhdGZvcm06IFBsYXRmb3JtLkxJTlVYX0FSTTY0LFxuICAgICAgICBpZ25vcmVNb2RlOiBJZ25vcmVNb2RlLkRPQ0tFUixcbiAgICAgIH0pLFxuICAgICAgYXJjaGl0ZWN0dXJlOiBBcmNoaXRlY3R1cmUuQVJNXzY0LFxuICAgICAgdGltZW91dDogRHVyYXRpb24uc2Vjb25kcyg5MDApLFxuICAgICAgcmV0cnlBdHRlbXB0czogMCxcbiAgICAgIGxvZ0dyb3VwOiB0aGlzLmRlZmF1bHRMb2dHcm91cCxcbiAgICB9KTtcblxuICAgIGNvbnN0IGltYWdlVGFnID0gcHJvcHMuaW1hZ2VUYWcgPz8gJ2xhdGVzdCc7XG5cbiAgICBjb25zdCBzY2FuQ29uZmlnT3V0cHV0ID0gcHJvcHMuc2NhbkNvbmZpZy5iaW5kKCk7XG5cbiAgICAvLyBWYWxpZGF0ZTogc2lnbmF0dXJlT25seSByZXF1aXJlcyBzaWduYXR1cmVWZXJpZmljYXRpb25cbiAgICBpZiAoc2NhbkNvbmZpZ091dHB1dC5zY2FuVHlwZSA9PT0gJ1NJR05BVFVSRV9PTkxZJyAmJiAhcHJvcHMuc2lnbmF0dXJlVmVyaWZpY2F0aW9uKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1NjYW5Db25maWcuc2lnbmF0dXJlT25seSgpIHJlcXVpcmVzIHNpZ25hdHVyZVZlcmlmaWNhdGlvbiB0byBiZSBzcGVjaWZpZWQuJyk7XG4gICAgfVxuXG4gICAgY29uc3Qgb3V0cHV0T3B0aW9ucyA9IHByb3BzLnNjYW5Mb2dzT3V0cHV0Py5iaW5kKGN1c3RvbVJlc291cmNlTGFtYmRhKTtcblxuICAgIC8vIFNCT00gb3V0cHV0IChmcm9tIHNjYW5Db25maWdPdXRwdXQpXG4gICAgY29uc3Qgc2JvbUNvbmZpZyA9IHNjYW5Db25maWdPdXRwdXQuc2JvbU91dHB1dD8uYmluZChjdXN0b21SZXNvdXJjZUxhbWJkYSk7XG5cbiAgICAvLyBTaWduYXR1cmUgdmVyaWZpY2F0aW9uXG4gICAgY29uc3Qgc2lnbmF0dXJlVmVyaWZpY2F0aW9uQ29uZmlnID0gcHJvcHMuc2lnbmF0dXJlVmVyaWZpY2F0aW9uPy5iaW5kKGN1c3RvbVJlc291cmNlTGFtYmRhKTtcblxuICAgIC8vIEVDUiBwZXJtaXNzaW9uc1xuICAgIC8vIERlc2NyaWJlSW1hZ2VzIGlzIGFsd2F5cyByZXF1aXJlZCAoZm9yIGRpZ2VzdCByZXNvbHV0aW9uKVxuICAgIC8vIERlc2NyaWJlSW1hZ2VTY2FuRmluZGluZ3MgaXMgb25seSByZXF1aXJlZCBmb3Igc2Nhbm5pbmcgbW9kZXNcbiAgICBjb25zdCBlY3JBY3Rpb25zID0gWydlY3I6RGVzY3JpYmVJbWFnZXMnXTtcbiAgICBpZiAoc2NhbkNvbmZpZ091dHB1dC5zY2FuVHlwZSAhPT0gJ1NJR05BVFVSRV9PTkxZJykge1xuICAgICAgZWNyQWN0aW9ucy5wdXNoKCdlY3I6RGVzY3JpYmVJbWFnZVNjYW5GaW5kaW5ncycpO1xuICAgIH1cbiAgICBjdXN0b21SZXNvdXJjZUxhbWJkYS5hZGRUb1JvbGVQb2xpY3koXG4gICAgICBuZXcgUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgYWN0aW9uczogZWNyQWN0aW9ucyxcbiAgICAgICAgcmVzb3VyY2VzOiBbcHJvcHMucmVwb3NpdG9yeS5yZXBvc2l0b3J5QXJuXSxcbiAgICAgIH0pLFxuICAgICk7XG5cbiAgICBpZiAoc2NhbkNvbmZpZ091dHB1dC5zY2FuVHlwZSA9PT0gJ0VOSEFOQ0VEJykge1xuICAgICAgY3VzdG9tUmVzb3VyY2VMYW1iZGEuYWRkVG9Sb2xlUG9saWN5KFxuICAgICAgICBuZXcgUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgICBhY3Rpb25zOiBbJ2luc3BlY3RvcjI6TGlzdENvdmVyYWdlJywgJ2luc3BlY3RvcjI6TGlzdEZpbmRpbmdzJ10sXG4gICAgICAgICAgcmVzb3VyY2VzOiBbJyonXSxcbiAgICAgICAgfSksXG4gICAgICApO1xuICAgIH1cblxuICAgIGlmIChzY2FuQ29uZmlnT3V0cHV0LnN0YXJ0U2Nhbikge1xuICAgICAgY3VzdG9tUmVzb3VyY2VMYW1iZGEuYWRkVG9Sb2xlUG9saWN5KFxuICAgICAgICBuZXcgUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgICBhY3Rpb25zOiBbJ2VjcjpTdGFydEltYWdlU2NhbiddLFxuICAgICAgICAgIHJlc291cmNlczogW3Byb3BzLnJlcG9zaXRvcnkucmVwb3NpdG9yeUFybl0sXG4gICAgICAgIH0pLFxuICAgICAgKTtcbiAgICB9XG5cbiAgICAvLyBTaWduYXR1cmUgdmVyaWZpY2F0aW9uIHBlcm1pc3Npb25zXG4gICAgaWYgKHNpZ25hdHVyZVZlcmlmaWNhdGlvbkNvbmZpZykge1xuICAgICAgY3VzdG9tUmVzb3VyY2VMYW1iZGEuYWRkVG9Sb2xlUG9saWN5KFxuICAgICAgICBuZXcgUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgICBhY3Rpb25zOiBbJ2VjcjpHZXRBdXRob3JpemF0aW9uVG9rZW4nXSxcbiAgICAgICAgICByZXNvdXJjZXM6IFsnKiddLFxuICAgICAgICB9KSxcbiAgICAgICk7XG4gICAgICBjdXN0b21SZXNvdXJjZUxhbWJkYS5hZGRUb1JvbGVQb2xpY3koXG4gICAgICAgIG5ldyBQb2xpY3lTdGF0ZW1lbnQoe1xuICAgICAgICAgIGFjdGlvbnM6IFsnZWNyOkJhdGNoR2V0SW1hZ2UnLCAnZWNyOkdldERvd25sb2FkVXJsRm9yTGF5ZXInXSxcbiAgICAgICAgICByZXNvdXJjZXM6IFtwcm9wcy5yZXBvc2l0b3J5LnJlcG9zaXRvcnlBcm5dLFxuICAgICAgICB9KSxcbiAgICAgICk7XG5cbiAgICAgIGlmIChzaWduYXR1cmVWZXJpZmljYXRpb25Db25maWcudHlwZSA9PT0gJ05PVEFUSU9OJykge1xuICAgICAgICBjdXN0b21SZXNvdXJjZUxhbWJkYS5hZGRUb1JvbGVQb2xpY3koXG4gICAgICAgICAgbmV3IFBvbGljeVN0YXRlbWVudCh7XG4gICAgICAgICAgICBhY3Rpb25zOiBbJ3NpZ25lcjpHZXRSZXZvY2F0aW9uU3RhdHVzJ10sXG4gICAgICAgICAgICByZXNvdXJjZXM6IFsnKiddLFxuICAgICAgICAgIH0pLFxuICAgICAgICApO1xuICAgICAgfVxuICAgICAgLy8gQ29zaWduIEtNUyBwZXJtaXNzaW9ucyBhcmUgZ3JhbnRlZCBieSBrZXkuZ3JhbnQoKSBpbiBiaW5kKClcbiAgICB9XG5cbiAgICAvLyBTQk9NIGV4cG9ydCBwZXJtaXNzaW9ucyAoSW5zcGVjdG9yIENyZWF0ZVNib21FeHBvcnQpXG4gICAgaWYgKHNib21Db25maWcpIHtcbiAgICAgIGN1c3RvbVJlc291cmNlTGFtYmRhLmFkZFRvUm9sZVBvbGljeShcbiAgICAgICAgbmV3IFBvbGljeVN0YXRlbWVudCh7XG4gICAgICAgICAgYWN0aW9uczogWydpbnNwZWN0b3IyOkNyZWF0ZVNib21FeHBvcnQnLCAnaW5zcGVjdG9yMjpHZXRTYm9tRXhwb3J0J10sXG4gICAgICAgICAgcmVzb3VyY2VzOiBbJyonXSxcbiAgICAgICAgfSksXG4gICAgICApO1xuICAgIH1cblxuICAgIGlmIChwcm9wcy52dWxuc05vdGlmaWNhdGlvblRvcGljKSB7XG4gICAgICBwcm9wcy52dWxuc05vdGlmaWNhdGlvblRvcGljLmdyYW50UHVibGlzaChjdXN0b21SZXNvdXJjZUxhbWJkYSk7XG4gICAgfVxuXG4gICAgY29uc3Qgc3VwcHJlc3NFcnJvck9uUm9sbGJhY2sgPSBwcm9wcy5zdXBwcmVzc0Vycm9yT25Sb2xsYmFjayA/PyB0cnVlO1xuICAgIGlmIChzdXBwcmVzc0Vycm9yT25Sb2xsYmFjaykge1xuICAgICAgY3VzdG9tUmVzb3VyY2VMYW1iZGEuYWRkVG9Sb2xlUG9saWN5KFxuICAgICAgICBuZXcgUG9saWN5U3RhdGVtZW50KHtcbiAgICAgICAgICBhY3Rpb25zOiBbJ2Nsb3VkZm9ybWF0aW9uOkRlc2NyaWJlU3RhY2tzJ10sXG4gICAgICAgICAgcmVzb3VyY2VzOiBbU3RhY2sub2YodGhpcykuc3RhY2tJZF0sXG4gICAgICAgIH0pLFxuICAgICAgKTtcbiAgICB9XG5cbiAgICAvLyBDaGVjayBmb3IgZGVmYXVsdExvZ0dyb3VwIGNvbnNpc3RlbmN5IGFjcm9zcyBtdWx0aXBsZSBpbnN0YW5jZXMgaW4gdGhlIHNhbWUgc3RhY2tcbiAgICBBc3BlY3RzLm9mKFN0YWNrLm9mKHRoaXMpKS5hZGQoe1xuICAgICAgdmlzaXQ6IChub2RlKSA9PiB7XG4gICAgICAgIGlmIChcbiAgICAgICAgICBub2RlIGluc3RhbmNlb2YgRWNyU2NhblZlcmlmaWVyICYmXG4gICAgICAgICAgbm9kZS5fZGVmYXVsdExvZ0dyb3VwPy5ub2RlLnBhdGggIT09IHRoaXMuZGVmYXVsdExvZ0dyb3VwPy5ub2RlLnBhdGhcbiAgICAgICAgKSB7XG4gICAgICAgICAgQW5ub3RhdGlvbnMub2YodGhpcykuYWRkV2FybmluZ1YyKFxuICAgICAgICAgICAgJ0BlY3Itc2Nhbi12ZXJpZmllcjpkdXBsaWNhdGVMYW1iZGFEZWZhdWx0TG9nR3JvdXAnLFxuICAgICAgICAgICAgXCJZb3UgaGF2ZSB0byBzZXQgdGhlIHNhbWUgbG9nIGdyb3VwIGZvciAnZGVmYXVsdExvZ0dyb3VwJyBmb3IgZWFjaCBFY3JTY2FuVmVyaWZpZXIgY29uc3RydWN0IGluIHRoZSBzYW1lIHN0YWNrLlwiLFxuICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfSk7XG5cbiAgICBjb25zdCB2ZXJpZmllclByb3ZpZGVyID0gbmV3IFByb3ZpZGVyKHRoaXMsICdQcm92aWRlcicsIHtcbiAgICAgIG9uRXZlbnRIYW5kbGVyOiBjdXN0b21SZXNvdXJjZUxhbWJkYSxcbiAgICB9KTtcblxuICAgIGNvbnN0IHZlcmlmaWVyUHJvcGVydGllczogU2Nhbm5lckN1c3RvbVJlc291cmNlUHJvcHMgPSB7XG4gICAgICBhZGRyOiB0aGlzLm5vZGUuYWRkcixcbiAgICAgIHJlcG9zaXRvcnlOYW1lOiBwcm9wcy5yZXBvc2l0b3J5LnJlcG9zaXRvcnlOYW1lLFxuICAgICAgaW1hZ2VUYWcsXG4gICAgICBzY2FuVHlwZTogc2NhbkNvbmZpZ091dHB1dC5zY2FuVHlwZSxcbiAgICAgIHN0YXJ0U2NhbjogU3RyaW5nKHNjYW5Db25maWdPdXRwdXQuc3RhcnRTY2FuKSxcbiAgICAgIHNldmVyaXR5OiBwcm9wcy5zZXZlcml0eSA/PyBbU2V2ZXJpdHkuQ1JJVElDQUxdLFxuICAgICAgZmFpbE9uVnVsbmVyYWJpbGl0eTogU3RyaW5nKHByb3BzLmZhaWxPblZ1bG5lcmFiaWxpdHkgPz8gdHJ1ZSksXG4gICAgICBpZ25vcmVGaW5kaW5nczogcHJvcHMuaWdub3JlRmluZGluZ3MgPz8gW10sXG4gICAgICBvdXRwdXQ6IG91dHB1dE9wdGlvbnMsXG4gICAgICBzYm9tOiBzYm9tQ29uZmlnLFxuICAgICAgc2lnbmF0dXJlVmVyaWZpY2F0aW9uOiBzaWduYXR1cmVWZXJpZmljYXRpb25Db25maWdcbiAgICAgICAgPyB7XG4gICAgICAgICAgICB0eXBlOiBzaWduYXR1cmVWZXJpZmljYXRpb25Db25maWcudHlwZSxcbiAgICAgICAgICAgIHRydXN0ZWRJZGVudGl0aWVzOiBzaWduYXR1cmVWZXJpZmljYXRpb25Db25maWcudHJ1c3RlZElkZW50aXRpZXMsXG4gICAgICAgICAgICBwdWJsaWNLZXk6IHNpZ25hdHVyZVZlcmlmaWNhdGlvbkNvbmZpZy5wdWJsaWNLZXksXG4gICAgICAgICAgICBrbXNLZXlBcm46IHNpZ25hdHVyZVZlcmlmaWNhdGlvbkNvbmZpZy5rbXNLZXlBcm4sXG4gICAgICAgICAgICBmYWlsT25VbnNpZ25lZDogU3RyaW5nKHNpZ25hdHVyZVZlcmlmaWNhdGlvbkNvbmZpZy5mYWlsT25VbnNpZ25lZCksXG4gICAgICAgICAgfVxuICAgICAgICA6IHVuZGVmaW5lZCxcbiAgICAgIHN1cHByZXNzRXJyb3JPblJvbGxiYWNrOiBTdHJpbmcoc3VwcHJlc3NFcnJvck9uUm9sbGJhY2spLFxuICAgICAgdnVsbnNUb3BpY0FybjogcHJvcHMudnVsbnNOb3RpZmljYXRpb25Ub3BpYz8udG9waWNBcm4sXG4gICAgICBkZWZhdWx0TG9nR3JvdXBOYW1lOlxuICAgICAgICB0aGlzLmRlZmF1bHRMb2dHcm91cD8ubG9nR3JvdXBOYW1lID8/IGAvYXdzL2xhbWJkYS8ke2N1c3RvbVJlc291cmNlTGFtYmRhLmZ1bmN0aW9uTmFtZX1gLFxuICAgIH07XG5cbiAgICBuZXcgQ3VzdG9tUmVzb3VyY2UodGhpcywgJ1Jlc291cmNlJywge1xuICAgICAgcmVzb3VyY2VUeXBlOiAnQ3VzdG9tOjpFY3JTY2FuVmVyaWZpZXInLFxuICAgICAgcHJvcGVydGllczogdmVyaWZpZXJQcm9wZXJ0aWVzLFxuICAgICAgc2VydmljZVRva2VuOiB2ZXJpZmllclByb3ZpZGVyLnNlcnZpY2VUb2tlbixcbiAgICB9KTtcblxuICAgIHByb3BzLmJsb2NrQ29uc3RydWN0cz8uZm9yRWFjaCgoY29uc3RydWN0KSA9PiB7XG4gICAgICBjb25zdHJ1Y3Qubm9kZS5hZGREZXBlbmRlbmN5KHRoaXMpO1xuICAgIH0pO1xuICB9XG5cbiAgLyoqIEBpbnRlcm5hbCAqL1xuICBnZXQgX2RlZmF1bHRMb2dHcm91cCgpOiBJTG9nR3JvdXAgfCB1bmRlZmluZWQge1xuICAgIHJldHVybiB0aGlzLmRlZmF1bHRMb2dHcm91cDtcbiAgfVxufVxuIl19
|
package/lib/sbom-output.js
CHANGED
|
@@ -30,7 +30,7 @@ class SbomOutput {
|
|
|
30
30
|
}
|
|
31
31
|
exports.SbomOutput = SbomOutput;
|
|
32
32
|
_a = JSII_RTTI_SYMBOL_1;
|
|
33
|
-
SbomOutput[_a] = { fqn: "ecr-scan-verifier.SbomOutput", version: "0.1.
|
|
33
|
+
SbomOutput[_a] = { fqn: "ecr-scan-verifier.SbomOutput", version: "0.1.2" };
|
|
34
34
|
class SbomOutputImpl extends SbomOutput {
|
|
35
35
|
constructor(props, format) {
|
|
36
36
|
super();
|
package/lib/scan-config.js
CHANGED
|
@@ -44,7 +44,7 @@ class ScanConfig {
|
|
|
44
44
|
}
|
|
45
45
|
exports.ScanConfig = ScanConfig;
|
|
46
46
|
_a = JSII_RTTI_SYMBOL_1;
|
|
47
|
-
ScanConfig[_a] = { fqn: "ecr-scan-verifier.ScanConfig", version: "0.1.
|
|
47
|
+
ScanConfig[_a] = { fqn: "ecr-scan-verifier.ScanConfig", version: "0.1.2" };
|
|
48
48
|
class BasicScanConfig extends ScanConfig {
|
|
49
49
|
constructor(options) {
|
|
50
50
|
super();
|
package/lib/scan-logs-output.js
CHANGED
|
@@ -43,7 +43,7 @@ class ScanLogsOutput {
|
|
|
43
43
|
}
|
|
44
44
|
exports.ScanLogsOutput = ScanLogsOutput;
|
|
45
45
|
_a = JSII_RTTI_SYMBOL_1;
|
|
46
|
-
ScanLogsOutput[_a] = { fqn: "ecr-scan-verifier.ScanLogsOutput", version: "0.1.
|
|
46
|
+
ScanLogsOutput[_a] = { fqn: "ecr-scan-verifier.ScanLogsOutput", version: "0.1.2" };
|
|
47
47
|
class CloudWatchLogsOutput extends ScanLogsOutput {
|
|
48
48
|
constructor(options) {
|
|
49
49
|
super();
|
|
@@ -47,7 +47,7 @@ class SignatureVerification {
|
|
|
47
47
|
}
|
|
48
48
|
exports.SignatureVerification = SignatureVerification;
|
|
49
49
|
_a = JSII_RTTI_SYMBOL_1;
|
|
50
|
-
SignatureVerification[_a] = { fqn: "ecr-scan-verifier.SignatureVerification", version: "0.1.
|
|
50
|
+
SignatureVerification[_a] = { fqn: "ecr-scan-verifier.SignatureVerification", version: "0.1.2" };
|
|
51
51
|
class NotationSignatureVerification extends SignatureVerification {
|
|
52
52
|
constructor(options) {
|
|
53
53
|
super();
|
package/package.json
CHANGED
package/scripts/integ.sh
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared helpers for ecr-scan-verifier integ test orchestration.
|
|
3
|
+
#
|
|
4
|
+
# Source this from a shell or from the /integ-test skill:
|
|
5
|
+
# . scripts/integ.sh
|
|
6
|
+
#
|
|
7
|
+
# Functions are intentionally small and idempotent so the skill can chain
|
|
8
|
+
# them without re-deriving region lists, account ids, or polling loops.
|
|
9
|
+
|
|
10
|
+
set -u
|
|
11
|
+
|
|
12
|
+
REGIONS=(us-east-1 us-east-2 us-west-2)
|
|
13
|
+
|
|
14
|
+
account_id() {
|
|
15
|
+
aws sts get-caller-identity --query Account --output text
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
default_region() {
|
|
19
|
+
aws configure get region
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# --- Inspector (Enhanced scanning) ------------------------------------------
|
|
23
|
+
|
|
24
|
+
inspector_status() {
|
|
25
|
+
local region="$1"
|
|
26
|
+
aws inspector2 batch-get-account-status \
|
|
27
|
+
--region "$region" \
|
|
28
|
+
--query 'accounts[0].resourceState.ecr.status' \
|
|
29
|
+
--output text
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
inspector_status_all() {
|
|
33
|
+
for region in "${REGIONS[@]}"; do
|
|
34
|
+
echo "$region: $(inspector_status "$region")"
|
|
35
|
+
done
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
inspector_enable_all() {
|
|
39
|
+
for region in "${REGIONS[@]}"; do
|
|
40
|
+
aws inspector2 enable --resource-types ECR --region "$region"
|
|
41
|
+
done
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
inspector_disable_all() {
|
|
45
|
+
for region in "${REGIONS[@]}"; do
|
|
46
|
+
aws inspector2 disable --resource-types ECR --region "$region"
|
|
47
|
+
done
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Poll one region until inspector status equals $target. Cap at 5 min so we
|
|
51
|
+
# fail loudly on stuck transitions instead of hanging.
|
|
52
|
+
wait_inspector_status() {
|
|
53
|
+
local region="$1" target="$2" i=0
|
|
54
|
+
while [ "$(inspector_status "$region")" != "$target" ]; do
|
|
55
|
+
i=$((i + 1))
|
|
56
|
+
if [ "$i" -ge 20 ]; then
|
|
57
|
+
echo "ERROR: $region did not reach $target after 5 min" >&2
|
|
58
|
+
return 1
|
|
59
|
+
fi
|
|
60
|
+
sleep 15
|
|
61
|
+
done
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
wait_inspector_status_all() {
|
|
65
|
+
local target="$1"
|
|
66
|
+
for region in "${REGIONS[@]}"; do
|
|
67
|
+
wait_inspector_status "$region" "$target" || return 1
|
|
68
|
+
done
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# --- scan-on-push on the CDK bootstrap asset repo ---------------------------
|
|
72
|
+
|
|
73
|
+
# Usage: scan_on_push_set true|false
|
|
74
|
+
scan_on_push_set() {
|
|
75
|
+
local enabled="$1"
|
|
76
|
+
local account
|
|
77
|
+
account="$(account_id)"
|
|
78
|
+
for region in "${REGIONS[@]}"; do
|
|
79
|
+
aws ecr put-image-scanning-configuration \
|
|
80
|
+
--repository-name "cdk-hnb659fds-container-assets-${account}-${region}" \
|
|
81
|
+
--image-scanning-configuration "scanOnPush=${enabled}" \
|
|
82
|
+
--region "$region"
|
|
83
|
+
done
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# --- Enhanced engine warmup (DISABLED -> ENABLED) ---------------------------
|
|
87
|
+
#
|
|
88
|
+
# `batch-get-account-status` flipping to ENABLED is not the same as the
|
|
89
|
+
# scanning engine being ready — empirically the lag is often 20-30 min on
|
|
90
|
+
# a fresh enable. wait_enhanced_engine_warmup absorbs that with a long
|
|
91
|
+
# initial sleep; enhanced_run_with_retry then retries the test up to N
|
|
92
|
+
# more times with a fixed gap between attempts.
|
|
93
|
+
|
|
94
|
+
# Initial wait after a DISABLED -> ENABLED transition. Skip if already
|
|
95
|
+
# enabled (no transition happened).
|
|
96
|
+
# Args: $1 = ORIGINAL_STATE (ENABLED|DISABLED), $2 = seconds (default 1200)
|
|
97
|
+
wait_enhanced_engine_warmup() {
|
|
98
|
+
local original_state="$1" secs="${2:-1200}"
|
|
99
|
+
if [ "$original_state" = "DISABLED" ]; then
|
|
100
|
+
echo "Inspector engine warmup: sleeping ${secs}s after fresh enable..."
|
|
101
|
+
sleep "$secs"
|
|
102
|
+
fi
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Run `$@` (the test command). On failure, sleep $RETRY_GAP_SECS and retry,
|
|
106
|
+
# up to $MAX_ATTEMPTS total attempts. Defaults: 3 attempts, 600s gap.
|
|
107
|
+
# Tuned for "Inspector engine still warming up" — NOT for catching flaky
|
|
108
|
+
# tests. If real findings change, surface them; don't burn time retrying.
|
|
109
|
+
enhanced_run_with_retry() {
|
|
110
|
+
local max="${MAX_ATTEMPTS:-3}" gap="${RETRY_GAP_SECS:-600}" attempt=1
|
|
111
|
+
while true; do
|
|
112
|
+
echo "Attempt ${attempt}/${max}: $*"
|
|
113
|
+
if "$@"; then
|
|
114
|
+
return 0
|
|
115
|
+
fi
|
|
116
|
+
if [ "$attempt" -ge "$max" ]; then
|
|
117
|
+
echo "ERROR: failed after ${max} attempts across ~$((max * gap / 60)) min of waits." >&2
|
|
118
|
+
echo " Stop waiting — the cause is not propagation lag." >&2
|
|
119
|
+
return 1
|
|
120
|
+
fi
|
|
121
|
+
echo "Attempt ${attempt} failed. Sleeping ${gap}s before retry..."
|
|
122
|
+
sleep "$gap"
|
|
123
|
+
attempt=$((attempt + 1))
|
|
124
|
+
done
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# --- Signature test cleanup -------------------------------------------------
|
|
128
|
+
#
|
|
129
|
+
# Cleans up shared signature-test artifacts. AWS Signer profile is left
|
|
130
|
+
# Active on purpose (cancellation is permanent and would block future runs).
|
|
131
|
+
|
|
132
|
+
cleanup_signature_artifacts() {
|
|
133
|
+
local key_id
|
|
134
|
+
key_id="$(aws ssm get-parameter --name /ecr-scan-verifier/cosign-kms-key-id \
|
|
135
|
+
--query 'Parameter.Value' --output text 2>/dev/null || true)"
|
|
136
|
+
|
|
137
|
+
aws ssm delete-parameter --name /ecr-scan-verifier/cosign-kms-key-id 2>/dev/null || true
|
|
138
|
+
aws ssm delete-parameter --name /ecr-scan-verifier/cosign-public-key 2>/dev/null || true
|
|
139
|
+
|
|
140
|
+
if [ -n "$key_id" ]; then
|
|
141
|
+
aws kms schedule-key-deletion --key-id "$key_id" --pending-window-in-days 7
|
|
142
|
+
else
|
|
143
|
+
echo "WARN: KMS key id not in SSM. Ask the user before scheduling deletion." >&2
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
rm -f cosign.key cosign.pub
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# --- signature-ecr-signing repo setup / teardown ----------------------------
|
|
150
|
+
|
|
151
|
+
ECR_SIGNING_REPO_NAME="${ECR_SIGNING_REPO_NAME:-ecr-scan-verifier-integ-ecr-signing}"
|
|
152
|
+
SIGNER_PROFILE_NAME="${SIGNER_PROFILE_NAME:-EcrScanVerifierTestProfile}"
|
|
153
|
+
|
|
154
|
+
# Idempotent-ish wrapper. `put-signing-profile` is NOT actually idempotent
|
|
155
|
+
# (returns ProfileAlreadyExists for an existing Active profile), so check
|
|
156
|
+
# first and only create if missing. Echoes the ARN on stdout.
|
|
157
|
+
# Returns non-zero if the profile cannot be ensured — callers using
|
|
158
|
+
# `var=$(signer_profile_ensure)` should also check `[ -n "$var" ]` since
|
|
159
|
+
# command substitution swallows the inner exit code by default.
|
|
160
|
+
signer_profile_ensure() {
|
|
161
|
+
local arn
|
|
162
|
+
arn="$(aws signer get-signing-profile --profile-name "$SIGNER_PROFILE_NAME" \
|
|
163
|
+
--query 'arn' --output text 2>/dev/null || true)"
|
|
164
|
+
if [ -z "$arn" ] || [ "$arn" = "None" ]; then
|
|
165
|
+
aws signer put-signing-profile \
|
|
166
|
+
--profile-name "$SIGNER_PROFILE_NAME" \
|
|
167
|
+
--platform-id Notation-OCI-SHA384-ECDSA >&2 || return 1
|
|
168
|
+
arn="$(aws signer get-signing-profile --profile-name "$SIGNER_PROFILE_NAME" \
|
|
169
|
+
--query 'arn' --output text 2>/dev/null || true)"
|
|
170
|
+
fi
|
|
171
|
+
if [ -z "$arn" ] || [ "$arn" = "None" ]; then
|
|
172
|
+
echo "ERROR: signer_profile_ensure: could not resolve ARN for $SIGNER_PROFILE_NAME" >&2
|
|
173
|
+
return 1
|
|
174
|
+
fi
|
|
175
|
+
echo "$arn"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# Build the cosign signing-config the Lambda verifier expects: no rekor
|
|
179
|
+
# (transparency log), no fulcio CA, no OIDC, no TSA. cosign 3.x will try
|
|
180
|
+
# keyless flows even with --key if these fields are present, which manifests
|
|
181
|
+
# as a hung "Signing artifact..." with no further output. Stripping them
|
|
182
|
+
# lets a `--key`-based sign complete.
|
|
183
|
+
cosign_minimal_signing_config() {
|
|
184
|
+
local out="${1:-/tmp/signing-config.json}"
|
|
185
|
+
curl -fsSL https://raw.githubusercontent.com/sigstore/root-signing/refs/heads/main/targets/signing_config.v0.2.json | \
|
|
186
|
+
jq 'del(.rekorTlogUrls, .oidcUrls, .caUrls, .tsaUrls)' > "$out"
|
|
187
|
+
echo "$out"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
ecr_signing_setup() {
|
|
191
|
+
local region profile_arn
|
|
192
|
+
region="$(default_region)"
|
|
193
|
+
profile_arn="$(signer_profile_ensure)"
|
|
194
|
+
aws ecr create-repository --repository-name "$ECR_SIGNING_REPO_NAME" 2>/dev/null || true
|
|
195
|
+
|
|
196
|
+
local cfg=/tmp/ecr-scan-verifier-signing-config.json
|
|
197
|
+
cat > "$cfg" <<EOF
|
|
198
|
+
{
|
|
199
|
+
"rules": [
|
|
200
|
+
{
|
|
201
|
+
"signingProfileArn": "${profile_arn}",
|
|
202
|
+
"repositoryFilters": [
|
|
203
|
+
{ "filter": "${ECR_SIGNING_REPO_NAME}", "filterType": "WILDCARD_MATCH" }
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
EOF
|
|
209
|
+
aws ecr put-signing-configuration --region "$region" \
|
|
210
|
+
--signing-configuration "file://${cfg}"
|
|
211
|
+
aws ecr get-signing-configuration --region "$region"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
ecr_signing_teardown() {
|
|
215
|
+
local region cfg=/tmp/ecr-scan-verifier-signing-config-empty.json
|
|
216
|
+
region="$(default_region)"
|
|
217
|
+
printf '{ "rules": [] }\n' > "$cfg"
|
|
218
|
+
aws ecr put-signing-configuration --region "$region" \
|
|
219
|
+
--signing-configuration "file://${cfg}" || true
|
|
220
|
+
aws ecr delete-repository --repository-name "$ECR_SIGNING_REPO_NAME" --force || true
|
|
221
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# verify-integ-docs.sh
|
|
3
|
+
#
|
|
4
|
+
# Verifies that the three sources documenting the integ workflow stay in
|
|
5
|
+
# sync with each other:
|
|
6
|
+
#
|
|
7
|
+
# .claude/skills/integ-test/SKILL.md — automated (Claude Code skill)
|
|
8
|
+
# test/integ/README.md — manual (human-readable docs)
|
|
9
|
+
# scripts/integ.sh — shell helpers shared by both
|
|
10
|
+
#
|
|
11
|
+
# Both docs must exist (skill = automated, README = manual; deleting one
|
|
12
|
+
# would leave the other lying about its counterpart). Both must mention
|
|
13
|
+
# every helper that lives in scripts/integ.sh, and both must agree on
|
|
14
|
+
# the mode list.
|
|
15
|
+
#
|
|
16
|
+
# Run via the /verify-integ-docs skill, which flips the `integ-docs`
|
|
17
|
+
# markgate marker after this passes.
|
|
18
|
+
|
|
19
|
+
set -u
|
|
20
|
+
|
|
21
|
+
SKILL=".claude/skills/integ-test/SKILL.md"
|
|
22
|
+
README="test/integ/README.md"
|
|
23
|
+
HELPERS="scripts/integ.sh"
|
|
24
|
+
|
|
25
|
+
fails=0
|
|
26
|
+
fail() { echo "FAIL: $*" >&2; fails=$((fails + 1)); }
|
|
27
|
+
ok() { echo "ok: $*"; }
|
|
28
|
+
|
|
29
|
+
# --- 1. Source files exist --------------------------------------------------
|
|
30
|
+
|
|
31
|
+
for f in "$SKILL" "$README" "$HELPERS"; do
|
|
32
|
+
if [ ! -f "$f" ]; then
|
|
33
|
+
fail "missing source file: $f"
|
|
34
|
+
fi
|
|
35
|
+
done
|
|
36
|
+
[ "$fails" -eq 0 ] || { echo "Aborting; source files missing." >&2; exit 1; }
|
|
37
|
+
ok "all three source files exist"
|
|
38
|
+
|
|
39
|
+
# --- 2. Mode parity ---------------------------------------------------------
|
|
40
|
+
# Every mode in the skill's argument-hint must appear in both:
|
|
41
|
+
# - the SKILL's `## Arguments` list (one `- \`mode\` — desc` line each)
|
|
42
|
+
# - the README's `### Modes` table (one `| \`mode\` |` row each)
|
|
43
|
+
|
|
44
|
+
modes_line=$(grep -E '^argument-hint:' "$SKILL" | head -1)
|
|
45
|
+
modes=$(printf '%s\n' "$modes_line" | grep -oE '<[^>]+>' | head -1 | tr -d '<>' | tr '|' '\n')
|
|
46
|
+
|
|
47
|
+
if [ -z "$modes" ]; then
|
|
48
|
+
fail "could not parse modes from SKILL argument-hint"
|
|
49
|
+
else
|
|
50
|
+
for mode in $modes; do
|
|
51
|
+
grep -qE "^- \`$mode\`" "$SKILL" \
|
|
52
|
+
|| fail "mode '$mode' in argument-hint but missing from SKILL '## Arguments' list"
|
|
53
|
+
grep -qE "\| \`$mode\` \|" "$README" \
|
|
54
|
+
|| fail "mode '$mode' in argument-hint but missing from README modes table"
|
|
55
|
+
done
|
|
56
|
+
[ "$fails" -gt 0 ] || ok "all argument-hint modes present in SKILL Arguments + README table"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# --- 3. Helper function parity ---------------------------------------------
|
|
60
|
+
# Every shell function defined in scripts/integ.sh should be referenced
|
|
61
|
+
# from at least one of SKILL/README (catches dead helpers and rename drift).
|
|
62
|
+
|
|
63
|
+
functions=$(grep -E '^[a-z_][a-z0-9_]*\(\)' "$HELPERS" | sed 's/().*//')
|
|
64
|
+
if [ -z "$functions" ]; then
|
|
65
|
+
fail "could not parse any functions from $HELPERS"
|
|
66
|
+
else
|
|
67
|
+
for fn in $functions; do
|
|
68
|
+
if ! grep -qw "$fn" "$SKILL" && ! grep -qw "$fn" "$README"; then
|
|
69
|
+
fail "helper '$fn' defined in $HELPERS but not referenced from SKILL or README"
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
[ "$fails" -gt 0 ] || ok "every helper function is referenced from at least one doc"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# --- 4. Both docs reference scripts/integ.sh -------------------------------
|
|
76
|
+
|
|
77
|
+
for f in "$SKILL" "$README"; do
|
|
78
|
+
grep -q "scripts/integ.sh" "$f" \
|
|
79
|
+
|| fail "$f does not reference scripts/integ.sh"
|
|
80
|
+
done
|
|
81
|
+
[ "$fails" -gt 0 ] || ok "both docs reference scripts/integ.sh"
|
|
82
|
+
|
|
83
|
+
# --- 5. No raw `aws signer put-signing-profile` ----------------------------
|
|
84
|
+
# Raw form is NOT idempotent (returns ProfileAlreadyExists on existing
|
|
85
|
+
# Active profile). Use signer_profile_ensure instead.
|
|
86
|
+
|
|
87
|
+
for f in "$SKILL" "$README"; do
|
|
88
|
+
if grep -nE "aws signer put-signing-profile" "$f" >/dev/null; then
|
|
89
|
+
fail "$f has raw 'aws signer put-signing-profile' — use signer_profile_ensure (raw form is NOT idempotent)"
|
|
90
|
+
fi
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
# --- 6. No insufficient signing-config jq strip ----------------------------
|
|
94
|
+
# cosign 3.x silently hangs when --key is combined with a signing-config
|
|
95
|
+
# that still has oidc/ca/tsa fields. The correct strip is rekor+oidc+ca+tsa,
|
|
96
|
+
# which lives in cosign_minimal_signing_config.
|
|
97
|
+
|
|
98
|
+
for f in "$SKILL" "$README"; do
|
|
99
|
+
# Match `jq 'del(.rekorTlogUrls)'` (the OLD broken strip) — but not
|
|
100
|
+
# `del(.rekorTlogUrls, .oidcUrls, .caUrls, .tsaUrls)` (the helper's correct strip).
|
|
101
|
+
if grep -E "del\(\\.rekorTlogUrls\)" "$f" >/dev/null; then
|
|
102
|
+
fail "$f has insufficient 'del(.rekorTlogUrls)' — use cosign_minimal_signing_config (also strips oidc/ca/tsa)"
|
|
103
|
+
fi
|
|
104
|
+
done
|
|
105
|
+
|
|
106
|
+
# --- 7. signature mode list matches integ.signature/integ.<name>.ts -------
|
|
107
|
+
# For every signature-* mode, the referenced integ test source must exist.
|
|
108
|
+
# Check .ts (the source) not .js (the build artifact) — a fresh clone
|
|
109
|
+
# without `pnpm tsc` would otherwise false-fail.
|
|
110
|
+
|
|
111
|
+
for mode in $(printf '%s\n' "$modes" | grep '^signature-'); do
|
|
112
|
+
# signature-notation -> test/integ/signature/integ.notation.ts
|
|
113
|
+
fname=$(printf '%s' "$mode" | sed 's/^signature-//')
|
|
114
|
+
testfile="test/integ/signature/integ.${fname}.ts"
|
|
115
|
+
if [ ! -f "$testfile" ]; then
|
|
116
|
+
fail "mode '$mode' references missing test source $testfile"
|
|
117
|
+
fi
|
|
118
|
+
done
|
|
119
|
+
|
|
120
|
+
# --- 8. Final report -------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
if [ "$fails" -gt 0 ]; then
|
|
123
|
+
echo "" >&2
|
|
124
|
+
echo "verify-integ-docs: $fails failure(s). Fix above before setting integ-docs marker." >&2
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
echo ""
|
|
129
|
+
echo "verify-integ-docs: all consistency checks passed."
|