Skip to content

Add Trusted Server CLI technical spec#655

Open
ChristianPavilonis wants to merge 1 commit intomainfrom
spec/ts-cli
Open

Add Trusted Server CLI technical spec#655
ChristianPavilonis wants to merge 1 commit intomainfrom
spec/ts-cli

Conversation

@ChristianPavilonis
Copy link
Copy Markdown
Collaborator

@ChristianPavilonis ChristianPavilonis commented Apr 23, 2026

Summary

  • add a new technical spec for the Trusted Server CLI (ts)
  • define the v1 command surface for config, audit, dev, Fastly auth, and Fastly provisioning
  • capture safety guarantees, automation/JSON behavior, and open design questions for future provider support

Changes

File Change
docs/superpowers/specs/2026-04-23-trusted-server-cli-design.md Add the Trusted Server CLI technical specification, including goals, command surface, shared conventions, Fastly auth/provisioning behavior, and phased implementation plan

Closes

Closes #664

Test plan

  • cargo test --workspace
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo fmt --all -- --check
  • JS tests: cd crates/js/lib && npx vitest run
  • JS format: cd crates/js/lib && npm run format
  • Docs format: cd docs && npm run format
  • WASM build: cargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1
  • Manual testing via fastly compute serve
  • Other: documentation-only change; verified the new spec file and command surface in the written doc

Checklist

  • Changes follow CLAUDE.md conventions
  • No unwrap() in production code — use expect("should ...")
  • Uses tracing macros (not println!)
  • New code has tests
  • No secrets or credentials committed

@ChristianPavilonis ChristianPavilonis self-assigned this Apr 23, 2026
@ChristianPavilonis ChristianPavilonis linked an issue Apr 27, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Collaborator

@aram356 aram356 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 auditts provision fastly apply against a fresh service) has no resources to identify yet. Spec must pick: name-based identity, ID-based with auto-write-back to trusted-server.toml after 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 audit wraps that Node+Playwright tool, reimplements it in Rust, or supersedes it. Materially changes scope and contradicts the "single Rust binary" framing if ts shells 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.toml only carries store IDs for request_signing + a consent_store name today. Where does ts provision fastly enumerate remote stores? [fastly.stores] section, hardcoded map, integration-derived? (inline at L745)
  • Interaction with PR #647 (runtime config store) — §6.1 asserts trusted-server.toml is the source of truth, but #647 makes the deployed config store a runtime read source too. Does apply push trusted-server.toml into the config store? How is drift handled? (inline at L164)

Non-blocking

🤔 thinking

  • --service-id required on every provisioning command is poor UXfastly.toml already has service_id; default to that. (inline at L735)
  • ts dev value over fastly compute serve is 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 dev last (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)
  • --force granularity in ts audit — pick a canonical pattern (either --no-config --force or --force-js-assets / --force-config). (inline at L472)

🌱 seedling

  • Distribution and installation model unspecifiedcargo install? Homebrew? Pre-built binaries? (inline at L147)
  • Multiple Fastly profiles / accounts--profile flag deferred to v2; spec should call out the v1 single-profile assumption. (inline at L676)

📝 note

  • PR body checklist wrongly mentions tracing — repo uses log macros per CLAUDE.md. Fix .github/pull_request_template.md separately. (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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 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:

  1. apply creates resources and writes provider-assigned IDs back into trusted-server.toml (the spec is silent on this auto-update), or
  2. resources are identified by name (but §13.10 says current behavior is mixed and likely needs cleanup), or
  3. 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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 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:

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.toml extended 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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 apply pushes trusted-server.toml into 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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ 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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ 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:

  1. document that --no-config --force is the canonical pattern for this case, or
  2. add --force-js-assets and --force-config for 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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌱 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🌱 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`)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create CLI Spec

2 participants