Skip to main content

max / makenotwork

Send the SDK key the token grant requires, and record testnot's missing secret Second wall behind the content type: the server calls validate_synckit_key on the authorization_code grant and rejects an empty one with a 422, while server/docs/oauth_integration.md step 3 does not list the field at all. mt uploads nothing through SyncKit, so it sends its client id - one billing slot per registered instance, and the refresh grant carries the stored key forward. Third was testnot itself: no SYNCKIT_JWT_SECRET, so /oauth/token answered 503 for everyone. Set on the box, and written down here so a rebuilt testnot does not lose it silently. The harness walk now passes end to end against astra, before and after reset-astra.sh.
Author: Max Johnson <me@maxj.phd> · 2026-08-16 01:08 UTC
Signed with PGP, not checked
Commit: d08f132a5e7be333669b4ae27698d954e205c55b
Parent: f95a0c6
3 files changed, +19 insertions, -0 deletions
@@ -174,6 +174,14 @@
174 174 `mnw-testnot-seed.sh` already passes through. Neither value is in the repo: one
175 175 is a credential, the other names a tailnet host.
176 176
177 + `SYNCKIT_JWT_SECRET` has to be set on that box too, and it was not until
178 + 2026-08-15. It is the HS256 key `/oauth/token` signs with, and without it every
179 + token exchange answers 503 "SyncKit is not configured" — a login outage that
180 + looks like a transport failure from mt's side, since mt retries a 5xx twice
181 + before giving up. The seed does not set it (it is a machine secret, not seed
182 + data), so a rebuilt testnot needs it added by hand: 32 characters minimum, or
183 + the server refuses to boot.
184 +
177 185 The three accounts carry fixed MNW ids, and mt's own seed (`src/seed.rs`)
178 186 assigns their community roles by `mnw_account_id` — Owner and Moderator for
179 187 `harness_owner` — because login upserts identity and perks but never
@@ -534,6 +534,14 @@
534 534 ("redirect_uri", config.oauth_redirect_uri.as_str()),
535 535 ("code_verifier", verifier),
536 536 ("client_id", config.oauth_client_id.as_str()),
537 + // The server's `key` is the SDK key naming the billing slot an
538 + // integration's uploads count against, and it rejects an empty
539 + // one on this grant even though the published contract
540 + // (server/docs/oauth_integration.md step 3) does not list it.
541 + // mt uploads nothing through SyncKit, so the client id is the
542 + // honest answer: one slot per registered instance, and the
543 + // refresh grant carries the stored key forward without asking.
544 + ("key", config.oauth_client_id.as_str()),
537 545 ])
538 546 .send()
539 547 .await;
@@ -194,6 +194,9 @@
194 194 .and(body_string_contains("grant_type=authorization_code"))
195 195 .and(body_string_contains("code=the-code"))
196 196 .and(body_string_contains("code_verifier="))
197 + // Required by the server on this grant, absent from its published
198 + // contract, and a 422 when it is missing.
199 + .and(body_string_contains("key="))
197 200 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
198 201 "access_token": "acc", "token_type": "Bearer", "expires_in": 300,
199 202 "refresh_token": "rt", "scope": "profile:read perks:read offline_access",