Add Trusted Server CLI technical spec#655
Conversation
aram356
left a comment
There was a problem hiding this comment.
Summary
Solid v1 spec — config-first model, strong local/remote separation, and conservative non-destructive provisioning are the right calls. The structure follows existing docs/superpowers/specs/ conventions cleanly. However, two architectural gaps will block implementation as written, and two open questions need answers before Phase 5 is actionable. Findings filed inline; cross-cutting items below.
Blocking
🔧 wrench
- Resource identity round-trip is undefined — §13.8 / §13.10 / §17.1: adoption requires "explicit identification" but the v1 happy path (
ts audit→ts provision fastly applyagainst a fresh service) has no resources to identify yet. Spec must pick: name-based identity, ID-based with auto-write-back totrusted-server.tomlafter first apply, or manual ID fill-in. (inline at L838) - Relationship to PR #633 (existing JS asset auditor) unanalyzed — the header references #633, but §10 / §17.3 never say whether
ts auditwraps that Node+Playwright tool, reimplements it in Rust, or supersedes it. Materially changes scope and contradicts the "single Rust binary" framing iftsshells out to Node. (inline at L536)
❓ question
- Source of truth for the remote KV/config/secret store inventory — §13.4 lists these as in-scope but
trusted-server.tomlonly carries store IDs forrequest_signing+ aconsent_storename today. Where doests provision fastlyenumerate remote stores?[fastly.stores]section, hardcoded map, integration-derived? (inline at L745) - Interaction with PR #647 (runtime config store) — §6.1 asserts
trusted-server.tomlis the source of truth, but #647 makes the deployed config store a runtime read source too. Doesapplypushtrusted-server.tomlinto the config store? How is drift handled? (inline at L164)
Non-blocking
🤔 thinking
--service-idrequired on every provisioning command is poor UX —fastly.tomlalready hasservice_id; default to that. (inline at L735)ts devvalue overfastly compute serveis unspecified — §11.5 calls it "intentionally thin" without saying what work it actually does. (inline at L602)- Phase ordering contradicts persona priority — §4 ranks dev workflow #1; §18.2 ships
ts devlast (Phase 6). (inline at L1107)
♻️ refactor
- Drift / stale-entry accumulation — non-destructive + idempotent leaves stale remote entries when integrations are removed locally; document the manual-cleanup expectation in §13.7. (inline at L813)
--forcegranularity ints audit— pick a canonical pattern (either--no-config --forceor--force-js-assets/--force-config). (inline at L472)
🌱 seedling
- Distribution and installation model unspecified —
cargo install? Homebrew? Pre-built binaries? (inline at L147) - Multiple Fastly profiles / accounts —
--profileflag deferred to v2; spec should call out the v1 single-profile assumption. (inline at L676)
📝 note
- PR body checklist wrongly mentions
tracing— repo useslogmacros perCLAUDE.md. Fix.github/pull_request_template.mdseparately. (inline at L1)
CI Status
- cargo fmt: PASS
- clippy / Analyze (rust): PASS
- cargo test: PASS
- vitest: PASS
- format-typescript: PASS
- format-docs: PASS
- integration tests: PASS
- browser integration tests: PASS
- CodeQL / Analyze (actions, javascript-typescript): PASS
| - update | ||
|
|
||
| The CLI must not silently seize vaguely matching resources. | ||
|
|
There was a problem hiding this comment.
🔧 wrench — Resource identity round-trip is undefined and blocks the audit→provision workflow.
§13.8 says adoption requires "explicitly identified" resources, §13.10 acknowledges identity (name vs. ID) is mixed today, and §17.1 leaves it open — but the v1 happy path in §19.1/§19.2 has the user run ts audit (or ts config init) and then ts provision fastly apply against a fresh service. On a fresh service no resources exist, so adoption-by-ID is impossible. The spec must pick one before Phase 5 is implementable:
applycreates resources and writes provider-assigned IDs back intotrusted-server.toml(the spec is silent on this auto-update), or- resources are identified by name (but §13.10 says current behavior is mixed and likely needs cleanup), or
- users manually fill IDs after a partial first run (poor UX, currently unspecified).
Suggested resolution: add a §13.10 sub-decision such as “v1 identifies KV/config/secret stores by name; the CLI writes back any provider-assigned IDs to trusted-server.toml after the first successful apply,” and remove the corresponding ambiguity from §17.1.
| - Chromiumoxide | ||
| - another suitable browser automation approach | ||
|
|
||
| Browser engine choice is an implementation concern and remains open for evaluation during implementation. |
There was a problem hiding this comment.
🔧 wrench — Relationship to PR #633 (existing JS asset auditor) is not analyzed.
PR #633 already implements a Playwright-based JS asset auditor at packages/js-asset-auditor/ (Node, ~2229 LOC) that targets a single URL and emits js-assets.toml plus trusted-server.toml. The spec references #633 in the header but never says whether ts audit:
- shells out to / wraps the existing tool,
- reimplements equivalent behavior in Rust, or
- supersedes Add JS Asset Auditor plugin with Playwright CLI #633 (and that PR should be reframed).
This materially changes scope (a wrapper is days; a Rust reimplementation is weeks) and tensions with the §5 "single Rust binary" model if ts shells out to Node. §17.3 talks about Playwright vs. Chromiumoxide as if the choice is greenfield, ignoring the existing implementation.
Suggested resolution: add a §10.X sub-section explicitly addressing the packages/js-asset-auditor relationship, including which v1 path is chosen and what happens to PR #633.
| - secret stores | ||
| - KV stores | ||
| - bindings/linkage of those resources to the Fastly Compute service | ||
|
|
There was a problem hiding this comment.
❓ question — What is the source of truth for the remote KV/config/secret store inventory?
§13.4 lists config/secret/KV stores as in-scope for provisioning, but trusted-server.toml today only contains store IDs for request_signing (config_store_id, secret_store_id) and a consent_store name in [consent]. The actual KV stores Trusted Server uses (creative_store, consent_store, signing_keys) are declared in fastly.toml under local_server.kv_stores — but that file is the local simulator, not a remote inventory.
Where does ts provision fastly enumerate what to ensure remotely?
- Is
trusted-server.tomlextended with a new[fastly.stores]section? - Is the inventory hardcoded in the CLI's Fastly provider module?
- Is it inferred from enabled integrations + a known mapping?
This is a prerequisite for any plan/apply implementation and should be answered before Phase 5.
| - `ts dev ...` runs from the local config file | ||
| - `ts provision ...` derives remote provider state from the local config file | ||
|
|
||
| Remote provider state is applied or synchronized from the config; it is not the primary source of truth. |
There was a problem hiding this comment.
❓ question — Interaction with PR #647 (runtime config store) is unaddressed.
The header references PR #647 ("Add config store runtime config spec"), which moves application config from build-time embedding to a runtime Fastly config store. §6.1 then asserts trusted-server.toml is the source of truth — but #647's premise is that the deployed config store also becomes a source the runtime reads at request time.
The spec does not say:
- whether
ts provision fastly applypushestrusted-server.tomlinto the runtime config store, - how drift between local TOML and remote store is detected/reconciled,
- which side wins on subsequent runs after manual edits in the Fastly dashboard.
This is a real architectural question, not just doc cleanup. Please reconcile with #647 before merging.
| --service-id <id> | ||
| ``` | ||
|
|
||
| In v1, this flag is required on every provisioning command. |
There was a problem hiding this comment.
🤔 thinking — --service-id required on every provisioning command is poor UX given fastly.toml already has it.
fastly.toml in this repo contains service_id = "dysUw6h73VzeomD61eal85" for the deployed adapter package. Mandating --service-id on every plan/apply invocation duplicates a value already pinned in the repo.
Suggested behavior: read fastly.toml service_id if present; require --service-id only as an explicit override or when fastly.toml is missing/multi-service. This matches how fastly compute publish already behaves and avoids a footgun where operators paste the wrong ID.
| - prune remote entries not represented in local config | ||
| - destroy bindings | ||
| - perform implicit destructive reconciliation | ||
|
|
There was a problem hiding this comment.
♻️ refactor — Drift / stale-entry accumulation is not addressed.
Non-destructive + idempotent means: when an operator removes an integration from trusted-server.toml, its previously-provisioned remote entries (KV records, store contents) remain. There is no documented cleanup path in v1.
Suggested addition: a brief note in §13.7 — "Stale entries are not pruned in v1; operators must clean up manually via the Fastly dashboard or API. Safe pruning is future work tracked separately." This sets expectations and prevents surprise the first time someone disables an integration in production.
|
|
||
| - fail if either output path already exists | ||
| - `--force` allows overwrite | ||
| - users may suppress either file with dedicated flags |
There was a problem hiding this comment.
♻️ refactor — --force granularity in ts audit.
A common workflow is "re-audit JS assets but keep my curated trusted-server.toml." With a single --force that overwrites both files, the user must either delete js-assets.toml first or pass --no-config --force. Two cleaner options:
- document that
--no-config --forceis the canonical pattern for this case, or - add
--force-js-assetsand--force-configfor granular control.
Either is fine; pick one and call it out so the implementation isn't ambiguous.
| - **Implementation language:** Rust | ||
| - **Argument parser:** `clap` | ||
|
|
||
| The CLI is a single binary with multiple subcommands, not multiple standalone tools. |
There was a problem hiding this comment.
🌱 seedling — Distribution and installation model is unspecified.
Spec doesn't say how ts ships: cargo install trusted-server-cli? Homebrew tap? Pre-built binaries via GitHub releases? This affects v1 adoption (developers don't want to build from source) and CI onboarding (cold-cache cargo install of a Rust+Playwright binary is slow). Worth a sub-section under §5 or in §18 before implementation starts.
|
|
||
| - `login` fails | ||
| - users should use `FASTLY_API_KEY` instead | ||
|
|
There was a problem hiding this comment.
🌱 seedling — Multiple Fastly profiles / accounts.
The official fastly CLI supports profiles via ~/.fastly/config.toml. The spec doesn't address whether ts auth fastly login creates one credential per host or per profile. For users juggling personal + work Fastly accounts (common in this org) this becomes friction quickly. Consider a --profile flag in v2 and an explicit "single profile in v1" statement now.
| @@ -0,0 +1,1180 @@ | |||
| # Technical Specification: Trusted Server CLI (`ts`) | |||
There was a problem hiding this comment.
📝 note — The PR body's checklist line "Uses tracing macros (not println!)" is wrong for this repo. CLAUDE.md mandates log macros (with log-fastly as the Fastly backend), not tracing. Worth fixing .github/pull_request_template.md in a follow-up so future PRs don't get the same incorrect checklist. Does not affect the spec content.
Summary
ts)Changes
docs/superpowers/specs/2026-04-23-trusted-server-cli-design.mdCloses
Closes #664
Test plan
cargo test --workspacecargo clippy --workspace --all-targets --all-features -- -D warningscargo fmt --all -- --checkcd crates/js/lib && npx vitest runcd crates/js/lib && npm run formatcd docs && npm run formatcargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1fastly compute serveChecklist
unwrap()in production code — useexpect("should ...")tracingmacros (notprintln!)