Skip to content

Commit 6b2cdbd

Browse files
committed
fix(backend): stage the tenant allowlist, correct what the flags do
Sets UNATTENDED_ENABLED_TENANTS and SAFE_WRITE_REQUIRED_TENANTS to ["metamind"] for Phase 3, and fixes the documentation around them, which was wrong in three places. SAFE_WRITE_REQUIRED_TENANTS is NOT the safe-write control. Its check sits on the legacy command path, reached only when the caller omits `understudy-command-contract: 2` AND the extension is protocol-1. @understudy/connector has sent that header unconditionally since 0.5.0, so it cannot fire for a consumer on a current connector. The protection it is named for is already unconditional in dispatchV2, which refuses a protocol-1 write with the identical 426 for every tenant. Recording its silence as an exercised safety control would have enshrined a no-op in an evidence-gated rollout. README.md required the allowlist to stay empty until acceptance passed. The acceptance suite creates unattended sessions, so it can never pass against an empty allowlist — the requirement was unsatisfiable. apps/backend/README.md sanctioned ["*"] after rollout, which the runbook rejects and which admits every tenant holding a caller token. stub-consumer.mjs posted `{}` to /v1/sessions, which is an UNATTENDED request that 400s on the schema. It has not been able to verify the attended path since attended creation became "no body", so it must not be reached for as a regression check. Also records for Phase 4 that the sweep's quarantine gate keys on "understudy answered", not on refusal-ness — so a persistent 404 or 426 changes no state for 24 hours and then starts abandoning leases.
1 parent 959e246 commit 6b2cdbd

5 files changed

Lines changed: 67 additions & 15 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ The Release workflow publishes the promoted versions with npm provenance. It rej
7878

7979
Do not merge `master` back into `dev`. `NPM_TOKEN` needs publish access to the `@understudy` scope.
8080

81-
Backend deployment remains a separate Wrangler operation. Keep `UNATTENDED_ENABLED_TENANTS=[]` until the canary extension passes the production acceptance suite. Follow the [unattended production rollout runbook](docs/unattended-production-rollout.md) for deployment order, evidence gates, and rollback.
81+
Backend deployment remains a separate Wrangler operation. Keep `UNATTENDED_ENABLED_TENANTS=[]` until the canary device is enrolled and reporting protocol 2, then enable it for that tenant by name — the acceptance suite creates unattended sessions, so it cannot run while the allowlist is empty. Named tenants only, never `"*"`. Follow the [unattended production rollout runbook](docs/unattended-production-rollout.md) for deployment order, evidence gates, and rollback.
8282

8383
## Preserve attended proof history
8484

‎apps/backend/README.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,19 @@ Copy `.dev.vars.example` to `.dev.vars` for local development. Never commit `.de
9797

9898
Non-secret Wrangler variables include:
9999

100-
| Variable | Default | Purpose |
100+
| Variable | Shipped value | Purpose |
101101
|---|---|---|
102-
| `UNATTENDED_ENABLED_TENANTS` | `[]` | Tenant allowlist for new unattended leases |
103-
| `SAFE_WRITE_REQUIRED_TENANTS` | `[]` | Tenant allowlist that rejects protocol-1 writes |
102+
| `UNATTENDED_ENABLED_TENANTS` | `["metamind"]` | Tenant allowlist for new unattended leases |
103+
| `SAFE_WRITE_REQUIRED_TENANTS` | `["metamind"]` | Legacy-path downgrade guard (see below) |
104104
| `QUOTA_POLICY` | Built-in JSON | Exact SQLite quota configuration |
105105

106-
Use `["*"]` only after the protocol-2 extension rollout completes. Keep unattended creation disabled during the initial backend deployment.
106+
Both allowlists start at `[]` and are set per tenant during rollout; `wrangler.jsonc` is authoritative for what is deployed.
107+
108+
Never use `["*"]` for either allowlist. `enabledForTenant` honours it, so a wildcard admits every tenant holding a caller token — the blast radius the named allowlist exists to bound. Wildcard enablement is a rejected option in the [rollout runbook](../../docs/unattended-production-rollout.md); onboard tenants by name, one at a time.
109+
110+
Keep unattended creation disabled during the initial backend deployment. Enable it for a named tenant once that tenant's canary device is enrolled and reporting protocol 2 — the Chromium acceptance suite creates unattended sessions and cannot run against an empty allowlist.
111+
112+
`SAFE_WRITE_REQUIRED_TENANTS` is a downgrade guard, not the safe-write control: its check is on the legacy command path, reached only when a caller omits `understudy-command-contract: 2` *and* the extension is protocol-1. `@understudy/connector` has sent that header unconditionally since 0.5.0. Protocol-1 writes are already refused for every tenant by `dispatchV2`, flag or no flag.
107113

108114
The default exact quotas are:
109115

‎apps/backend/scripts/stub-consumer.mjs‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,18 @@ function wsBase(httpBase) {
6565
return httpBase.replace(/^http/, "ws");
6666
}
6767

68+
// `body === undefined` sends NO body, which is how attended creation is
69+
// discriminated (see the "Attended creation is defined as no body" comment in
70+
// src/index.ts). Sending `{}` instead makes the request UNATTENDED and it fails
71+
// the unattended schema with a 400 before any flag is consulted.
6872
async function postJson(path, body) {
6973
const res = await fetch(`${BASE_URL}${path}`, {
7074
method: "POST",
7175
headers: {
72-
"Content-Type": "application/json",
76+
...(body === undefined ? {} : { "Content-Type": "application/json" }),
7377
Authorization: `Bearer ${CALLER_TOKEN}`,
7478
},
75-
body: JSON.stringify(body),
79+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
7680
});
7781
const text = await res.text();
7882
let json;
@@ -134,7 +138,7 @@ async function main() {
134138
console.log("understudy stub consumer - M3 runbook harness");
135139
console.log(`BASE_URL=${BASE_URL}`);
136140

137-
const { sessionId } = await postJson("/v1/sessions", {});
141+
const { sessionId } = await postJson("/v1/sessions");
138142
console.log(`\nsession opened: ${sessionId}`);
139143

140144
const wsUrl = `${wsBase(BASE_URL)}/agents/session/${sessionId}?token=${EXTENSION_TOKEN}`;

‎apps/backend/wrangler.jsonc‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,21 @@
9191
},
9292
"vars": {
9393
"QUOTA_POLICY": "{\"sessionCreatesPerActorMinute\":10,\"commandsPerSessionMinute\":120,\"commandsPerTenantMinute\":600,\"credentialFillsPerActorMinute\":30,\"deviceTicketsPerDeviceMinute\":30,\"sessionCommandCap\":10000}",
94-
"UNATTENDED_ENABLED_TENANTS": "[]",
95-
"SAFE_WRITE_REQUIRED_TENANTS": "[]"
94+
// Gates ONLY the unattended create branch in src/index.ts; a request with an
95+
// empty body returns from the attended branch above that gate, so attended
96+
// callers never reach it. This is the live blast-radius control for
97+
// unattended traffic: named tenants only, NEVER "*" — enabledForTenant
98+
// honours "*" and it would admit every tenant that holds a caller token.
99+
"UNATTENDED_ENABLED_TENANTS": "[\"metamind\"]",
100+
// A downgrade guard, NOT the safe-write control. Its check in src/index.ts
101+
// sits on the legacy command path, reached only when the CALLER omits
102+
// `understudy-command-contract: 2` AND the extension is protocol-1.
103+
// @understudy/connector has sent that header unconditionally since 0.5.0, so
104+
// this never fires for a consumer on a current connector — it guards a caller
105+
// on an older one, or a hand-rolled POST. The protection it is named for is
106+
// already unconditional for every tenant: dispatchV2 in src/session.ts
107+
// refuses a non-dryRun write from a protocol-1 extension with the same 426.
108+
"SAFE_WRITE_REQUIRED_TENANTS": "[\"metamind\"]"
96109
},
97110
"analytics_engine_datasets": [
98111
{

0 commit comments

Comments
 (0)