Skip to content

Add full-surface documentation refresh spec - #1049

Open
aram356 wants to merge 111 commits into
rc/202608from
spec-docs-refresh
Open

aram356 wants to merge 111 commits into
rc/202608from
spec-docs-refresh

Conversation

@aram356

@aram356 aram356 commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Single PR for the full documentation refresh: the design spec plus all eight work packages land here, one commit (or small series) per package, reviewable commit-by-commit.
  • The spec was synthesized from a four-track audit of main at 2e85a1cdc (VitePress site, root/crate markdown, in-code docs, config templates), every finding cited at file:line, with ground-truth inventories (routes with per-adapter availability, all 15 Settings sections, 14-integration capability matrix, ts CLI tree) as appendices.

Changes

File Change
docs/superpowers/specs/2026-08-19-documentation-refresh-design.md Design spec: audit findings, source-of-truth map, eight work packages, acceptance criteria
WP1 (upcoming commits) Publishing/policy hygiene: srcExclude, CNAME, fastly.toml sensitive values, empty /guide/ page, gate alignment
WP2 Remove fabricated/dead content: dead endpoints, Equativ, RequestWrapper, .with_asset, GAM/Kargo pages, auction README rot, FAQ_POC.md, CHANGELOG/.env.example repairs
WP3 Config reference parity with all 15 Settings sections and 14 integration configs
WP4 API reference rebuilt from adapter route tables with availability matrix
WP5 New pages: deployment guides, EdgeZero, telemetry/Tinybird, tsjs, GPT handoff, adserver_mock, script guards; nav repair; TESTING.md rewrite
WP6 Root markdown and crate READMEs
WP7 In-code docs: core module index, platform/ docs, crate headers, tsjs JSDoc
WP8 Enforcement: docs build on PRs, cargo doc -D warnings, doctests in CI, dependabot gaps

Closes

Closes #1038
Closes #277
Closes #341

Test plan

  • Docs format: cd docs && npm run format (passes on the spec)
  • Docs build: cd docs && npm run lint && npm run build (after WP commits land)
  • cargo fmt --all -- --check and target-matched clippy/tests for crates touched by WP7
  • cargo doc --no-deps warning-free for core and adapters (WP7/WP8)
  • Acceptance greps from WP2-WP4 included in this description when work lands
  • Other: spec content verified against main at 2e85a1cdc by four parallel read-only audits, then realigned to rc/202608 (new [cache] section, admin EC diagnostics routes, restructured CLI)

Checklist

  • Changes follow CLAUDE.md conventions
  • No secrets or credentials committed

Settings parity (WP3)

Generated from the checked settings record at 18f4d6b2eb2abdaef43b0b5f4b8c15d52be3eba0.

Settings roots (17/17)

  • [auction]
  • [cache]
  • [consent]
  • [creative_opportunities]
  • [debug]
  • [ec]
  • [[handlers]]
  • [image_optimizer]
  • [integrations.*]
  • [proxy]
  • [publisher]
  • [request_signing]
  • [response_headers]
  • [rewrite]
  • [tester_cookie]
  • [tinybird]
  • [trusted_client_ip]

Deploy-validated integration IDs (14/14)

  • adserver_mock
  • aps
  • datadome
  • didomi
  • google_tag_manager
  • gpt
  • gpt_diagnostics
  • lockr
  • nextjs
  • osano
  • permutive
  • prebid
  • sourcepoint
  • testlight

Provider profile schemas (3/3; 14 fields)

Profile Fields Provider timeout default
aps 5 800 ms
prebid-server 7 1000 ms
standard 2 Auction timeout

Directional field dispositions (18/18)

The checked axes are lifecycle, key identity, serialization, runtime use, and secret handling.

Path Lifecycle Key identity Serialization Runtime Secret handling
AssetOriginAuth.s3_sig_v4 deprecated alias of AssetOriginAuth.s3_sigv4 skipped deserialization only none
DataDomeConfig.server_side_key_secret_name canonical canonical serialized active store resolved
DataDomeConfig.server_side_key_secret_store deprecated canonical skipped normalized away none
DataDomeProtectionTestBypassConfig.credential_secret_name canonical canonical serialized active store resolved
DataDomeProtectionTestBypassConfig.credential_secret_store deprecated canonical skipped normalized away none
Ec.passphrase canonical canonical serialized active store resolved
EcPartner.api_token canonical canonical serialized active store resolved
EcPartner.ts_pull_token canonical canonical serialized active store resolved
Handler.password canonical canonical serialized active store resolved
Publisher.proxy_secret canonical canonical serialized active store resolved
S3SigV4AuthConfig.access_key_id canonical canonical serialized active store resolved
S3SigV4AuthConfig.secret_access_key canonical canonical serialized active store resolved
S3SigV4AuthConfig.secret_store deprecated canonical skipped normalized away none
S3SigV4AuthConfig.session_token canonical canonical serialized active store resolved
TinybirdSettings.access_token_secret deprecated canonical skipped normalized away accepted, then discarded
TinybirdSettings.auction_token_secret canonical canonical serialized active store resolved
TinybirdSettings.secret_store deprecated canonical skipped normalized away none
TrustedClientIpConfig.shared_secret canonical canonical serialized active deliberately inline

Secret classifications

  • 11 store-resolved paths: publisher proxy secret; EC passphrase and two partner-token paths; handler password; Tinybird auction token; DataDome server key and test-bypass credential; S3 access key, secret key, and optional session token.
  • trusted_client_ip.shared_secret is deliberately inline and may appear in diff/dry-run/confirmation output.
  • tinybird.access_token_secret is accepted, discarded, and omitted from serialized config.
  • The four deprecated secret_store selectors are accepted and normalized away; none is recommended by the example template.

Exact WP3 checks

  • settings --check, generate --check, snippets --check, classify --check, scan --check, links --local --check, and check --all
  • Standalone docs-parity: format, all-target/all-feature clippy with warnings denied, and 398 tests
  • cargo test-fastly config: 12 Fastly adapter tests and 199 core tests selected; no failures
  • Docs: ESLint, Prettier, and VitePress build
  • Root and standalone Cargo lockfile hashes unchanged; origin/rc/202608 remains 07dfc1c6dddf69345ded17bd2d40a3d01bb39bcf

Adapter first-success smokes (WP5 deployment)

Hosted receipts use implementation SHA 2f1f11c2cf94cf0b5606e107672d6386d283959a. The Spin receipt was repeated at 552dbeebe63aa6c742f2def95565bfcc80076513; the smoke script is byte-identical at both commits.

Axum

./scripts/smoke-axum.sh

Sequence: resolve/build ts and the Axum binary; start an isolated loopback sentinel origin; initialize and strictly validate the app config; run ts config push --adapter axum --local; read the generated envelope; launch one isolated Axum process for missing config, each missing secret, and the positive case.

Cleanup: the EXIT INT TERM trap stops the active Axum process and sentinel origin, then removes the generated temporary workspace and .edgezero state.

Oracle: HTTP 200; body contains SMOKE_ORIGIN_SENTINEL; rewritten URL targets the Axum listener; original origin URL is absent. Independent failures: missing TRUSTED_SERVER_CONFIG; missing handlers[0].password; missing publisher.proxy_secret; missing ec.passphrase. Each failure requires HTTP 500, its exact startup diagnostic, and live adapter/origin processes.

Receipt: Integration Tests / adapter smoke (Axum).

Fastly

./scripts/smoke-fastly.sh

Sequence: resolve/build ts and release Wasm; start an isolated loopback sentinel origin; initialize and strictly validate the app config; prove /health is 200 while an unconfigured publisher request fails; run ts config push --adapter fastly --local; seed the three ts_secrets entries; remove each secret independently; restore all entries and run the positive publisher case through fastly compute serve.

Cleanup: the EXIT INT TERM trap stops the active Fastly process and sentinel origin, restores fastly.toml byte-for-byte, restores or removes .fastly.toml.edgezero-lock according to its initial state, and removes the temporary workspace.

Oracle: HTTP 200; body contains SMOKE_ORIGIN_SENTINEL; rewritten URL targets the Fastly listener; original origin URL is absent. Independent failures: missing config-store key while health remains 200; missing handler_password; missing publisher_proxy_secret; missing ec_passphrase. Each publisher failure requires HTTP 500, its exact startup diagnostic, and live Fastly/origin processes.

Receipt: Integration Tests / adapter smoke (Fastly).

Cloudflare

./scripts/smoke-cloudflare.sh

Sequence: require the exact Wrangler pin; resolve/build ts and the Worker bundle; start an isolated loopback sentinel origin; initialize and strictly validate the app config; map the logical store to TRUSTED_SERVER_KV; run ts config push --adapter cloudflare --local; read trusted_server_config back with the explicit binding and local flags; encode the envelope as TRUSTED_SERVER_CONFIG.app_config; generate one isolated Wrangler manifest per missing binding and the positive case; run each with wrangler dev.

Cleanup: the EXIT INT TERM trap stops the active Wrangler process and sentinel origin and removes the temporary workspace, including generated manifests and local Wrangler KV state.

Oracle: HTTP 200; body contains SMOKE_ORIGIN_SENTINEL; rewritten URL targets the Wrangler listener; original origin URL is absent. Independent failures: missing env.TRUSTED_SERVER_CONFIG; missing env.handler_password; missing env.publisher_proxy_secret; missing env.ec_passphrase. Each failure requires HTTP 500, a binding inventory that omits only the selected binding while retaining its control binding, the normalized exact diagnostic, and live Wrangler/origin processes.

Receipt: Integration Tests / adapter smoke (Cloudflare).

Spin

./scripts/smoke-spin.sh

Sequence: resolve/build ts and release Wasm; start an isolated loopback sentinel origin; initialize and strictly validate the app config; prove a pre-push publisher request fails; map the logical store to default; run ts config push --adapter spin --local; launch one spin up process with each encoded secret variable omitted independently; launch the positive case with all three variables.

Cleanup: the EXIT INT TERM trap stops the active Spin process and sentinel origin and removes the temporary workspace, including the generated manifest, component logs, and .spin/sqlite_key_value.db.

Oracle: HTTP 200; body contains SMOKE_ORIGIN_SENTINEL; rewritten URL targets the Spin listener; original origin URL is absent. Independent failures: missing default/trusted_server_config; missing encoded handler-password variable; missing encoded publisher-proxy-secret variable; missing encoded EC-passphrase variable. Each failure requires HTTP 503, the controlled config-push/one-variable delta, the normalized exact diagnostic, and live Spin/origin processes; a generic degraded-router 503 is insufficient.

Time-bounded receipt: local run passed at 552dbeebe63aa6c742f2def95565bfcc80076513 with Spin 4.1.0 (c0b3726, 2026-08-25), Rust 1.95.0 on aarch64-apple-darwin, and owner documentation-maintainers; expires 2026-10-07T00:00:00Z.

Final documentation-refresh acceptance

  • Exact head: f682c05df07f89bdbda61c2372d3f3f34e5c8de2
  • Exact base: 07dfc1c6dddf69345ded17bd2d40a3d01bb39bcf (rc/202608)
  • Base range: 107 commits, 267 paths, 140,494 insertions, and 3,300 deletions.
  • Hosted workflows on that exact head: Run Format — success, Documentation automation — success, Run Tests — success, Integration Tests — success, and CodeQL Advanced — success.
  • Check publishers: GitHub Actions published the format, documentation, test, capture, and integration checks; GitHub Advanced Security published CodeQL. Representative exact-head jobs: Format documentation parity, Documentation automation parity, Linux CLI capture, macOS CLI capture, browser integration, and Rust CodeQL.
  • Local acceptance: the complete Task 18 matrix and Task 19 remediation matrix passed, including release WASM builds; all four first-success smokes; docs lint, format, and build; JavaScript lint, format, 13 builds, and 949 tests with type checking; warning-denied rustdoc/doctests; generated no-diff checks; actionlint; shellcheck and bash -n for the seven workflow scripts; and standalone docs-parity formatting, warning-denied Clippy, full tests, and check --all. The final docs-parity run passed 38 library, 35 classification, 27 CLI, 13 CLI-help, five dependency, six gate, 33 integration, two JSDoc, 62 link, 11 Markdown, two README, 70 route, 74 scanner, 24 settings, five snippet, and seven workflow tests. The reviewed sensitive manifest is SHA-256 007c971f2d2b5bf1db923cf3ea45501917069a9a2a2b3534ff10e507abcc9661 and governs 5,476 exact occurrences without changing the exception count.
  • Workflow policy: every external action uses an exact reviewed release tag; no action is SHA-pinned or branch-pinned. Every new multi-command workflow operation delegates to one of seven checked shell scripts. The new workflow and writer implementation does not invoke Python.
  • Runtime scope: Cloudflare and Spin add only startup diagnostic logging required by the negative smokes. Public APIs, routes, response bodies, status behavior, and successful-request behavior are unchanged.
  • Documentation scope: public onboarding moved to docs/internal/onboarding.md under the approved containment package; the public documentation was rewritten and expanded across configuration, routes, integrations, all four adapters, testing, telemetry, TSJS, architecture, and deployment. This change is a documentation refresh plus deterministic parity enforcement, not tooling alone.
  • Immutable inputs: root Cargo.lock SHA-256 9bb34225c5b8d1da39c75c3a8143d905f4b7d228a8986dc93d7e58a4196b4bba; docs-parity lock SHA-256 234a21b4831ec92fca081bc389dad6bdff1bc18d3a715f41831e2067a95e2ffb.
  • CLI golden provenance: authenticated source workflow 34316723321 at 0dcf054063dd6f746b1bb44ca58418d23efae887; Linux artifact ID 10090397245, digest sha256:ff23327b542aca834c118a53313f50722164a1f92a52a563543979907310e7f4; macOS artifact ID 10090451966, digest sha256:1844e6f1536b375fec8a7a92a18a6403831f289f5710d40684ae8fee4619563a. Two authenticated imports retained Linux help SHA-256 d1cae561a509fac817befdca1ba733a2582dba87d322cf4c2efccce9729a6786, macOS help SHA-256 cecd3a524ff6c50459b446dfe95980be0cdb037e1934ea0ab2a389aa6472f450, and capture-manifest SHA-256 fc51a476a716e7b18ccd4a0dd9b912698448d78a26ecc65b7d1c139a4b3ddf2c. The exact-final-head capture jobs also passed without changing the reviewed goldens.
  • Release-pending, not claimed here: live Pages/CNAME verification after merge to main; the first real scheduled external-link run; dependency submission plus graph visibility; and optional main branch-protection activation after the new contexts report from the expected apps. The committed release runbook owns these operations.

@aram356 aram356 self-assigned this Aug 20, 2026
@aram356
aram356 marked this pull request as draft August 20, 2026 06:53
@aram356 aram356 added this to the 202608 milestone Aug 20, 2026
@aram356
aram356 requested a review from jevansnyc August 20, 2026 16:07
@aram356
aram356 changed the base branch from main to rc/202608 August 20, 2026 17:39
@aram356
aram356 force-pushed the spec-docs-refresh branch 2 times, most recently from 392c994 to 087e1a7 Compare August 21, 2026 03:45
@aram356
aram356 force-pushed the spec-docs-refresh branch 2 times, most recently from d514dc6 to f27cc1a Compare August 28, 2026 05:37
@aram356
aram356 force-pushed the spec-docs-refresh branch from f11ad3c to 0ddbb88 Compare August 28, 2026 21:28
@aram356
aram356 marked this pull request as ready for review September 8, 2026 19:21
@aram356
aram356 requested review from ChristianPavilonis, jevansnyc and prk-Jr and removed request for prk-Jr September 9, 2026 17:23

@prk-Jr prk-Jr left a comment

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.

Requesting changes for the six blocking findings below. Also included are ten non-blocking observations.

Blocking (6)

🔧 W1 — Pinned Node is silently discarded, and docs-parity enforces the broken command

.github/workflows/docs-links.yml:27 + tools/docs-parity/src/workflow.rs:451

run: is a YAML plain scalar, so " reaches the shell literally. Proven from this PR's own CI log:

awk: cmd. line:1: ^ backslash not last character on line
node: v22.23.2
node-version is empty → setup-node falls back to the runner default. The job ran Node 22.23.2, not the pinned 24.12.0. The three " uses in test.yml are inside YAML double-quoted scalars and are correct; this is the only broken one.

Not suggestion-eligible: workflow.rs:451 requires this exact byte string, so fixing only the workflow turns check --all red. Both files must change together.

🔧 W2 — CLI-help gate hard-pins PR #1049; the first post-merge CLI change red-locks CI

tools/docs-parity/src/cli_help.rs:2103

|| run.pull_request != 1049
check_repository (wired into check --all at lib.rs:395) fails with "CLI source/blob set changed after capture" as soon as any crates/trusted-server-cli blob differs. The only golden-refresh path is import-hosted → validate_run, which rejects every run whose pull_request != 1049. cli-overrides.toml is per-command platform annotations, not an escape hatch. The runbook even says "only after PR #1049 has reached main" and directs receipts to comments on this PR.

🔧 W3 — A documented required gate runs in no workflow (and CI runs a gate the manifest omits)

tools/docs-parity/manifests/gates.toml:51 → generated into CLAUDE.md:396, TESTING.md:52, AGENTS.md, docs/guide/testing.md:

cargo test --manifest-path crates/trusted-server-integration-tests/Cargo.toml --test documentation_snippets
Zero hits across .github/. That is the gate that compiles the documentation-snippet:runtime-services fixture in integration-guide.md — so the "compile-verified" fixture is verified by nothing. Conversely test.yml:303 runs clippy on the integration-tests crate, absent from the manifest and reachable by no alias. Root cause: gates.rs:298 compares the manifest only against four markdown files, never against .github/workflows/*.

🔧 W4 — GTM container_id contract documented wrong (newly added line)

docs/guide/configuration.md:1612 says "GTM- followed by alphanumeric characters; 5–50 characters". google_tag_manager.rs:54 is ^GTM-[A-Z0-9]{4,20}$ → 8–24 chars, uppercase only (length(min = 1, max = 50) is non-binding). A 5-char or lowercase value is rejected at startup.

The suggested replacement is attached inline.

🔧 W5 — Fabricated TSJS script tag survives; the PR removed 2 of 3 instances

docs/guide/creative-processing.md:792-798 documents src="/static/tsjs-core.min.js", async, data-tsjs-integration="core". Real emitter tsjs.rs:12,48-51 produces /static/tsjs=tsjs-unified.min.js?v={hash} with id="trustedserver-js" and no async. data-tsjs-integration appears nowhere in the codebase. Count went 3 → 1 in this PR; line 823 of the same file states the correct URL.

🔧 W6 — Sensitive-data scanner skips every non-PSL host

tools/docs-parity/src/scanner.rs:368, predicate at :563. A URL authority that isn't a registrable public-suffix domain is continued, so it never becomes a finding and never needs an allowlist entry. Verified against the live tree:

value source sha256 allowlist entries
169.254.169.254 sourcepoint.rs:1548 34146ce1… 0
192.168.1.1 settings.rs:6046 c5eb5a4c… 0
A committed https://jenkins-prod/job/deploy or https://10.0.3.14/keys is invisible to the gate. The .internal special-case one line above shows non-PSL hosts were meant to be covered. Both live hits are fictional test values, so this is a coverage hole, not a leak — reasonable to defer if you'd rather land the PR.

Non-blocking (10)

♻️ N1 — Documentation parity is the name of two different jobs (format.yml:136, docs-links.yml:13), both on pull_request. Branch protection matches required checks by name. The docs-links job's steps are a strict subset of the format.yml job's.

♻️ N2 — Five hardcoded toolchain: "1.95.0" in docs-links.yml (lines 30, 57, 89, 123, 153) while this same PR adds scripts/read-tool-versions.sh, which format.yml uses.

♻️ N3 — srcExclude still lists guide/onboarding.md, the file this PR deletes. Every other entry resolves. The suggested removal is attached inline.

♻️ N4 — docs/README.md:44,120 still instructs operators to edit docs/public/CNAME, which this PR deletes. Contradicts the PR's own decision record ("never restore the placeholder CNAME").

♻️ N5 — docs-parity check with no flags exits 0 having validated nothing (lib.rs:607; --all isn't required = true unlike five sibling subcommands), and Outcome::Drift exits nonzero with no output (main.rs:6-8) — a check --all failure gives an empty CI log.

🤔 N6 — Two commands in docs/guide/testing.md don't work (pre-existing, page edited here): cargo test-axum test_generate_ec_id runs zero tests (alias is -p trusted-server-adapter-axum; the test is in trusted-server-core), and cargo clippy-axum --fix --allow-dirty fails — I reproduced it with an equivalent alias: error: Unrecognized option: 'fix' (args land after the alias's --).

🤔 N7 — Spin is the one adapter excluded from the adapter first success matrix, and scripts/smoke-spin.sh is referenced by no workflow, yet test.yml drops the Spin boot env overrides saying boot "is covered by a separately provisioned smoke test."

🤔 N8 — crates/trusted-server-adapter-spin/src/logging.rs claims the process-global logger slot with one that emits only STARTUP_DIAGNOSTIC_TARGET. No regression today, but it permanently blocks any later Spin logger.

📌 N9 — docs/guide/integrations/lockr.md:147 documents POST /integrations/lockr/sync; lockr.rs:353 registers only /sdk, /api/*. File untouched by this PR; the PR's own generated api-reference.md lists the three real routes.

👍 N10 — CHANGELOG v1.2.0 correction is right (I confirmed only v1.1.0 exists on origin); fastly.toml drops a real author email and the retained service_id carries a properly typed/owned/expiring exception per CLAUDE.md.

Comment thread docs/guide/configuration.md Outdated
Comment thread docs/.vitepress/config.mts
aram356 added a commit that referenced this pull request Sep 10, 2026
- Fix the docs-links Node pin: the run step is a YAML plain scalar, so the
  escaped quotes reached awk and emptied the setup-node version; update the
  enforced byte string in workflow.rs to match
- Rename the docs-links pull-request job so "Documentation parity" names
  exactly one required check
- Unpin authenticated CLI capture imports from PR #1049 so any successful
  pull-request capture run can refresh the goldens after merge
- Run the documentation_snippets gate in test.yml, add the integration-tests
  fmt and clippy gates to the manifest, and pin both couplings plus the
  toolchain versions with repository contract fragments
- Correct the GTM container_id contract, the injected TSJS script tag, the
  Lockr routed endpoints, the stale onboarding srcExclude entry, and the
  CNAME setup instructions that contradicted the decision record
- Require a mode flag on docs-parity check and name each drifting offline
  check on stderr instead of exiting silently
- Refresh manifest selectors and fingerprints shifted by these edits

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aram356

aram356 commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

All six blocking findings and eight of the ten observations are addressed in 51ad97a. Per-finding disposition:

W1 Fixed. The run step is now a plain scalar with unescaped quotes; verified it emits node=24.12.0 against .tool-versions. The enforced byte string in workflow.rs changed in the same commit, and check --all passes with the pair.

W2 Fixed. validate_run now accepts any authenticated pull-request capture run and rejects only pull_request == 0; the error message, import-hosted doc, and tests updated (a case proving PR 2048 imports, a rejection case for a PR-less run).

W3 Fixed both directions. test.yml now runs --test documentation_snippets (verified green locally), and the manifest gained the integration-tests fmt and clippy commands, regenerated into the four consumers. I did not build full manifest-vs-workflow reconciliation in gates.rs; instead the two couplings are pinned as repository contract fragments in workflow.rs::check_repository, so removing either side fails check --all.

W4 Applied your suggestion verbatim.

W5 Replaced with the real emitter output: /static/tsjs=tsjs-unified.min.js?v=<hash> with id="trustedserver-js", no async, no data-tsjs-integration.

W6 Deferred, per your offer, with data from an actual attempt: I implemented the predicate (flag DNS-shaped non-PSL authorities, exempt non-routable and documentation-range IPv4) and bootstrapped the tree. It produced 69 new findings, and apart from s3.us-east-1.amazonaws.com every one is a code or docs placeholder (ssp, host, h, origin, your-domain). None of them can be allowlisted: vendor_url semantics require a PSL-valid host, and no other class fits. Closing the hole needs both predicate tuning and an exception-class extension, so it is reverted here and belongs in a focused follow-up. I can open the issue with the experiment attached.

N1 The docs-links job is renamed "Documentation automation guard", so "Documentation parity" now names exactly one pull-request check (the format.yml superset job).

N2 The five hardcoded pins stay, but check --all now requires rust 1.95.0 in .tool-versions and channel = "1.95.0" in rust-toolchain.toml, so a toolchain bump fails the gate until the workflow pins and the validator constant move together. Making the scheduled jobs read read-tool-versions.sh would touch four job validators for jobs that only run on main; the drift guard buys the same safety cheaper.

N3 Applied your suggestion.

N4 Rewrote the custom-domain instructions to configure the domain in GitHub Pages settings, stating the repository intentionally tracks no CNAME file; the troubleshooting checklist and project-structure tree no longer mention it.

N5 check now requires --all or --tracked-paths-record like the sibling subcommands, and each drifting offline check is named on stderr, so a red CI run says which check drifted.

N6 Both commands fixed: the axum example uses a test that exists in the adapter (sets_geo_unavailable_header), and the clippy fix line is the expanded cargo clippy -p trusted-server-adapter-axum ... --fix --allow-dirty since alias arguments land after the --.

N7 Reworded the comment to state plainly that boot is exercised by the manually run scripts/smoke-spin.sh and that no CI workflow runs it. Wiring the smoke into CI is runtime-provisioning work outside this PR.

N8 No change. Agreed the logger claim blocks any later Spin logger, but changing adapter runtime behavior is out of scope for this PR; I can file an issue.

N9 Corrected to the real routes: GET /integrations/lockr/sdk and GET/POST /integrations/lockr/api/*.

The manifest selector and fingerprint churn in the diff is mechanical fallout from the byte shifts, reconciled against a fresh bootstrap. Full check --all, the docs-parity fmt/clippy/test gates, generate --check, and prettier over the edited docs all pass locally.

- Fix the docs-links Node pin: the run step is a YAML plain scalar, so the
  escaped quotes reached awk and emptied the setup-node version; update the
  enforced byte string in workflow.rs to match
- Rename the docs-links pull-request job so "Documentation parity" names
  exactly one required check
- Unpin authenticated CLI capture imports from PR #1049 so any successful
  pull-request capture run can refresh the goldens after merge
- Run the documentation_snippets gate in test.yml, add the integration-tests
  fmt and clippy gates to the manifest, and pin both couplings plus the
  toolchain versions with repository contract fragments
- Correct the GTM container_id contract, the injected TSJS script tag, the
  Lockr routed endpoints, the stale onboarding srcExclude entry, and the
  CNAME setup instructions that contradicted the decision record
- Require a mode flag on docs-parity check and name each drifting offline
  check on stderr instead of exiting silently
- Refresh manifest selectors and fingerprints shifted by these edits
@aram356
aram356 requested a review from prk-Jr September 10, 2026 22:15

@prk-Jr prk-Jr left a comment

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.

Summary

The latest changes address the prior W1–W5 blockers; the scanner follow-up remains explicitly deferred. One new issue prevents the dependency-submission workflow from producing an API-valid payload.

Blocking

  • 🔧 [P1] Include the API-required scan timestamp in dependency snapshots — see inline at tools/docs-parity/src/dependency_snapshot.rs:85.

Validation and scope

The standalone docs-parity build and five existing snapshot tests passed. A scratch assertion on the generated payload reproduced the missing required field. No live dependency submission was attempted. This exceptionally large diff was reviewed with priority on prior fixes and substantive runtime/automation code; this was not an exhaustive audit of every extractor, generated record, and test.

CI Status

Comment thread tools/docs-parity/src/dependency_snapshot.rs Outdated

@ChristianPavilonis ChristianPavilonis left a comment

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.

Summary

Request changes. I reviewed the non-tools/** diff with five parallel read-only reviewers against origin/rc/202608. The targeted core, CLI, integration, adapter, browser-fixture, and documentation-snippet checks passed, but the issues below remain.

Scope blocker

The entire tools/** subtree should not merge to main. It contributes roughly 125k lines of manifests, inventories, goldens, and tooling to this PR. The non-tool changes are currently coupled to it through include_str! calls in core tests, generated gate blocks, CI commands, and documentation instructions. Remove tools/** and those dependent changes before merging, or split the documentation-tool proposal into a separate, explicitly approved PR.

The generated gate matrix is also duplicated across multiple agent and testing documents, which increases drift and obscures the actual CI contract.

Validation

git diff --check passed. Targeted core, CLI, browser-fixture, integration-snippet, Spin, and Cloudflare checks passed. Real Spin/Cloudflare runtime log sinks were not exercised locally.

Comment thread crates/trusted-server-adapter-spin/src/logging.rs Outdated
Comment thread scripts/smoke-fastly.sh Outdated
Comment thread crates/trusted-server-core/src/config.rs Outdated
Comment thread docs/internal/audits/documentation-refresh-evidence.md Outdated
@aram356

aram356 commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator Author

Review remediation is pushed in 9dfb956. The substantive documentation refresh and the separate internal onboarding path remain. I removed the standalone docs-parity Cargo workspace, generated inventories and goldens, scheduled link and dependency writers, and all core-crate coupling to that proposal. The retained automation is one source-preserving shell aggregate plus a read-only workflow_dispatch-only Documentation checks workflow, so it cannot block normal core or adapter CI. The Spin diagnostic no longer owns the global logger, and the Fastly smoke mutates only per-run manifest copies. Action references use exact release tags, and workflow logic longer than one command remains in repository scripts. Local verification covered the manual documentation aggregate, the target-matched Rust and JavaScript gates, all four real adapter smokes, workflow linting, shell linting, formatting, and an independent final review with no remaining findings. Hosted checks are now running against the exact pushed head.

@prk-Jr prk-Jr left a comment

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.

Summary

A large, unusually careful documentation refresh that also carries runtime Rust, CI workflow, shell script, config template, and governance changes. The documentation itself is high quality — I mechanically cross-checked every documented route and every TOML key in the changed docs against the source and found zero fabricated routes or config keys, which is a rare result for a 6,000-line docs change.

The blocking findings are not about the docs. They are three things riding along inside a "docs refresh": a runtime observability regression on the Spin adapter, a rewrite of the repository's sensitive-data policy that legalizes an existing violation, and a rewrite of the project's governance charter.

2 of the inline comments below carry a one-click GitHub suggestion — use Commit suggestion (or Add suggestion to batch) to apply them as commits on this branch. Both were scratch-verified in an isolated worktree at this head, individually and together. The remaining comments describe the fix in prose because the change is a scope/ownership decision or spans multiple sites and can't be auto-applied.

Note on the stated diff size

The PR description cites 107 commits / 140,494 insertions, which is measured against an older base. Against current rc/202608 (07dfc1c6dddf69345ded17bd2d40a3d01bb39bcf) the real diff is 109 commits, 203 files, +6,003 / −3,631. This review is against that diff, not the description.

Blocking

🔧 wrench

  • Spin startup diagnostic drops the error-stack report — see inline at crates/trusted-server-adapter-spin/src/app.rs:509 (suggestion)
  • Sensitive-data policy rewritten to sanction a value the same PR keeps — see inline at fastly.toml:10
  • ProjectGovernance.md rewrites the chartered governance of an IAB Tech Lab project — see inline at ProjectGovernance.md:13

❓ question

  • Seven commands documented under "CI Gates" are run by no PR-triggered workflow — see inline at .github/workflows/documentation-checks.yml:3

Non-blocking

🤔 thinking / ♻️ refactor / 📌 out of scope / ⛏ nitpick / 📝 note

  • 🤔 Spin release build lost its boot-proving env overrides with no CI replacement — see inline at .github/workflows/test.yml:201
  • 🤔 documentation_snippets.rs hardcodes dep versions and leans on ambient cargo cache — see inline at crates/trusted-server-integration-tests/tests/documentation_snippets.rs:40
  • 🤔 [trusted_client_ip] removed from the operator template with no decision-record entry — see inline at trusted-server.example.toml:145 (LEFT side)
  • ♻️ Same current_context() truncation on Cloudflare — see inline at crates/trusted-server-adapter-cloudflare/src/app.rs:362 (suggestion)
  • 📌 spin is not pinned in .tool-versions — see inline at .tool-versions:4
  • uncomment_block heuristic still mis-handles prose — see inline at crates/trusted-server-core/src/config.rs:540
  • The new .env.dev [ec] entry is a no-op — see inline at .env.dev:8
  • 📝 OpenRTB codegen binary is now completely silent — see inline at crates/trusted-server-openrtb-codegen/src/main.rs:25 (LEFT side)

Cross-cutting / body-level findings

🤔 Actions are pinned to mutable release tags, not commit SHAs

I verified every pin against the GitHub releases API. All thirteen exist and are the current latest release, so there are no broken or hallucinated pins:

action pinned latest
actions/checkout v7.0.1 v7.0.1
actions/setup-node v7.0.0 v7.0.0
actions/cache v6.1.0 v6.1.0
actions/upload-artifact v7.0.1 v7.0.1
actions/download-artifact v8.0.1 v8.0.1
actions/configure-pages v6.0.0 v6.0.0
actions/upload-pages-artifact v5.0.0 v5.0.0
actions/deploy-pages v5.0.1 v5.0.1
actions-rust-lang/rustfmt v1.1.2 v1.1.2
github/codeql-action v4.37.9 current
browser-actions/setup-chrome v2.2.0 v2.2.0
fastly/compute-actions v14 v14

The upload-artifact v7 / download-artifact v8 major mismatch is genuine upstream, not an authoring error. actions-rust-lang/setup-rust-toolchain stays at v1.17.0 while v2.0.0 exists — a safe, deliberate non-bump.

The remaining concern is only that git tags are mutable and can be repointed by a compromised maintainer; SHA pinning is the hardening standard, and this PR bills itself partly as a hardening pass. docs/internal/audits/documentation-refresh-decisions.md:93 explicitly chose "exact release tags", so this is a recorded disagreement rather than a defect — flagging so the tradeoff is visible.

Verified clean on the rest of the supply-chain surface: zero ${{ github.event.* }} interpolations anywhere in .github/workflows/ or .github/actions/ (no script-injection sinks), and every workflow declares least-privilege permissions: (contents: read, with pages: write + id-token: write scoped to deploy-docs.yml alone).

📝 gam.md / kargo.md are published but unlinked — intentional, recording so it isn't "fixed" later

Both were dropped from the sidebar in docs/.vitepress/config.mts yet still build (.vitepress/dist/guide/integrations/{gam,kargo}.html) with zero inbound links from any published page. I read both: they are tombstones ("Trusted Server does not ship a direct Google Ad Manager integration…"), whose purpose is to catch stale external links, so absence from navigation is correct. No action requested — noting it so a future reader doesn't re-link them. (Anchored here rather than inline because the removal is a deletion spanning several config.mts hunks.)

👍 Praise

  • Route-contract tests across all four adapters (crates/trusted-server-adapter-fastly/src/app.rs:1592-1631, and tests/routes.rs in the cloudflare / spin / axum adapters) pin the exact (method, path) set, including the legacy /admin/keys/* aliases. This is precisely the regression net this area needed.
  • crates/trusted-server-adapter-cloudflare/src/platform.rs:585-598 corrects a doc claim that was wrong. The old text said config and KV "are sourced from the edgezero handles that run_app injects". I verified grep -n "fn stores" across all four adapters returns only crates/trusted-server-adapter-fastly/src/app.rs:1336, so the new text ("the Cloudflare application does not implement Hooks::stores()") is the accurate one. Correcting a doc toward the less flattering truth is the right instinct.
  • Zero fabricated routes or config keys. I extracted all 27 backticked route paths from the changed guides and every key = from every TOML fence in every added/modified doc, then grepped them against the full Rust source. The only hit was thumbnail, which is a user-chosen profile-map key rather than a struct field.
  • The secret_store template removals are correctcrates/trusted-server-core/src/settings.rs:291-305, 913-915, 1922-1924 deprecate-and-ignore secret_store, server_side_key_secret_store, and credential_secret_store with a warning, so dropping them from the template is right.
  • The deploy-docs provenance assertion actually works. I ran GITHUB_SHA=aaaa…aaaa npm run build in the worktree and confirmed grep -R --fixed-strings --quiet "$GITHUB_SHA" .vitepress/dist succeeds.
  • docs/public/CNAME deletion is safe — it contained the literal placeholder your-custom-domain.com, not a live domain.
  • scripts/smoke-fastly.sh correctly avoids the tracked-manifest secret hazard — it copies fastly.toml into a per-run temp project (scripts/smoke-fastly.sh:33-34) before ts config push --local writes secrets into it, so the tracked file is never written.
  • The .env synthetic → EC migration is correctgrep "counter_store\|opid_store\|SYNTHETIC" crates/trusted-server-core/src/settings*.rs returns nothing, so the removed keys really are dead.
  • cache-dependency-path fixed from package.jsonpackage-lock.json in both format.yml and test.yml; $GITHUB_OUTPUT quoted throughout; multi-line run: blocks moved into repository scripts.

Local verification

Run from an isolated worktree detached at 9dfb956cda2674b2ad04796321a8f6edcd26337b.

Gate Result
cargo fmt --all -- --check PASS
cargo fmt --manifest-path crates/trusted-server-integration-tests/Cargo.toml -- --check PASS
cargo clippy-spin-native PASS (no warnings)
cargo clippy-cloudflare PASS
cargo clippy-cloudflare-wasm PASS
cargo test-spin PASS — 38 passed, 0 failed
cargo test-cloudflare PASS — 24 + 23 passed, 0 failed
cd docs && npm ci && npm run format PASS — "All matched files use Prettier code style!"
cd docs && npm run lint PASS
cd docs && npm run build PASS — build complete in 6.02s, no dead links

Both suggestion blocks were applied and verified individually against a clean tree, then together as a batch (fmt + target-matched clippy + cargo test-spin + cargo test-cloudflare all green in every configuration). The worktree was restored to the PR head afterwards.

Not run locally (cost, and green on CI): cargo test-fastly, cargo test-axum, the parity suite, the full clippy alias chain, vitest.

Verified vs. unverified

Scratch-verified: the Spin and Cloudflare diagnostic findings (fix applied, fmt/clippy/tests green); the policy and governance findings (both sides of the diff plus the decision record read in full); the CI-gate-enforcement question (grep across all workflows plus live gh pr checks); the Spin release-build finding (workflow diff and smoke matrix contents); the action pins (all 13 checked against the GitHub releases API); the [trusted_client_ip] finding (settings.rs:2739-2810, all four adapter call sites, doc coverage at both base and head, and absence from every decision record); and the config.rs, .env.dev, .tool-versions, openrtb-codegen, and gam/kargo findings. All praise items verified as stated.

Unverified / judgement: the documentation_snippets.rs finding — I reasoned about --offline resolution and the shared CARGO_TARGET_DIR from reading the test; I did not construct a cold-cache run to prove it fails. The spin pinning finding relies on this PR's own evidence document for the Spin 4.1.0 claim; I could not run the Spin CLI here.

Concerns I raised and then killed by checking — listing these so they don't get re-raised in a later pass: bogus or nonexistent action version pins (all real and current); the docs/public/CNAME deletion breaking a custom domain (it was a placeholder); scripts/install-wrangler.sh's strict test "$(wrangler --version)" = "$version" (the adapter first success (cloudflare) check is green, so it works); the [ec] and secret_store template removals being wrong (both correct against the code); and a "docs-parity standalone crate with its own lockfile" (git ls-files | grep -i docs.parity returns nothing — it was withdrawn by commit 9dfb956c, "Remove intrusive documentation tooling").

Recommendation

Three of the four blockers are scope problems rather than code problems. The cleanest path forward:

  1. Split ProjectGovernance.md into its own PR for Task Force / IAB Tech Lab sign-off. Deleting chartered commitments (biweekly cadence, published minutes, continuous release) is not a change a code review can approve.
  2. Split the CLAUDE.md sensitive-data policy rewrite into its own PR so the new typed-exception system gets explicit maintainer sign-off from someone other than the exception's own owner — and either remove the service_id or have the exception re-approved independently.
  3. Apply the one-line Spin diagnostic suggestion (and optionally the matching Cloudflare one).
  4. Answer the "CI Gates" labelling question.

What remains after that split is a strong, accurate documentation refresh I would be glad to see merged.

CI Status

All 22 reported checks pass. Branch protection reports no required checks on spec-docs-refresh.

  • .github/dependabot.yml: PASS
  • Analyze (actions): PASS
  • Analyze (javascript-typescript): PASS
  • Analyze (rust): PASS
  • CodeQL: PASS
  • adapter first success (axum): PASS
  • adapter first success (cloudflare): PASS
  • adapter first success (fastly): PASS
  • browser integration tests: PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • cargo fmt: PASS
  • cargo test: PASS
  • cargo test (axum native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo test (ts CLI, native): PASS
  • format-docs: PASS
  • format-typescript: PASS
  • integration tests: PASS
  • integration tests (Fastly EC lifecycle): PASS
  • prepare integration artifacts: PASS
  • vitest: PASS
  • Documentation checks: not run — the workflow this PR adds is workflow_dispatch-only, which is the subject of the ❓ finding above.

Comment thread crates/trusted-server-adapter-spin/src/app.rs
Comment thread fastly.toml Outdated
Comment thread ProjectGovernance.md Outdated
Comment thread .github/workflows/documentation-checks.yml
Comment thread .github/workflows/test.yml
Comment thread crates/trusted-server-adapter-cloudflare/src/app.rs
Comment thread .tool-versions
Comment thread crates/trusted-server-core/src/config.rs
Comment thread .env.dev Outdated
Comment thread crates/trusted-server-openrtb-codegen/src/main.rs
…tics

- Revert ProjectGovernance.md to the release-branch charter; chartered
  commitments are a Task Force decision and will be proposed separately
- Revert the CLAUDE.md sensitive-data policy rewrite and the fastly.toml
  service-ID exception comment; the typed-exception system needs sign-off
  independent of the exception owner and moves to its own pull request
- Format Spin and Cloudflare startup diagnostics with the full error-stack
  report instead of only the leaf context
- Move the rustdoc commands out of "CI Gates" into a "Manual documentation
  gates" section that names the workflow_dispatch-only enforcement, and
  align the documented CLI clippy command with the one CI runs
- Read the error-stack requirement from the workspace manifest in the
  documentation-snippet fixture instead of hardcoding 0.6
- Restore the [trusted_client_ip] template block, record the Spin
  boot-coverage gap in the decision record, drop the no-op [ec] overlay
  from .env.dev, and restore the codegen progress output with a scoped
  allow for the host-only tool

@ChristianPavilonis ChristianPavilonis left a comment

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.

Summary

Reviewed at e7687a1b075847bf50c7bdc64bfc99b737a53c97 against 07dfc1c6dddf69345ded17bd2d40a3d01bb39bcf. I have not repeated findings already covered by existing review threads.

One follow-up worth tracking: trusted_client_ip.shared_secret is the secret value itself and is serialized into the app-config blob. Redacted only masks debug and display output, and this field is not registered for secret-store resolution. Changing that storage contract is outside this documentation refresh, but it should get a separate issue with a compatibility and migration plan.

Comment thread docs/roadmap.md Outdated
@aram356

aram356 commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

Review feedback

Reviewed at e7687a1b0. All 21 checks pass and the earlier rounds have been addressed thoroughly, so this is one blocking item plus scope observations — not a defect list.

Blocking: onboarding is the only content removed without a destination

Everything else that shrank in this PR was deduplicated into something better. testing.md 367→142 is a genuine improvement — it correctly deletes cargo test -p trusted-server-core, which fails to link because it carries no --target, and keeps the useful filter examples. integrations-overview.md 403→82 became a complete inventory table. Good changes.

Onboarding is different. It goes 160→42 lines, moves to docs/internal/onboarding.md, and srcExclude: ['internal/**'] at docs/.vitepress/config.mts:52-56 means it is no longer published at all. Combined with two pre-existing gaps on the base branch:

  • it is not in the sidebar (this predates the PR);
  • neither README.md nor CONTRIBUTING.md mentions onboarding;
  • after the move, the only inbound links are docs/internal/audits/documentation-refresh-decisions.md:35 and the plan/spec files — i.e. the records explaining why it was hidden.

Net effect: after merge a new engineer has no discoverable onboarding entry point.

The containment rationale is right — contacts and meeting times should not be public. The execution over-applied it. The material that did not survive is the part that is not sensitive and has no other home: what the system does, how a request flows through it, and what the ad-tech vocabulary means. Nothing in the refreshed reference set absorbed it; rust-analyzer, asdf, and "good first issue" appear nowhere in the new docs.

I have put up #1165 as a draft stacked on this branch showing one way to close it: a published docs/guide/onboarding-new-engineer.md plus a sidebar entry, leaving your internal maintainer page untouched. Take it, adapt it, or replace it — but something public should ship with this.

Scope

The title says documentation refresh; the diff is 202 files, of which 144 are non-doc (+2641/-1146) including adapter source, workflows, fastly.toml, and seven new shell scripts.

I checked whether that hides behaviour changes and it largely does not — the TypeScript edits in core/render.ts, registry.ts, types.ts, globals.ts and integrations/creative/* are comment-to-JSDoc conversions with zero non-comment lines changed, and the Rust is characterization tests plus the startup-diagnostic fix reviewers already requested. The smoke scripts are shellcheck-clean and wired into integration-tests.yml.

That is solid work. It is also why this needed four review rounds: reviewers had to audit a runtime surface to approve a docs change, and two real bugs (the Spin global logger suppressing production logging, the current_context() truncation) were found by reading code rather than by any of the ledgers. Splitting governance and the CLAUDE.md policy edits out mid-review was the right call; applying that instinct earlier would have made this two or three PRs. 100 commits also means commit-by-commit review is not really available.

Not asking you to re-cut it now — flagging it for the next one of this size.

Smaller points

  1. Spin smoke is manual and time-bounded. The matrix at integration-tests.yml:123 is [axum, fastly, cloudflare]; the PR body carries a local Spin receipt expiring 2026-10-07, about three weeks out. The decision record is admirably blunt that no CI workflow proves the Spin artifact boots, and Pin spin in .tool-versions and add Spin to the adapter-first-success smoke matrix #1164 is filed — just worth not letting that receipt lapse silently.

  2. roadmap.md:35 page-bids method — still open from @ChristianPavilonis, and it is correct: both /__ts/page-bids and /_ts/page-bids are GET.

  3. Comment-to-JSDoc conversions lose reasoning in a few places. In render.ts, the old comment on sanitizeCreativeHtml explained why sanitizedLength always equals originalLength and removedCount is always 0 — they exist for structural consistency with the shared result type and carry no signal. The JSDoc replacement drops that, and it is the kind of thing the next reader will otherwise misread as a bug. Worth keeping the "why" sentence where the original had one.

Correction to something I said earlier

I previously flagged {{RUST_VERSION}} in getting-started.md as rendering literally. That was wrong. docs/.vitepress/config.mts installs a markdown parse hook that reads .tool-versions and substitutes every {{TOOL_VERSION}} at build time, so the published page reads "Rust 1.95.0". The placeholder never appears as a literal in the config, which is why my grep missed it. The mechanism is also the better design — versions cannot drift from the pinned toolchain.

Verdict

Merge it once the onboarding placement is settled. That is a one-line srcExclude decision plus a page, not a content dispute. The reference layer here is a real improvement: four adapter guides, telemetry, tsjs, settings and route parity, ten crate READMEs, and the Equativ/Kargo/GAM fiction removed. What it does not add is anything that teaches — no request trace, no glossary, no triage path — but that is the next PR, not a reason to hold this one.

@aram356

aram356 commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

Latest feedback is addressed in 5c043a3.

  • Restored public Onboarding at the established /guide/onboarding URL, with the verified Fastly request path, repository vocabulary, code map, development loop, and triage pointers.
  • Kept maintainer onboarding outside the VitePress build and aligned the decision record, design, and executed plan.
  • Corrected both page-bids roadmap methods to GET.
  • Restored the sanitizeCreativeHtml rationale for the structural length/count fields.
  • Closed the stacked draft Add a new-engineer setup page alongside the onboarding guide #1165 because its useful content is now in this PR.

Verification on the final tree: full scripts/check-documentation.sh passed; all 949 Vitest tests passed; Prettier passed for the root README and JS/docs trees; independent review found no Critical or Important issues.

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

Labels

None yet

Projects

None yet

3 participants