@biffo/cli 0.79.0 → 0.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,15 +67,16 @@ jobs:
67
67
  - name: Test
68
68
  if: ${{ !cancelled() }}
69
69
  run: pnpm run test
70
- # Deliberately run with NO NEXT_PUBLIC_CORE_COGNITO_* in scope (issue
71
- # #286). `next build` prerenders `/`, which imports src/lib/auth.ts; when
72
- # that module built its Cognito pool eagerly the build blew up on any
73
- # machine without the real pool ids exported, and nothing in CI caught it
74
- # the first person to find out was whoever had just scaffolded the
75
- # sibling, debugging template code they did not write. A sibling's build
76
- # must not need auth credentials, so this step is the assertion of that.
77
- # Provide the real values in deploy.yml, where they are inlined into the
78
- # artifact that actually ships.
70
+ # Run with NO NEXT_PUBLIC_CORE_COGNITO_* in scope — this is now the
71
+ # PERMANENT state, not a build-guard quirk. The frontend NEVER reads those
72
+ # vars: it resolves the core's Cognito identity at runtime from
73
+ # /.well-known/biffo-identity.json (#403 Stage 3), so nothing is baked into
74
+ # the bundle and deploy.yml no longer sets them either. `next build`
75
+ # prerenders `/`, which imports src/lib/auth.ts; that module builds its
76
+ # Cognito pool lazily (never at module scope) so the build succeeds with no
77
+ # identity in scope. Originally added as a guard for the eager-construction
78
+ # crash (issue #286); it now simply asserts a sibling's build needs no auth
79
+ # credentials at all.
79
80
  - name: Build
80
81
  if: ${{ !cancelled() }}
81
82
  run: pnpm run build
@@ -153,8 +153,12 @@ jobs:
153
153
  NEXT_PUBLIC_SIBLING_PATH_PREFIX: ${{ vars.PATH_PREFIX && format('/{0}', vars.PATH_PREFIX) || '' }}
154
154
  NEXT_PUBLIC_BASE_PATH: ${{ vars.PATH_PREFIX && format('/{0}', vars.PATH_PREFIX) || '' }}
155
155
  NEXT_PUBLIC_CORE_PORTAL_URL: ${{ vars.CORE_PORTAL_URL }}
156
- NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID: ${{ vars.CORE_COGNITO_USER_POOL_ID }}
157
- NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID: ${{ vars.CORE_COGNITO_CLIENT_ID }}
156
+ # The frontend no longer bakes NEXT_PUBLIC_CORE_COGNITO_* into the
157
+ # bundle: it resolves the core's Cognito identity at RUNTIME from the
158
+ # core-published /.well-known/biffo-identity.json document (#403), so
159
+ # the build no longer needs the pool/client ids. (The BACKEND still
160
+ # consumes CORE_COGNITO_* via TF_VAR for JWT validation — see the
161
+ # deploy-infra job above; that is deliberately unchanged.)
158
162
  NEXT_PUBLIC_API_URL: ${{ vars.API_URL }}
159
163
  run: pnpm run build
160
164
  - uses: aws-actions/configure-aws-credentials@v6
@@ -198,14 +198,21 @@ part of the install, so its lifecycle scripts never run.)
198
198
 
199
199
  ## The build must not need Cognito credentials
200
200
 
201
- `pnpm run build` has to succeed with **no** `NEXT_PUBLIC_CORE_COGNITO_*` set,
202
- and `.github/workflows/ci.yml` runs it that way on every PR to keep it that
203
- way. `next build` prerenders `/` in Node, which imports `src/lib/auth.ts`; that
204
- module therefore constructs its Cognito user pool lazily, on first session
205
- read, never at module scope (the `CognitoUserPool` constructor throws outright
206
- when either id is missing). If you add module-scope code that requires real
207
- core config, the build breaks for everyone who has not exported the pool ids —
208
- starting with CI. Read config inside the function that needs it.
201
+ The frontend resolves the core's Cognito identity at **runtime** from the
202
+ core-published `/.well-known/biffo-identity.json` document (#403) it never
203
+ bakes `NEXT_PUBLIC_CORE_COGNITO_*` into the bundle. So `pnpm run build` succeeds
204
+ with **no** `NEXT_PUBLIC_CORE_COGNITO_*` set, and `.github/workflows/ci.yml`
205
+ runs it that way on every PR to keep it that way. `next build` prerenders `/` in
206
+ Node, which imports `src/lib/auth.ts`; that module constructs its Cognito user
207
+ pool lazily, on first session read, never at module scope (the `CognitoUserPool`
208
+ constructor throws outright when either id is missing). If you add module-scope
209
+ code that requires real core config, the build breaks for everyone — starting
210
+ with CI. Read config inside the function that needs it.
211
+
212
+ (The sibling's **backend** still receives the core pool/client ids at deploy
213
+ time via `TF_VAR_core_cognito_*` — its Terraform builds a JWKS URL from the pool
214
+ id to validate JWTs. That is a separate concern from the frontend bundle and is
215
+ deliberately unchanged.)
209
216
 
210
217
  ## The two-phase CDN registration
211
218
 
@@ -267,12 +274,19 @@ this repo's own `statusChecks` list — see that method's third parameter):
267
274
 
268
275
  1. Rename `biffo.sibling.json`'s `name`/`core_project`/`path_prefix`.
269
276
  2. Copy `apps/frontend/.env.example` to `.env.local` and fill in your core
270
- project's real Cognito/API values for local dev.
277
+ project's real API/portal values for local dev. No Cognito values are
278
+ needed: the frontend resolves the core's Cognito identity at runtime from
279
+ `/.well-known/biffo-identity.json` (#403). A locally-run frontend with no
280
+ reachable document just treats the visitor as signed-out.
271
281
  3. `cd infra && terraform init -backend-config=backend.hcl && terraform apply`
272
282
  (generate `backend.hcl` yourself for local use — CI generates it inline,
273
283
  see `deploy.yml`).
274
284
  4. Wire the GitHub Environment variables/secrets `deploy.yml` reads
275
285
  (`CORE_COGNITO_USER_POOL_ID`, `CORE_COGNITO_CLIENT_ID`, `CORE_API_URL`,
276
- `CORE_PORTAL_URL`, `SIBLING_OIDC_ROLE_ARN`, `TF_STATE_BUCKET`, etc.).
286
+ `CORE_PORTAL_URL`, `SIBLING_OIDC_ROLE_ARN`, `TF_STATE_BUCKET`, etc.). The
287
+ `CORE_COGNITO_*` variables are still required: the **backend** consumes them
288
+ via `TF_VAR_core_cognito_*` for JWT validation (its Terraform derives a JWKS
289
+ URL from the pool id). They are no longer inlined into the frontend bundle —
290
+ the frontend resolves the core's Cognito identity at runtime (#403).
277
291
  5. Open the registration PR against your core project (`infra/siblings.auto.tfvars.json`)
278
292
  yourself, or let `biffo sibling create` do it for you.
@@ -6,8 +6,11 @@ NEXT_PUBLIC_SIBLING_NAME=example-sibling
6
6
  NEXT_PUBLIC_SIBLING_PATH_PREFIX=/example-sibling
7
7
  NEXT_PUBLIC_BASE_PATH=/example-sibling
8
8
  NEXT_PUBLIC_CORE_PORTAL_URL=https://baseurl.com
9
- NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
10
- NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID=XXXXXXXXXXXXXXXXXXXXXXXXXX
9
+ # No Cognito env is needed: the sibling resolves the core's Cognito identity at
10
+ # RUNTIME from /.well-known/biffo-identity.json (served by the core), so nothing
11
+ # is baked into the bundle (#403). A locally-run frontend with no reachable
12
+ # document simply treats the visitor as signed-out — a sibling never signs
13
+ # anyone in (ADR-0007).
11
14
  # This sibling's OWN API Gateway endpoint (from `terraform output api_url`),
12
15
  # NOT the core project's API — the frontend never calls the core API
13
16
  # directly (ADR-0002/ADR-0007).
@@ -22,16 +22,16 @@ function userWithSession(session: unknown, err: Error | null = null) {
22
22
  }
23
23
  }
24
24
 
25
- // The env vars are the TRANSITIONAL fallback (#403 Stage 3 removes them). The
26
- // document is now the preferred source, so most tests set env AND control
27
- // `fetch` to prove which one wins.
28
- function configureCognitoEnv() {
29
- vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID', 'us-east-1_TESTPOOL')
30
- vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID', 'testclientid')
25
+ // The sibling is DOC-ONLY (#403 Stage 3): it NEVER reads NEXT_PUBLIC_CORE_COGNITO_*.
26
+ // This helper only exists to set BOGUS env values in the "env is ignored" tests,
27
+ // proving the pool is never built from env even when it is present.
28
+ function stubBogusCognitoEnv() {
29
+ vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID', 'us-east-1_BOGUSENV')
30
+ vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID', 'bogusenvclientid')
31
31
  }
32
32
 
33
- // A `fetch` returning a valid identity document. The ids differ from the env
34
- // values on purpose so a test can prove the DOCUMENT wins over baked env.
33
+ // A `fetch` returning a valid identity document. The ids are what the pool must
34
+ // be built from the ONLY source of Cognito coordinates now.
35
35
  function mockFetchDocument(overrides: Record<string, unknown> = {}) {
36
36
  const body = {
37
37
  userPoolId: 'us-east-1_DOCPOOL',
@@ -45,7 +45,8 @@ function mockFetchDocument(overrides: Record<string, unknown> = {}) {
45
45
  }) as unknown as typeof fetch
46
46
  }
47
47
 
48
- // A `fetch` that rejects (network error) — drives the env fallback path.
48
+ // A `fetch` that rejects (network error) — the document is unreachable, so the
49
+ // sibling resolves to null (no env fallback).
49
50
  function mockFetchUnreachable() {
50
51
  global.fetch = vi.fn().mockRejectedValue(new Error('network down')) as unknown as typeof fetch
51
52
  }
@@ -53,7 +54,7 @@ function mockFetchUnreachable() {
53
54
  // Each test re-imports the module fresh: both the pool (auth.ts) and the
54
55
  // resolved-identity Promise (identity.ts) are memoised at module scope, so a
55
56
  // module instance left over from a previous test would carry that test's
56
- // document/env into the next one.
57
+ // document into the next one.
57
58
  async function loadAuth() {
58
59
  vi.resetModules()
59
60
  return await import('@/lib/auth')
@@ -62,11 +63,9 @@ async function loadAuth() {
62
63
  describe('getCurrentSession', () => {
63
64
  beforeEach(() => {
64
65
  vi.clearAllMocks()
65
- configureCognitoEnv()
66
- // Default: document unreachable, so these behaviour tests build the pool
67
- // from the env fallback (us-east-1_TESTPOOL / testclientid) exactly as the
68
- // pre-runtime version did.
69
- mockFetchUnreachable()
66
+ // The document is reachable, so these behaviour tests build the pool from
67
+ // the DOCUMENT (us-east-1_DOCPOOL / docclientid) never from env.
68
+ mockFetchDocument()
70
69
  })
71
70
 
72
71
  afterEach(() => {
@@ -100,9 +99,9 @@ describe('getCurrentSession', () => {
100
99
  })
101
100
  })
102
101
 
103
- // The core now publishes its Cognito coordinates at runtime; the sibling
104
- // prefers that document over its baked env vars (#403/#400). These tests pin
105
- // which source wins under each condition.
102
+ // The core publishes its Cognito coordinates at runtime; the sibling is
103
+ // DOC-ONLY (#403 Stage 3) and resolves them from that document alone. These
104
+ // tests pin that the document is the sole source env is never consulted.
106
105
  describe('runtime core identity resolution', () => {
107
106
  beforeEach(() => {
108
107
  vi.clearAllMocks()
@@ -113,9 +112,9 @@ describe('runtime core identity resolution', () => {
113
112
  vi.restoreAllMocks()
114
113
  })
115
114
 
116
- it('builds the pool from the published document, not from env', async () => {
117
- // Env is set to DIFFERENT values so a pass proves the document won.
118
- configureCognitoEnv()
115
+ it('builds the pool from the published document', async () => {
116
+ // Bogus env is set to prove the document not env — is the source.
117
+ stubBogusCognitoEnv()
119
118
  mockFetchDocument()
120
119
  const { getCurrentSession } = await loadAuth()
121
120
  getCurrentUser.mockReturnValue(null)
@@ -131,55 +130,44 @@ describe('runtime core identity resolution', () => {
131
130
  })
132
131
  })
133
132
 
134
- it('falls back to env and warns when the document is unreachable', async () => {
135
- configureCognitoEnv()
133
+ it('resolves null and never builds the pool from env when the document is unreachable', async () => {
134
+ // Env is present but BOGUS: a DOC-ONLY sibling must ignore it entirely.
135
+ stubBogusCognitoEnv()
136
136
  mockFetchUnreachable()
137
137
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
138
138
  const { getCurrentSession } = await loadAuth()
139
139
  getCurrentUser.mockReturnValue(null)
140
140
 
141
- await getCurrentSession()
142
-
143
- expect(poolConstructor).toHaveBeenCalledWith({
144
- UserPoolId: 'us-east-1_TESTPOOL',
145
- ClientId: 'testclientid',
146
- })
141
+ await expect(getCurrentSession()).resolves.toBeNull()
142
+ // Proves env is ignored: the pool was never constructed from the bogus env.
143
+ expect(poolConstructor).not.toHaveBeenCalled()
147
144
  expect(warn).toHaveBeenCalledOnce()
148
- expect(warn.mock.calls[0]?.[0]).toContain('DEGRADED')
149
145
  })
150
146
 
151
- it('falls back to env when the document is served but missing ids', async () => {
152
- configureCognitoEnv()
147
+ it('resolves null and never builds the pool when the document is served but missing ids', async () => {
148
+ stubBogusCognitoEnv()
153
149
  // ok:true but the body lacks a clientId — treated as unusable.
154
150
  mockFetchDocument({ clientId: '' })
155
151
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
156
152
  const { getCurrentSession } = await loadAuth()
157
153
  getCurrentUser.mockReturnValue(null)
158
154
 
159
- await getCurrentSession()
160
-
161
- expect(poolConstructor).toHaveBeenCalledWith({
162
- UserPoolId: 'us-east-1_TESTPOOL',
163
- ClientId: 'testclientid',
164
- })
155
+ await expect(getCurrentSession()).resolves.toBeNull()
156
+ expect(poolConstructor).not.toHaveBeenCalled()
165
157
  expect(warn).toHaveBeenCalledOnce()
166
158
  })
167
159
 
168
- it('resolves null and never constructs the pool when both document and env are absent', async () => {
169
- vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID', '')
170
- vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID', '')
160
+ it('resolves null when the document is unreachable', async () => {
171
161
  mockFetchUnreachable()
172
162
  const warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
173
163
  const { getCurrentSession } = await loadAuth()
174
164
 
175
165
  await expect(getCurrentSession()).resolves.toBeNull()
176
166
  expect(poolConstructor).not.toHaveBeenCalled()
177
- // No document AND no env is an unconfigured build, not degradation — quiet.
178
- expect(warn).not.toHaveBeenCalled()
167
+ expect(warn).toHaveBeenCalledOnce()
179
168
  })
180
169
 
181
170
  it('fetches the document at most once across multiple session reads (memoised)', async () => {
182
- configureCognitoEnv()
183
171
  mockFetchDocument()
184
172
  const { getCurrentSession } = await loadAuth()
185
173
  getCurrentUser.mockReturnValue(null)
@@ -210,14 +198,12 @@ describe('lazy pool construction', () => {
210
198
  })
211
199
 
212
200
  it('does not construct the pool merely by importing the module', async () => {
213
- configureCognitoEnv()
201
+ mockFetchDocument()
214
202
  await loadAuth()
215
203
  expect(poolConstructor).not.toHaveBeenCalled()
216
204
  })
217
205
 
218
- it('imports cleanly with no Cognito env vars set and no fetch available', async () => {
219
- vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID', '')
220
- vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID', '')
206
+ it('imports cleanly with no fetch available (next build prerender)', async () => {
221
207
  // Simulate `next build` prerendering `/` in Node with no fetch in scope.
222
208
  const originalFetch = global.fetch
223
209
  // @ts-expect-error deliberately remove fetch to mimic the Node prerender.
@@ -230,16 +216,14 @@ describe('lazy pool construction', () => {
230
216
  }
231
217
  })
232
218
 
233
- it('resolves null instead of throwing when Cognito env vars are absent', async () => {
234
- vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID', '')
235
- vi.stubEnv('NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID', '')
219
+ it('resolves null instead of throwing when the document is unreachable', async () => {
236
220
  const { getCurrentSession } = await loadAuth()
237
221
  await expect(getCurrentSession()).resolves.toBeNull()
238
222
  expect(poolConstructor).not.toHaveBeenCalled()
239
223
  })
240
224
 
241
- it('constructs the pool once, on first session read, from the env fallback', async () => {
242
- configureCognitoEnv()
225
+ it('constructs the pool once, on first session read, from the document', async () => {
226
+ mockFetchDocument()
243
227
  const { getCurrentSession } = await loadAuth()
244
228
  getCurrentUser.mockReturnValue(null)
245
229
 
@@ -248,8 +232,8 @@ describe('lazy pool construction', () => {
248
232
 
249
233
  expect(poolConstructor).toHaveBeenCalledTimes(1)
250
234
  expect(poolConstructor).toHaveBeenCalledWith({
251
- UserPoolId: 'us-east-1_TESTPOOL',
252
- ClientId: 'testclientid',
235
+ UserPoolId: 'us-east-1_DOCPOOL',
236
+ ClientId: 'docclientid',
253
237
  })
254
238
  })
255
239
  })
@@ -32,11 +32,12 @@ import { resolveCoreIdentity } from './identity'
32
32
  // ---------------------------------------------------------------------------
33
33
  // Module-private on purpose: the pool is an implementation detail of
34
34
  // getCurrentSession(), not part of this sibling's auth surface. Its Cognito
35
- // coordinates now come from resolveCoreIdentity() (identity.ts), which prefers
36
- // the core's runtime-published /.well-known/biffo-identity.json document and
37
- // only falls back to the baked NEXT_PUBLIC_CORE_COGNITO_* env vars when that
38
- // document is unreachable see identity.ts for why runtime resolution kills
39
- // the stale-baked-pool bug class (#403/#400).
35
+ // coordinates come from resolveCoreIdentity() (identity.ts), which resolves the
36
+ // core's runtime-published /.well-known/biffo-identity.json document. The
37
+ // sibling is now DOC-ONLY (#403 Stage 3): there is NO NEXT_PUBLIC_CORE_COGNITO_*
38
+ // env fallback. An unreachable document resolves to null the caller treats it
39
+ // as "signed out" see identity.ts for why runtime resolution kills the
40
+ // stale-baked-pool bug class (#403/#400).
40
41
  //
41
42
  // Constructed LAZILY, on first session read — never at module scope. The
42
43
  // CognitoUserPool constructor throws ("Both UserPoolId and ClientId are
@@ -29,12 +29,13 @@
29
29
  // this exactly ONE network request per page load, shared by every caller,
30
30
  // rather than one per session read.
31
31
  //
32
- // The env fallback below is TRANSITIONAL (Stage 3 of #403 removes it). It lets
33
- // an instance whose core has not yet started publishing the document keep
34
- // working off its baked env vars, so instances can upgrade at their own pace.
35
- // A baked value that shadows a live document is precisely the stale-copy bug
36
- // we are removing, so falling back because the document was UNREACHABLE is a
37
- // degraded path and warns loudly.
32
+ // A sibling is DOC-ONLY (#403 Stage 3): it carries NO CORE_COGNITO_* env vars —
33
+ // `biffo sibling create` no longer sets them and this module no longer reads
34
+ // them. When the document is unreachable, resolution returns null, and the
35
+ // caller (getCurrentSession) treats that as "no session" the sibling redirects
36
+ // to the portal's login rather than trusting a stale baked value, because a
37
+ // stale value is exactly the bug (#400) this removes. A clean redirect beats a
38
+ // silent point at a dead pool.
38
39
  // ---------------------------------------------------------------------------
39
40
 
40
41
  export interface CoreIdentity {
@@ -54,16 +55,6 @@ const IDENTITY_DOCUMENT_PATH = '/.well-known/biffo-identity.json'
54
55
  // callers before the fetch settles also coalesce onto the one request.
55
56
  let cached: Promise<CoreIdentity | null> | null = null
56
57
 
57
- // Read the transitional env fallback. Returns a valid CoreIdentity only when
58
- // BOTH ids are present; otherwise null (an unconfigured build has neither a
59
- // document nor env, and that is not an error to shout about).
60
- function identityFromEnv(): CoreIdentity | null {
61
- const userPoolId = process.env['NEXT_PUBLIC_CORE_COGNITO_USER_POOL_ID'] ?? ''
62
- const clientId = process.env['NEXT_PUBLIC_CORE_COGNITO_CLIENT_ID'] ?? ''
63
- if (!userPoolId || !clientId) return null
64
- return { userPoolId, clientId }
65
- }
66
-
67
58
  // A parsed document counts only when it carries both ids non-empty; a document
68
59
  // missing either is treated as unusable and triggers the fallback.
69
60
  function identityFromDocument(data: unknown): CoreIdentity | null {
@@ -90,38 +81,31 @@ async function fetchCoreIdentity(): Promise<CoreIdentity | null> {
90
81
  const identity = identityFromDocument(await res.json())
91
82
  if (identity) return identity
92
83
  }
93
- // Reached here => the document was served but unusable (non-ok status or
94
- // missing ids). Fall through to the degraded env fallback below.
84
+ // Served but unusable (non-ok status or missing ids). Fall through to null.
95
85
  } catch {
96
86
  // Network error, or `fetch` not available (e.g. `next build` prerendering
97
- // `/` in Node with no fetch). Fall through to the env fallback.
87
+ // in Node with no fetch though resolution is lazy and never runs there).
88
+ // Fall through to null.
98
89
  }
99
90
 
100
- // DEGRADED path: the document was unreachable/unusable, so we lean on the
101
- // baked env vars. If those exist, warn once a stale baked value silently
102
- // shadowing a live document is the #403/#400 bug. If they DON'T exist, this
103
- // is simply an unconfigured build (no document, no env) not degradation,
104
- // so stay quiet.
105
- const fallback = identityFromEnv()
106
- if (fallback) {
107
- console.warn(
108
- '[biffo] DEGRADED: could not resolve the core identity document at ' +
109
- `${IDENTITY_DOCUMENT_PATH}; falling back to baked ` +
110
- 'NEXT_PUBLIC_CORE_COGNITO_* env vars. This is transitional (#403) and ' +
111
- 'risks pointing at a stale/dead Cognito pool — ensure the core is ' +
112
- 'publishing the runtime identity document.',
113
- )
114
- }
115
- return fallback
91
+ // The document was unreachable or unusable. A sibling has NO baked fallback
92
+ // (#403 Stage 3), so this resolves to null and the caller treats it as
93
+ // "signed out" — a clean redirect to the portal login beats trusting a stale
94
+ // local pool id, which is the exact bug (#400) this removes.
95
+ console.warn(
96
+ `[biffo] could not resolve the core identity document at ${IDENTITY_DOCUMENT_PATH}; ` +
97
+ 'treating the visitor as signed out. If this persists, confirm the core is publishing it.',
98
+ )
99
+ return null
116
100
  }
117
101
 
118
102
  /**
119
- * Resolve the core's Cognito identity at runtime, preferring the published
120
- * `/.well-known/biffo-identity.json` document and falling back to baked
121
- * `NEXT_PUBLIC_CORE_COGNITO_*` env vars when the document is unreachable.
103
+ * Resolve the core's Cognito identity at runtime from the published
104
+ * `/.well-known/biffo-identity.json` document. A sibling has no env fallback
105
+ * (#403 Stage 3).
122
106
  *
123
107
  * Memoised: at most one fetch per page load, shared by every caller. Returns
124
- * null when neither the document nor the env vars supply both ids.
108
+ * null when the document is unreachable or does not supply both ids.
125
109
  */
126
110
  export function resolveCoreIdentity(): Promise<CoreIdentity | null> {
127
111
  cached ??= fetchCoreIdentity()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.79.0",
3
+ "version": "0.80.0",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",