Add an AWS deployment planning skill for Prebid Server Go - #1166
ChristianPavilonis wants to merge 13 commits into
Conversation
Gather deployment requirements before choosing AWS services and generating Terraform or runtime files. Keep cloud execution behind separate approval and document safe testing, state ownership, and credential handling. Refs #1163
aram356
left a comment
There was a problem hiding this comment.
This is not a complete review, but I wanted to make sure we keep the CLI clean and consistent. We already have a prebid subcommand, so I would recommend building on top of that.
For the new command:
ts prebid server ...
And the existing command would become:
ts prebid client ...
prk-Jr
left a comment
There was a problem hiding this comment.
Summary
Adds an agent planning skill for self-hosted Prebid Server Go on AWS, a new experimental ts prebid server CLI namespace (local inspection and validation, guarded Secrets Manager writes, EC2 status), and renames ts prebid bundle to ts prebid client. The CLI subsystem is carefully built — sanitized &'static str error payloads, an injected Interaction trait so tests can't touch a terminal, secrets kept out of argv, owner-only temp payloads cleaned up on both success and failure — and the negative-path test suite is genuinely strong. All 20 CI checks pass.
One blocking item: .tool-versions pins every tool except the AWS CLI this PR adds, which is exactly the binary the credential-write path shells out to.
3 of the inline comments below carry a one-click GitHub
suggestion— use Commit suggestion (or Add suggestion to batch for several at once) to apply them as commits on the PR branch. Each was verified in an isolated worktree, individually and as a batch, againstcargo fmt --all -- --check, both host-targettrusted-server-cliclippy invocations with-D warnings, andcargo test --package trusted-server-cli. The remaining comments describe the fix in prose because the change touches multiple files, lands outside the diff hunks, or didn't survivecargo fmtin the reviewed form.
Blocking
🔧 wrench
- AWS CLI pinned to
latestwhile every other tool is exact — see inline at.tool-versions:6
Non-blocking
♻️ refactor
rpassword/serde_yaml_ngbypass workspace dependency inheritance — see inline atcrates/trusted-server-cli/Cargo.toml:27- The determinism check compares a pure function against itself — see inline at
crates/trusted-server-cli/src/commands/pbs/config.rs:378 - Hand-rolled JSON escaping round-trip in
bidder_list— see inline atcrates/trusted-server-cli/src/commands/pbs/inspect.rs:91(suggestion)
🤔 thinking
ts prebid bundle→ts prebid clientis a breaking rename with no alias — see inline atcrates/trusted-server-cli/src/run.rs:75(suggestion)- Transport failure on
put-secret-valuedoesn't flag the outcome as uncertain — see inline atcrates/trusted-server-cli/src/commands/pbs/secrets.rs:173 - A dated
Status: Implementedspec is rewritten retroactively — see inline atdocs/superpowers/specs/2026-06-17-prebid-bundle-cli-design.md:5 - The
ts prebid servernamespace is undocumented indocs/— see inline atdocs/guide/cli.md:274 - Missing
python3makes two tests pass for the wrong reason — see inline atcrates/trusted-server-cli/tests/pbs_cli.rs:23 identifier()rejects AWS profile names containing.— see inline atcrates/trusted-server-cli/src/commands/pbs/config.rs:104
⛏ nitpick
- Unparenthesized
&&/||on the noninteractive-write gate — see inline atcrates/trusted-server-cli/src/commands/pbs/secrets.rs:112(suggestion)
🌱 seedling
- Unbounded recursion over operator YAML — see inline at
crates/trusted-server-cli/src/commands/pbs/config.rs:318
📝 note
- Identity failure aborts the whole status report; resource-query failure degrades — see inline at
crates/trusted-server-cli/src/commands/pbs/status.rs:26
👍 praise
- Negative-path test discipline — see inline at
crates/trusted-server-cli/tests/pbs_cli.rs:39
Cross-cutting / body-level findings
- 📌 Three separable concerns in one PR — this bundles (a) agent-only markdown under
.claude/skills/with zero runtime impact, (b) a ~1800-LOC experimental CLI subsystem that writes AWS credentials, and (c) a breaking rename of an already-shipped command. They have different audiences, different risk profiles, and different revert stories: the rename is the one most likely to need a fast follow-up or a release note, and it's currently welded to a large feature branch. Not a change request on this PR — but if the rename landed separately it could be communicated and reverted on its own cadence. Flagging underAGENTS.md's "every change should impact as little code as possible".
CI Status
- integration tests (Fastly EC lifecycle): PASS
- integration tests: PASS
- browser integration tests: PASS
- CodeQL: PASS
- cargo test (ts CLI, native): PASS
- vitest: PASS
- format-typescript: PASS (required)
- Analyze (javascript-typescript): PASS
- cargo fmt: PASS (required)
- cargo test (axum native): PASS
- Analyze (rust): PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo test: PASS (required)
- format-docs: PASS (required)
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo test (cross-adapter parity): PASS
- CLAUDE.md symlink guard: PASS
- prepare integration artifacts: PASS
- Analyze (actions): PASS
No failed, cancelled, or pending checks.
jevansnyc
left a comment
There was a problem hiding this comment.
Nothing major so posting in here as single comment:
Breaking rename with no alias — [run.rs:76] ts prebid bundle became ts prebid client. Any existing script gets error: unrecognized subcommand 'bundle'. Confirmed against the built binary. Either add a hidden alias or call the break out in the PR description.
.tool-versions aws plugin name is wrong — [.tool-versions:6] The entry is aws 2.36.45, but asdf/mise call that plugin awscli. asdf install fails on the exact onboarding path the docs point at. CI is unaffected since the workflows grep only rust/nodejs/viceroy.
Regional module has no provider pin — [modules/regional/terraform.tf:3] No AWS provider version constraint, and [.gitignore:3] excludes its lock file, yet RUNBOOK.md tells operators to init/test inside the module. So the module test runs against an unpinned provider that will drift away from the root's = 6.64.0.
Confirm prompt rejects long input instead of declining — [pbs/mod.rs:228] Terminal::confirm caps the answer at 16 bytes and returns "input exceeds size limit" rather than treating it as a no. A 17-character answer makes the operator re-enter the secret through the hidden prompt.
CPU alarm pages on stopped hosts — [modules/regional/monitoring.tf:13] The per-instance high-CPU alarm sets treat_missing_data = "breaching", so a stopped or replaced instance pages as saturated.
ONE QUESTION for Christian:
The test plan lists terraform fmt, init, and validate, but not terraform test, even though both .tftest.hcl files ship and the README/RUNBOOK instruct operators to run them. Worth confirming those two suites actually ran. validate leaves module inputs unknown and evaluates very little of the plan.
prk-Jr
left a comment
There was a problem hiding this comment.
Summary
Reviewed commit 74bdc5b57e5a8d3cca8174d9ccc7684f87466cbe.
The secret-write boundary handles account checks, payload privacy, confirmation, and uncertain outcomes carefully. The discovery command, however, reads the retired server configuration layout and misses server bidders in current valid configurations.
Blocking
- 🔧 Read server demand from the current auction schema — see inline at
crates/trusted-server-cli/src/commands/pbs/inspect.rs:134–140.
Validation
The unmodified production PBS module was imported into an isolated harness. Inspecting a current-format provider/bidder fixture returned an empty server-candidate list, false endpoint presence, null test mode, and zero override rules. This was a module harness, not a build of the complete CLI.
Terraform formatting and validation, five root mock tests, two regional mock tests, Compose configuration, shell syntax, and both example descriptor checks passed. No live AWS calls, deployments, or secret writes were performed. Full Rust/JS/adapter gates rely on remote CI.
CI Status
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- browser integration tests: PASS
- CodeQL: PASS
- cargo test (axum native): PASS
- cargo test (ts CLI, native): PASS
- format-typescript: PASS (required)
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- Analyze (rust): PASS
- cargo test: PASS (required)
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- format-docs: PASS (required)
- Analyze (actions): PASS
- cargo fmt: PASS (required)
- Analyze (javascript-typescript): PASS
- CLAUDE.md symlink guard: PASS
- cargo test (cross-adapter parity): PASS
- prepare integration artifacts: PASS
- vitest: PASS
- Analyze (javascript-typescript): PASS
aram356
left a comment
There was a problem hiding this comment.
Summary
Adds an explicitly-invoked AWS planning skill for Prebid Server Go, an experimental ts prebid server CLI namespace (inspect / check / secrets set / status), and a locally-checked two-region EC2/Compose Terraform example. Reviewed at 18d1b4877.
Verification performed in an isolated worktree rather than by reading alone: built and ran the CLI live, ran ./scripts/test-cli.sh (239 tests pass), cargo fmt --all -- --check and target-matched clippy (both clean), exercised aws configure get history semantics against a real AWS CLI v2, ran the shipped Terraform through fmt/validate/test, and reproduced each defect below end-to-end.
The secret-handling path holds up under scrutiny: payloads stay out of argv, temporary request files are owner-only and removed on every error path via Drop, AWS CLI history is checked on both cli_history and default.cli_history (I confirmed the two-key probe catches the [default]-inherited case), and account / replica / ARN verification all fail closed. The redaction tests genuinely hold.
Two blocking items below. Neither is a live production defect: one is a removed command with no compatibility path, the other is a regression guard that does not guard.
2 of the inline comments carry a one-click GitHub
suggestion. Both were applied in a scratch worktree and verified in isolation (fmt, clippy, full 239-test CLI suite, byte-exact post-verify drift check). The remaining comments describe fixes in prose because they span multiple assertions or files.
Blocking
🔧 wrench
security_unit_test.tftest.hcldoes not defend the invariants it is named for — see inline atdeploy/pbs-example/modules/regional/tests/security_unit_test.tftest.hcl:36ts prebid bundleremoved with no alias — silent breaking change — see inline atcrates/trusted-server-cli/src/run.rs:75
Non-blocking
♻️ refactor / 🤔 thinking / ⛏ nitpick
pinned_image()accepts uppercase digests that Docker rejects — see inline atcrates/trusted-server-cli/src/commands/pbs/config.rs:275- Retry guidance is wrong when a request token is reused with different content — see inline at
crates/trusted-server-cli/src/commands/pbs/aws.rs:79 - I/O errors name no path, so a multi-file descriptor failure is unactionable — see inline at
crates/trusted-server-cli/src/commands/pbs/mod.rs:150 - ALB egress rule contradicts its own description — see inline at
deploy/pbs-example/modules/regional/security.tf:18 --deploymentis the only PBS flag with empty help text — see inline atcrates/trusted-server-cli/src/commands/pbs/secrets.rs:23
Cross-cutting / body-level findings
-
📌 Committed Terraform lock file carries a single platform hash —
deploy/pbs-example/.terraform.lock.hcl:7records oneh1:hash. On a non-matching platformterraform initsilently rewrites the file, and aterraform testafter restoring it fails with "does not match any of the checksums recorded in the dependency lock file". This contradictsRUNBOOK.md:8("use the committed AWS provider lock file") andRUNBOOK.md:70("review any lock-file change"): operators on other platforms get a spurious diff on every run, which trains them to rubber-stamp lock changes. Regenerate withterraform providers lock -platform=darwin_arm64 -platform=darwin_amd64 -platform=linux_amd64 -platform=linux_arm64. -
🌱 No ALB access logging —
deploy/pbs-example/modules/regional/load_balancing.tf:1-13has noaccess_logsblock anywhere in the tree. For an internet-facing ALB in a deliberately "production-shaped" reference there is no request-level forensic record.drop_invalid_header_fields = trueandidle_timeout = 30are both set, which makes the omission stand out. Either add anaccess_logsblock or state inDEPLOYMENT_PLAN.mdthat it is deliberately out of scope — the current silence reads as an oversight. -
🌱 No VPC endpoints, so SSM and Secrets Manager traffic exits via NAT — there is no
aws_vpc_endpointin the tree. The instance role attachesAmazonSSMManagedInstanceCoreand reads Secrets Manager (modules/regional/secrets.tf:42-62), but with no interface endpoints forssm,ssmmessages,ec2messages,secretsmanager, orkms, that control-plane and credential traffic traverses the public internet and incurs per-GB NAT cost on every secret fetch. Notable for a design whose stated benefit is private hosts with regionally isolated credentials. -
📝
AmazonSSMManagedInstanceCoreis the one broad IAM grant — the inline policy atmodules/regional/secrets.tf:47-69is tight (Resourceenumerates the declared secret ARNs, KMS is conditionally scoped to one key, no wildcards). The AWS-managed SSM policy is the deliberate exception and carries"Resource": "*". Standard practice and hard to avoid, but worth a comment in an example that markets least-privilege. -
⛏ Six new markdown files are not prettier-clean —
crates/trusted-server-cli/README.md, fourreferences/*.md, anddeploy/pbs-example/DEPLOYMENT_PLAN.mdfailprettier --checkunderdocs/.prettierrc(mostly misaligned table pipes). No CI gate covers this:.github/workflows/format.yml:121-125scopesformat-docstodocs/, and nothing in CI touches.claude/,crates/**/README.md, ordeploy/. Raised only because the other new markdown files in this PR are clean, so the inconsistency is internal. Do not extend the CI gate for this. -
⛏ Terraform 1.16.2 is pinned in docs and HCL but not in
.tool-versions—RUNBOOK.md:8anddeploy/pbs-example/terraform.tf(required_version = ">= 1.16.2, < 1.17.0") agree, but.tool-versionshas noterraformentry while every other toolchain in the repo is asdf-pinned.references/terraform.md:29advises selecting a reproducible Terraform version "in the repository's toolchain", so the skill's own guidance is not followed by the example it ships. -
⛏
crates/trusted-server-cli/README.md:5will be stale on merge — "The namespace is experimental and is being evaluated in PR review." Prefer a durable phrasing such as "experimental; the interface may change without a deprecation cycle". -
⛏
deploy/pbs-example/runtime/compose.yaml:6publishes on all interfaces —"${PBS_HOST_PORT:-8000}:8000"renders without a host-IP restriction, so a smoke-test PBS instance is reachable from the local network. The smoke script itself only ever talks to127.0.0.1. Consider"127.0.0.1:${PBS_HOST_PORT:-8000}:8000"for the example.
CI Status
- cargo fmt: PASS (required)
- cargo test: PASS (required)
- format-docs: PASS (required)
- format-typescript: PASS (required)
- cargo test (ts CLI, native): PASS
- cargo test (axum native): PASS
- cargo test (cross-adapter parity): PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- vitest: PASS
- CodeQL: PASS
- Analyze (rust): PASS
- Analyze (javascript-typescript): PASS
- Analyze (actions): PASS
- CLAUDE.md symlink guard: PASS
- prepare integration artifacts: PENDING
No failing checks. CI is not a factor in this verdict.
| } | ||
|
|
||
| run "plans_private_hosts_and_scoped_ingress" { | ||
| command = plan |
There was a problem hiding this comment.
🔧 wrench — This file is named security_unit_test and RUNBOOK.md:31 presents it as the gate on broad ingress and secret policy, but it is blind to the four highest-value invariants in the module.
All four are implemented correctly in the source — this is a regression hole, not a live defect:
| Invariant | Where it is implemented | Asserted here? |
|---|---|---|
| IMDSv2 required | compute.tf:15 http_tokens = "required" |
no |
| Root EBS encrypted | compute.tf:21 encrypted = true |
no |
| Private subnet placement | compute.tf:7 aws_subnet.private[...] |
no |
| TLS 1.3/1.2 listener policy | load_balancing.tf:52 |
no |
Each was mutated in a scratch copy and the suite still reported 2 passed, 0 failed on all four. Only the ingress-CIDR rule is genuinely locked in (breaking it does fail the suite).
The assertion at line 44 looks like it covers host exposure but is near-vacuous:
condition = alltrue([for instance in aws_instance.pbs : instance.associate_public_ip_address == false])It re-reads the literal false written two lines away at compute.tf:9; it does not constrain subnet placement, which is what actually determines reachability.
Proposed fix (apply manually — adds four assertions to the existing run "plans_private_hosts_and_scoped_ingress" block; the exact set is a judgement call so it is not offered as a one-click suggestion):
assert {
condition = alltrue([for i in aws_instance.pbs : i.metadata_options[0].http_tokens == "required"])
error_message = "PBS hosts should require IMDSv2 session tokens."
}
assert {
condition = alltrue([for i in aws_instance.pbs : i.root_block_device[0].encrypted])
error_message = "PBS host root volumes should be encrypted."
}
assert {
condition = alltrue([for az, i in aws_instance.pbs : i.subnet_id == aws_subnet.private[az].id])
error_message = "PBS hosts should launch only in private subnets."
}
assert {
condition = aws_lb_listener.https.ssl_policy == "ELBSecurityPolicy-TLS13-1-2-2021-06"
error_message = "The ALB listener should pin the TLS 1.3/1.2 policy."
}A security test that stays green through four broken invariants is worse than no test, because the next person to touch this module will trust it.
| && !image.contains('@') | ||
| && !image.chars().any(char::is_whitespace) | ||
| && digest.len() == 64 | ||
| && digest.bytes().all(|byte| byte.is_ascii_hexdigit()) |
There was a problem hiding this comment.
♻️ refactor — is_ascii_hexdigit() accepts A-F, but OCI image digests are lowercase-only. A descriptor pinned with an uppercase digest passes ts prebid server check and then fails at container pull time with invalid checksum digest format.
Reproduced end-to-end against the shipped example with the digest uppercased:
$ ts prebid server check --deployment deployment.yaml --json
{ "local_checks": "passed", ... }
exit=0
Docker rejects the same reference:
$ docker pull alpine@sha256:AAAA...
invalid checksum digest format
Since the whole point of pinned_image() is that the operator has a pullable, immutable reference, this is worth catching locally rather than on the host.
Verified: with the change applied, the uppercase descriptor is rejected (exit=2), both shipped examples (crates/trusted-server-cli/examples/pbs/deployment.yaml and deploy/pbs-example/deployment.example.yaml) still pass, fmt and clippy clean, full CLI suite 239 tests pass, drift check clean.
| && digest.bytes().all(|byte| byte.is_ascii_hexdigit()) | |
| && digest | |
| .bytes() | |
| .all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f')) |
| drop(payload); | ||
| if !output.status.success() { | ||
| let message = if operation == "put-secret-value" { | ||
| "write outcome uncertain; retain the request token and retry identical input" |
There was a problem hiding this comment.
🤔 thinking — This message is wrong for one of the two ways put-secret-value commonly fails.
AWS treats ClientRequestToken as an idempotency key: reusing a token with the same SecretString is a no-op returning the existing version, while reusing it with different content returns ResourceExistsException. --request-token help (secrets.rs:37) documents this correctly: "Reuse with identical values only."
But when the operator does violate it, they get:
write outcome uncertain; retain the request token and retry identical input
Both halves mislead. The outcome is not uncertain — AWS definitively rejected the write and nothing changed. And "retry identical input" is the wrong remedy: their input is precisely what is not identical. The correct action is to issue a new token for genuinely new content.
I recognise the constraint here — withholding raw AWS stderr is a deliberate and correct secret-safety decision (PbsError::Aws), so the code cannot cheaply distinguish a timeout from a token collision. Two options, both compatible with that policy:
- Broaden the wording to cover both cases, e.g. "write not confirmed; if retrying identical input reuse this token, otherwise issue a new token for changed values".
- Inspect the AWS CLI exit status or a narrowly-matched error code without forwarding the message body, and emit a distinct
PbsErrorvariant for the collision.
Not blocking, but an operator hitting this at 3am will follow the instruction literally and retry unchanged, which cannot succeed.
| /// # Errors | ||
| /// Returns an I/O error or rejects oversized/non-UTF-8 input. | ||
| pub(super) fn read_text(path: &Path, limit: usize) -> Result<String> { | ||
| let file = File::open(path).map_err(|_| Report::new(PbsError::Io("cannot open input file")))?; |
There was a problem hiding this comment.
🤔 thinking — PbsError::Io("cannot open input file") is the same string for every file the descriptor pulls in, and it names none of them. A descriptor references the PBS config, a bindings file, and one overrides file per region, so a typo in any of four-plus paths produces an identical, unactionable message.
Reproduced against the shipped example — three distinct missing files, one indistinguishable error:
$ rm pbs.yaml && ts prebid server check --deployment deployment.yaml
[ts] PBS I/O failed: cannot open input file
$ rm east.yaml && ts prebid server check --deployment deployment.yaml
[ts] PBS I/O failed: cannot open input file
$ ts prebid server check --deployment nope.yaml
[ts] PBS I/O failed: cannot open input file
The sanitization rationale in the module docs is about file contents — parser snippets and AWS stderr can carry credential values. A path the operator typed into their own descriptor is not secret, and inspect already echoes "source": path into its JSON report (inspect.rs:177), so path disclosure is established behaviour in this module.
Proposed fix (apply manually — PbsError::Io holds &'static str, so attaching a runtime path means either adding a variant or using error-stack's attach, which touches the enum and several call sites):
// at the read_text call site
File::open(path)
.map_err(|_| Report::new(PbsError::Io("cannot open input file")))
.attach_printable_lazy(|| format!("path: {}", path.display()))?attach keeps the sanitized Display string intact while giving the operator the failing path in the report.
| egress { | ||
| description = "Forward requests to private PBS hosts" | ||
| from_port = 0 | ||
| to_port = 0 | ||
| protocol = "-1" | ||
| cidr_blocks = ["0.0.0.0/0"] | ||
| } |
There was a problem hiding this comment.
♻️ refactor — The description says "Forward requests to private PBS hosts", but the rule permits all protocols to the entire internet. The ALB only ever needs to reach the PBS hosts on var.pbs_port.
Worth contrasting with the PBS host SG at security.tf:44-50, whose 0.0.0.0/0 egress is justified and correctly described ("Bidder and AWS API access through the per-AZ NAT gateway") — bidder endpoints are arbitrary public addresses. The ALB has no such requirement.
Proposed fix (apply manually — a referenced_security_group_id here creates a dependency cycle with the PBS SG's security_groups = [aws_security_group.alb.id] ingress at security.tf:41, so the rule has to move out of the inline block into a standalone resource, which is also the modern provider-6.x idiom):
resource "aws_vpc_security_group_egress_rule" "alb_to_pbs" {
description = "Forward requests to private PBS hosts"
security_group_id = aws_security_group.alb.id
referenced_security_group_id = aws_security_group.pbs.id
from_port = var.pbs_port
to_port = var.pbs_port
ip_protocol = "tcp"
}Then drop the inline egress block from aws_security_group.alb. Note that mixing inline blocks with standalone rules on the same SG is unsupported, so the ALB SG must use one style throughout.
| #[arg(long)] | ||
| pub deployment: PathBuf, |
There was a problem hiding this comment.
⛏ nitpick — --deployment is the only flag in the entire ts prebid server namespace without a doc comment, so it renders with empty help text:
Options:
--deployment <DEPLOYMENT>
--region <REGION> Exactly one declared region; replicas cannot be written independently
--file <FILE> Read a complete JSON string-valued object from this file; never changes the file
TargetArgs::deployment in mod.rs:51-52 already has the right wording, and the path-resolution semantics it describes matter here too, since every path inside the descriptor resolves relative to this file.
| #[arg(long)] | |
| pub deployment: PathBuf, | |
| /// Deployment descriptor; paths inside it are relative to this file. | |
| #[arg(long)] | |
| pub deployment: PathBuf, |
Separately: pub is wider than needed. mod.rs:134 is the only consumer, so pub(super) would match the module's otherwise-tight visibility and AGENTS.md's "avoid unnecessary pub". Left out of the suggestion above since it is a separate concern.
Summary
ts prebid: browser bundle generation ists prebid client, while self-hosted Prebid Server operations are underts prebid server.deploy/pbs-example/.The earlier post-merge inspection compatibility blocker is resolved at the current head. The CLI tests now pass against the current configuration schema.
Changes
.claude/skills/planning-prebid-aws/ts prebid server.crates/trusted-server-cli/src/commands/pbs/crates/trusted-server-cli/src/run.rsts prebid.crates/trusted-server-cli/tests/pbs_cli.rsand module testscrates/trusted-server-cli/README.mdandexamples/pbs/deploy/pbs-example/ts prebid bundlereferences withts prebid client..tool-versionsCLI layout
Client
This generates the publisher-specific browser bundle. It replaces the former
ts prebid bundlecommand.Server
ts prebid server inspect --config <file>ts prebid server check --deployment <file>ts prebid server secrets set <bidder> --deployment <file> --region <region>ts prebid server status --deployment <file>Add
--jsonanywhere underts prebid serverfor machine-readable output.Secret values stay out of process arguments and reports. AWS request payloads use owner-only temporary files on Unix and are removed on normal success and error paths. Raw AWS stderr is withheld. Abrupt termination can leave temporary files, so operators must use protected temporary storage. Windows ACL behavior has not been validated.
Example deployment
deploy/pbs-example/is a committed, locally checked example. It models:us-east-1andus-west-2.v4.7.0pinned to a verified image digest.The example uses fictional account, certificate, hosted-zone, AMI, instance, CIDR, and bidder values. It is not deployable as-is. It has no remote Terraform backend, WAF, runtime secret loader, deployment command, rollback command, or load-test evidence.
Boundaries
The deployment descriptor supports
ec2-composeonly. The planning skill may recommend ECS or another approved architecture, but that requires separate CLI support.There are no
deployorrollbackserver subcommands. This PR does not provision infrastructure, install a runtime secret loader, replace containers, adopt sandbox state, activate bidders, or change caller traffic. The example files document those deferred operations rather than presenting them as implemented.The planning skill now treats AWS RTB Fabric as an optional outbound path per bidder and region. It records partner participation and acceptance, PBS endpoint mapping, regional quotas and timeouts, cost, fallback, monitoring, and Terraform link lifecycle limitations. It does not provision gateways or links, automate partner acceptance, or replace the current EC2/Compose descriptor.
Live AWS operations still require explicit operator authorization. No real AWS calls or secret writes were made during implementation.
Open questions
ec2-compose, or should this skill stay focused on the currently supported Compose/EC2 path? ECS would improve managed task replacement and deployment behavior, but it adds a second runtime and CLI contract.ts prebid server statusdescriptor accepts explicit instance IDs, not ASG membership.Test plan
./scripts/test-cli.shcargo fmt --all -- --checkcargo clippy --package trusted-server-cli --all-targets --target x86_64-unknown-linux-gnu -- -D warningscd crates/trusted-server-js/lib && npm run formatcd crates/trusted-server-js/lib && npx vitest runwith 901 tests passingcd crates/trusted-server-js/lib && node build-all.mjsts prebid,ts prebid client --help, andts prebid server --helpterraform fmt -check -recursive deploy/pbs-exampleterraform -chdir=deploy/pbs-example init -backend=false -input=falseterraform -chdir=deploy/pbs-example validatecargo run_cli_linux prebid server check --deployment deploy/pbs-example/deployment.yaml --jsondocker compose --env-file deploy/pbs-example/runtime/examples/compose.env -f deploy/pbs-example/runtime/compose.yaml config --quietdeploy/pbs-example/runtime/secret-bindings.jsonwith Pythondeploy/pbs-test/remains untracked and untouchedDeferred evidence
Checklist
disable-model-invocation: true.unwrap()calls.Closes
Closes #1163