mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 23:35:20 +00:00
fix(int): escopo do espelho Google Calendar por id, não por título — destrava deploy verify
O verify do deploy manual (run 34763253924, main 7ca708a3) falhou em
tests/int/googleCalendarSync.int.spec.ts ("a cancelled event...") nas duas
tentativas do retry: o spec isolava o espelho e a limpeza com
`{ title: { like: 'C114%' } }`, mas no Payload 3.82 `like`/`contains` compilam
para ILIKE '%…%' (contains, sem ancoragem) — um UUID de fixture de outro spec
embutindo o hex `c114` entrava no escopo e podia ser deletado pela limpeza.
Escopa passes e cleanup pelos ids criados (ownedActivityIds), com
`{ track: false }` para linhas deliberadamente alheias; teste de regressão do
cenário exato (falha sem o fix); pin de convenção em codebaseConventions
banindo marcador de título hex em tests/int; post-mortem e changelog.
This commit is contained in:
parent
7ca708a3cb
commit
fedb291592
4 changed files with 185 additions and 25 deletions
|
|
@ -0,0 +1 @@
|
|||
**Deploy verify destravado — escopo do espelho Google Calendar por id, não por título (2026-09-13):** o verify do deploy manual (run 34763253924, main `7ca708a3`) falhou no passo "Integration tests (full suite)" com 1 failed | 82 passed — `tests/int/googleCalendarSync.int.spec.ts` > "a cancelled event in Google cancels the confirmado activity, then the trash is cleaned" caiu nas duas tentativas do `retry: 1` (1ª `AssertionError: expected 2 to be 1` no `cleanup.deleted`; 2ª `store` ainda com 1 evento `{ id: 'teqo11' }`) e os jobs `deploy-staging`/`deploy-production` ficaram skipped, sem outage (produção permaneceu no build anterior). Causa: o escopo/limpeza do spec usava marcador de título (`{ title: { like: 'C114%' } }`, introduzido em `c69990a3`, 2026-08-11), mas no Payload 3.82 + `@payloadcms/drizzle` o `like` compila para `ILIKE '%<word>%'` por palavra (split por espaço) e o `contains` para `ILIKE '%<value>%'` (`node_modules/@payloadcms/drizzle/dist/queries/parseParams.js:183-184`, `sanitizeQueryValue.js:202-212`) — ambos CONTAINS, nunca prefixo: um UUID de fixture de outro spec embutindo o hex `c114` (case-insensitive) entrou no espelho e podia ser DELETADO pela limpeza `afterEach` do próprio spec; a colisão é rara (~29 posições hex por UUID; ~4% por arquivo/run) e ficou latente desde agosto, com a C153 adicionando 2 arquivos int (speechCatalog/speechImport) que cruzaram a probabilidade e o verify rodando a suíte int completa a cada commit. Fix (sem migration, sem código de produção): `tests/int/googleCalendarSync.int.spec.ts` passa a rastrear `ownedActivityIds: Set<number>`, `createActivity(..., { track: false })` para linhas deliberadamente alheias, `afterEach` deleta por `id: { in: [...] }`, `runSync` usa `activityWhere: { id: { in: [...ownedActivityIds] } }` e um teste de regressão novo ("a foreign title CONTAINING c114 stays out of the scope (id-scoped mirror)"), com o comentário de convenção do header corrigido; `tests/unit/codebaseConventions.unit.spec.ts` ganha o describe "int fixture scoping avoids hex title markers", que varre `tests/int` e bane `title: { like|contains: '<hex-only 3+>' }`. Verificação: o teste de regressão falha sem o fix (`expected 2 to be 1`) e passa com; o verificador independente rodou a suíte int completa (`VITEST_MAX_WORKERS=4 pnpm test:int`) com 83 files / 744 tests verdes (antes: 83 files / 743 tests, 1 failed no CI) e os 6 specs da família Google (47 tests) verdes; `pnpm gate:fast` verde e o pin de convenções falha com o padrão antigo. Sem Issue própria — o registro é o post-mortem (`docs/postmortems/2026-09-13-deploy-verify-escopo-google-calendar.md`). CI do PR do fix e deploy de produção pendentes (dispatch manual do humano após o merge).
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
# Post-mortem: escopo do espelho Google Calendar por título deixa linha alheia vazar e bloqueia o verify do deploy
|
||||
|
||||
> Template do `/bug-fix`. Preencha com fatos apurados; o que não for apurado fica "não apurado" — nunca invente.
|
||||
|
||||
## Registro
|
||||
|
||||
| Campo | Valor |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| Data do post-mortem | 2026-09-13 |
|
||||
| Severidade | alta (bloqueou o deploy de produção; sem outage — produção permaneceu no build anterior) |
|
||||
| Ambiente | CI (verify do deploy manual) |
|
||||
| Issue(s) | sem Issue (fluxo `/bug-fix`; precedente: post-mortem B196 de 2026-09-12) |
|
||||
| PR do fix | a preencher no merge |
|
||||
| Detectado por | teste (job `verify` do `deploy.yml`, passo "Integration tests (full suite)") |
|
||||
|
||||
## Timeline
|
||||
|
||||
| Momento | Data/hora | Evento |
|
||||
| ------------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Início provável | 2026-08-11 | commit `c69990a3` ("fix(C126): isola o escopo do espelho Google nos int specs (fim do flake paralelo)") introduz o escopo/limpeza por título `{ title: { like: 'C114%' } }` em `tests/int/googleCalendarSync.int.spec.ts`; a colisão com UUID alheio é rara, então o defeito fica latente |
|
||||
| Detecção | 2026-09-13 14:46:31 UTC | run de deploy 34763253924 (main `7ca708a3`), job `verify`, passo "Integration tests (full suite)": 1 failed \| 82 passed — `tests/int/googleCalendarSync.int.spec.ts` > "a cancelled event in Google cancels the confirmado activity, then the trash is cleaned" falha nas DUAS tentativas (`retry: 1`); os jobs `deploy-staging` e `deploy-production` ficam skipped |
|
||||
| Correção mergeada | pendente | PR do fix a preencher no merge |
|
||||
| Deploy | pendente | dispatch manual do `deploy.yml` pelo humano após o merge (nenhum merge publica sozinho) |
|
||||
| Verificado em prod | pendente | confirmação do humano após o deploy |
|
||||
|
||||
## O bug
|
||||
|
||||
O deploy manual de 2026-09-13 não chegou a produção: o job `verify` falhou na suíte int e os jobs de deploy (`deploy-staging`/`deploy-production`) foram skipped — o código de main (`7ca708a3`) não foi publicado nesse dispatch, e produção permaneceu no build anterior (sem outage; nenhum usuário afetado). A falha foi o teste `tests/int/googleCalendarSync.int.spec.ts` > "a cancelled event in Google cancels the confirmado activity, then the trash is cleaned", reprovado nas duas tentativas do `retry: 1` do vitest: na 1ª, `AssertionError: expected 2 to be 1` na asserção de `cleanup.deleted` (o passe de limpeza apagou 2 eventos em vez de 1); na 2ª, o `store` ainda continha 1 evento (`{ id: 'teqo11' }`). No mesmo arquivo, os testes "creates the full mirror…" e "failures land in paused…" passaram com `(retry x1)` — a contaminação era intermitente e o retry a mascarava. **Sintoma — não a causa.**
|
||||
|
||||
## Causa-raiz
|
||||
|
||||
O escopo e a limpeza do spec usavam marcador de título (`{ title: { like: 'C114%' } }`, commit `c69990a3`, 2026-08-11), tratando o texto como ownership. No Payload 3.82 + `@payloadcms/drizzle`, porém, `like` compila para `ILIKE '%<word>%'` por palavra (split por espaço) e `contains` para `ILIKE '%<value>%'` — ambos CONTAINS, nunca prefixo (origem: `node_modules/@payloadcms/drizzle/dist/queries/parseParams.js:183-184` e `sanitizeQueryValue.js:202-212`). Um UUID de fixture de outro spec embutindo o hex `c114` (case-insensitive) entrou no espelho e podia ser DELETADO pela limpeza `afterEach` do próprio spec. A suíte int roda arquivos em paralelo contra um banco compartilhado e se isola por texto compartilhado.
|
||||
|
||||
5-whys:
|
||||
|
||||
1. **Por que o teste falhou?** As contagens exatas do espelho incluíram uma linha alheia.
|
||||
2. **Por que a linha alheia entrou no escopo?** O escopo/limpeza usava marcador de título e `like`/`contains` do Payload são CONTAINS match (`ILIKE '%…%'`, sem ancoragem) — um UUID de fixture de outro spec que embutia o hex `c114` casou com o marcador e entrou no espelho (e na mira da limpeza).
|
||||
3. **Por que o escopo era textual?** O spec tratou marcador de título como ownership em vez de rastrear os ids que criou; nada pinava essa suposição.
|
||||
4. **Por que o defeito ficou latente desde 2026-08-11?** A colisão é rara (~29 posições hex por UUID; ~4% por arquivo/run) e o `retry: 1` (`vitest.config.mts`) redesenhava os UUIDs no retry, mascarando a contaminação intermitente.
|
||||
5. **Por que virou bloqueio de release agora?** A classe é sistêmica: arquivos int paralelos compartilham um banco e se isolam por texto compartilhado; a C153 adicionou 2 arquivos int (speechCatalog/speechImport) com novos runIDs, cruzando a probabilidade, e o verify do deploy roda a suíte int completa a cada commit — qualquer colisão vira bloqueio de publicação.
|
||||
|
||||
**Evidência:** o teste de regressão novo falha sem o fix (`expected 2 to be 1`) e passa com; o pin de convenção falha com o padrão antigo (`title: { like: 'C114%' }`); a falha de CI veio nas duas tentativas com manifestações distintas (contagem e `store` residual), e os testes irmãos só passaram no `(retry x1)`.
|
||||
|
||||
## Correção
|
||||
|
||||
Sem migration e sem mudança em código de produção — o defeito era do isolamento do spec, não do motor de sync:
|
||||
|
||||
- `tests/int/googleCalendarSync.int.spec.ts`: `ownedActivityIds: Set<number>` (`:103`) rastreia as linhas criadas; `createActivity(..., { track: false })` (`:128-148`) cria deliberadamente linhas alheias fora do escopo; o `afterEach` (`:105-120`) deleta por `id: { in: [...] }` em vez de título; `runSync` (`:168-178`) usa `activityWhere: { id: { in: [...ownedActivityIds] } }`; o comentário de convenção do header foi atualizado (marcador textual não é ownership) e um teste de regressão novo — "a foreign title CONTAINING c114 stays out of the scope (id-scoped mirror)" (`:250`) — pina o cenário exato do bug.
|
||||
- `tests/unit/codebaseConventions.unit.spec.ts`: novo describe "int fixture scoping avoids hex title markers" (`:771`), que varre `tests/int` e bane `title: { like|contains: '<hex-only 3+>' }`.
|
||||
|
||||
Resolve a causa: o escopo e a limpeza passam a ser ownership por id (à prova de colisão de UUID), e a suposição antiga ("marcador de título isola") deixa de ser aceita sem falhar um teste.
|
||||
|
||||
## Verificação
|
||||
|
||||
- Teste de regressão: `tests/int/googleCalendarSync.int.spec.ts:250` "a foreign title CONTAINING c114 stays out of the scope (id-scoped mirror)" — falha sem o fix (`expected 2 to be 1`) e passa com
|
||||
- Suíte: verificador independente rodou a suíte int completa `VITEST_MAX_WORKERS=4 pnpm test:int` = 83 files / 744 tests verdes (antes: 83 files / 743 tests, 1 failed no CI) e os 6 specs da família Google (47 tests) verdes; `pnpm gate:fast` verde; o pin de convenções falha com o padrão antigo
|
||||
- CI: pendente — PR do fix a preencher no merge
|
||||
- Prod: pendente — dispatch manual do `deploy.yml` pelo humano após o merge; confirmação do humano pendente
|
||||
|
||||
## Prevenção
|
||||
|
||||
| Estratégia | Custo | Estado |
|
||||
| --------------------------------------------------------------------------------------------------------------- | ------ | -------------------------------------------------------------------- |
|
||||
| Escopo e limpeza do spec Google por ids das linhas criadas (`ownedActivityIds`) + teste de regressão | barata | implementada agora (PR do fix — a preencher no merge) |
|
||||
| Pin de convenção em `tests/unit/codebaseConventions.unit.spec.ts` banindo marcador de título hex em `tests/int` | barata | implementada agora (PR do fix — a preencher no merge) |
|
||||
| Convenção documentada no header do spec (marcador textual não é ownership) | barata | implementada agora (PR do fix — a preencher no merge) |
|
||||
| Isolamento de banco/schema por arquivo ou suíte int serial (infra de teste nova) | cara | documentada — não implementada neste fluxo; candidata a Issue futura |
|
||||
| Operador de prefixo no Payload ou remoção do `retry: 1` (mudança de contrato/framework) | cara | documentada — não implementada neste fluxo; candidata a Issue futura |
|
||||
|
||||
**Estratégia implementada:** ownership por id no spec (escopo do espelho e limpeza), teste de regressão do cenário "título alheio contendo o marcador", pin de convenção que bane marcador de título hex em `tests/int` e a convenção documentada no header — a suposição errada passa a falhar na autoria, não no verify do deploy.
|
||||
|
||||
**Estratégia documentada (cara):** isolamento de banco/schema por arquivo ou suíte int serial (infra de teste nova) e operador de prefixo no Payload ou remoção do `retry: 1` (mudança de contrato/framework). Candidatas a Issue futura — não implementadas neste fluxo.
|
||||
|
||||
## Lições
|
||||
|
||||
- **Marcador textual não é ownership:** casar por texto compartilhado num banco compartilhado é isolamento frágil; rastrear os ids que o próprio spec criou é à prova de colisão.
|
||||
- **`like`/`contains` do Payload são contains-match:** o `%` no padrão não ancora nada — `like: 'C114%'` vira `ILIKE '%C114%'`. Quem lê `like` esperando prefixo se engana.
|
||||
- **`retry: 1` mascara flakes de isolamento:** a segunda tentativa redesenhava os UUIDs e escondia a contaminação; o verde com `(retry x1)` era sinal precoce, não ruído.
|
||||
- **Comentário de convenção que documenta o mecanismo errado reforça a falsa confiança:** o header do spec declarava o isolamento por título como garantia — o comentário precisou ser corrigido junto com o código.
|
||||
|
|
@ -7,8 +7,12 @@
|
|||
// stable without touching other specs:
|
||||
//
|
||||
// 1. Any NEW spec here that asserts on mirror counts MUST scope its passes
|
||||
// with its own title prefix via `activityWhere` (this file uses
|
||||
// `{ title: { like: 'C114%' } }`) — never count on an empty window.
|
||||
// by the ids of the activities THIS spec created via `activityWhere`
|
||||
// (`{ id: { in: [...] } }`) — never count on an empty window. A title
|
||||
// marker is NOT enough: Payload's `like`/`contains` are CONTAINS matches
|
||||
// (`ILIKE '%…%'`), so a marker of hex characters (e.g. `C114`) also
|
||||
// matches a fixture UUID that embeds it — that is how a parallel spec's
|
||||
// row leaked into this mirror and broke the exact counts (deploy verify 2026-09-13).
|
||||
// 2. Specs elsewhere that create pass-through activities in the window
|
||||
// keep the per-test cleanup from `installCampaignFixtures` (fixtures
|
||||
// are owned and removed beforeEach/afterEach) — never leak rows past
|
||||
|
|
@ -90,23 +94,43 @@ describe('campaign Google calendar sync engine (C114)', () => {
|
|||
/** Config rows this file created — cleanup never touches other specs' rows. */
|
||||
const ownedConfigIds = new Set<number>()
|
||||
|
||||
/**
|
||||
* Activity ids this spec created — the mirror scope AND the cleanup set.
|
||||
* Ownership by id is collision-proof: a title marker is not (Payload's
|
||||
* `like`/`contains` match anywhere in the string — the deploy verify blocker
|
||||
* of 2026-09-13).
|
||||
*/
|
||||
const ownedActivityIds = new Set<number>()
|
||||
|
||||
afterEach(async () => {
|
||||
// Teqo never depends on Google: the engine must never throw into callers.
|
||||
// Cleanup happens per test; the env is restored by the credential helper.
|
||||
await payload.delete({
|
||||
collection: 'activity',
|
||||
where: { title: { like: 'C114%' } },
|
||||
overrideAccess: true,
|
||||
})
|
||||
if (ownedActivityIds.size > 0) {
|
||||
await payload.delete({
|
||||
collection: 'activity',
|
||||
where: { id: { in: [...ownedActivityIds] } },
|
||||
overrideAccess: true,
|
||||
})
|
||||
ownedActivityIds.clear()
|
||||
}
|
||||
for (const id of ownedConfigIds) {
|
||||
await payload.delete({ collection: 'googleCalendarSync', id, overrideAccess: true })
|
||||
}
|
||||
ownedConfigIds.clear()
|
||||
})
|
||||
|
||||
const createActivity = async (overrides: Record<string, unknown> = {}) => {
|
||||
/**
|
||||
* Creates an activity this spec OWNS by default (mirror scope + cleanup).
|
||||
* Pass `{ track: false }` for a deliberately foreign row: it exists in the
|
||||
* window but must stay out of the scoped mirror (the fixture cleanup still
|
||||
* owns it through the proxy — the flag only keeps it out of the scope).
|
||||
*/
|
||||
const createActivity = async (
|
||||
overrides: Record<string, unknown> = {},
|
||||
options: { track?: boolean } = {},
|
||||
) => {
|
||||
const municipality = await campaignFixtures().getMunicipality()
|
||||
return payload.create({
|
||||
const activity = await payload.create({
|
||||
collection: 'activity',
|
||||
data: hookFilledCreateData<'activity'>({
|
||||
title: `C114 ${crypto.randomUUID().slice(0, 8)}`,
|
||||
|
|
@ -119,6 +143,8 @@ describe('campaign Google calendar sync engine (C114)', () => {
|
|||
depth: 0,
|
||||
overrideAccess: true,
|
||||
})
|
||||
if (options.track !== false) ownedActivityIds.add(activity.id)
|
||||
return activity
|
||||
}
|
||||
|
||||
const createConfig = async (calendarId: string) => {
|
||||
|
|
@ -133,11 +159,11 @@ describe('campaign Google calendar sync engine (C114)', () => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Runs a sync pass scoped to THIS spec's fixture (`C114%` titles). The int
|
||||
* suite runs files in parallel against one shared database and other specs
|
||||
* create activities inside the mirror window — a full-scope pass would count
|
||||
* them and flake the global assertions (C126). Production callers omit the
|
||||
* scope and keep the espelho cheio.
|
||||
* Runs a sync pass scoped to the activities THIS spec created. The int suite
|
||||
* runs files in parallel against one shared database and other specs create
|
||||
* activities inside the mirror window — a full-scope pass would count them
|
||||
* and flake the global assertions (C126; deploy verify blocker of
|
||||
* 2026-09-13). Production callers omit the scope and keep the espelho cheio.
|
||||
*/
|
||||
const runSync = (
|
||||
client: GoogleCalendarClient,
|
||||
|
|
@ -147,7 +173,7 @@ describe('campaign Google calendar sync engine (C114)', () => {
|
|||
runCampaignCalendarSync(payload, {
|
||||
reason,
|
||||
client,
|
||||
activityWhere: { title: { like: 'C114%' } },
|
||||
activityWhere: { id: { in: [...ownedActivityIds] } },
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -195,9 +221,43 @@ describe('campaign Google calendar sync engine (C114)', () => {
|
|||
// C114 fixture scope. The int suite runs files in parallel against one
|
||||
// shared database, so such rows exist at arbitrary times — the mirror
|
||||
// must not count them (the C126 race).
|
||||
const foreign = await createActivity({
|
||||
title: `C126 alheia ${crypto.randomUUID().slice(0, 8)}`,
|
||||
})
|
||||
const foreign = await createActivity(
|
||||
{ title: `C126 alheia ${crypto.randomUUID().slice(0, 8)}` },
|
||||
{ track: false },
|
||||
)
|
||||
await createConfig(calendarA)
|
||||
const store: GoogleRemoteEvent[] = []
|
||||
const client = createStubClient(store)
|
||||
|
||||
try {
|
||||
const outcome = await runSync(client)
|
||||
expect(outcome.created).toBe(1)
|
||||
expect(store).toHaveLength(1)
|
||||
expect(store.find((event) => event.id === googleEventIdForActivity(activity.id))).toBeTruthy()
|
||||
expect(
|
||||
store.find((event) => event.id === googleEventIdForActivity(foreign.id)),
|
||||
).toBeUndefined()
|
||||
} finally {
|
||||
// Never leak the foreign row into other specs (the exact bug this pin
|
||||
// guards against). The fixture proxy also auto-owns it, but the
|
||||
// explicit delete keeps the guarantee visible here.
|
||||
if (foreign) {
|
||||
await payload.delete({ collection: 'activity', id: foreign.id, overrideAccess: true })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('a foreign title CONTAINING c114 stays out of the scope (id-scoped mirror)', async () => {
|
||||
const activity = await createActivity()
|
||||
// The scope used to be a title marker (`like: 'C114%'`), but Payload's
|
||||
// `like`/`contains` match ANYWHERE in the string (`ILIKE '%…%'`) — a
|
||||
// parallel spec's fixture UUID embedding `c114` pulled its row into the
|
||||
// mirror, flaking the exact counts (deploy verify, 2026-09-13). The
|
||||
// scope is now ownership by id, so the marker's position is irrelevant.
|
||||
const foreign = await createActivity(
|
||||
{ title: `C126 alheia c114 ${crypto.randomUUID().slice(0, 8)}` },
|
||||
{ track: false },
|
||||
)
|
||||
await createConfig(calendarA)
|
||||
const store: GoogleRemoteEvent[] = []
|
||||
const client = createStubClient(store)
|
||||
|
|
@ -211,10 +271,6 @@ describe('campaign Google calendar sync engine (C114)', () => {
|
|||
store.find((event) => event.id === googleEventIdForActivity(foreign.id)),
|
||||
).toBeUndefined()
|
||||
} finally {
|
||||
// The afterEach only cleans `C114%` titles — never leak the foreign
|
||||
// row into other specs (the exact bug this pin guards against). The
|
||||
// fixture proxy also auto-owns it, but the explicit delete keeps the
|
||||
// guarantee visible here.
|
||||
if (foreign) {
|
||||
await payload.delete({ collection: 'activity', id: foreign.id, overrideAccess: true })
|
||||
}
|
||||
|
|
@ -227,9 +283,10 @@ describe('campaign Google calendar sync engine (C114)', () => {
|
|||
// scope. A scoped mirror is authoritative for its scope — its teqo
|
||||
// events are reconciled even when the delete-guard's full-scope view
|
||||
// would have kept them.
|
||||
const foreign = await createActivity({
|
||||
title: `C126 alheia ${crypto.randomUUID().slice(0, 8)}`,
|
||||
})
|
||||
const foreign = await createActivity(
|
||||
{ title: `C126 alheia ${crypto.randomUUID().slice(0, 8)}` },
|
||||
{ track: false },
|
||||
)
|
||||
await createConfig(calendarA)
|
||||
const store: GoogleRemoteEvent[] = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -768,6 +768,29 @@ describe('e2e name locators are anchored', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('int fixture scoping avoids hex title markers', () => {
|
||||
// Deploy verify blocker of 2026-09-13 — Payload's `like` and `contains` are
|
||||
// CONTAINS matches (the Postgres adapter compiles them to `ILIKE '%…%'`), so
|
||||
// a hex-only title marker like `C114` also matches a fixture UUID that
|
||||
// embeds it: a parallel spec's row leaks into the scoped mirror/cleanup and
|
||||
// flakes exact counts. Scope passes by the ids of the rows the spec created;
|
||||
// this sweep keeps the marker assumption from coming back.
|
||||
const hexTitleMarker = /title:\s*\{\s*(?:like|contains):\s*['"]([^'"]+)['"]/g
|
||||
|
||||
it('keeps hex-only title markers out of tests/int queries', () => {
|
||||
const offenders: string[] = []
|
||||
|
||||
for (const file of walkSourceFiles(resolve(repoRoot, 'tests/int'), ['.ts'])) {
|
||||
const source = readFileSync(file, 'utf8')
|
||||
for (const match of source.matchAll(hexTitleMarker)) {
|
||||
if (/^[0-9a-f]{3,}%?$/i.test(match[1]!)) offenders.push(`${repoPath(file)}: ${match[0]}`)
|
||||
}
|
||||
}
|
||||
|
||||
expect(offenders, 'scope by owned ids instead of a title marker (deploy verify 2026-09-13)').toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('public site metadata global access', () => {
|
||||
// An empty `metadata` global (fresh DB / poisoned unstable_cache) used to
|
||||
// crash `next build` via raw field access. Every `getCachedGlobal('metadata')`
|
||||
|
|
|
|||
Loading…
Reference in a new issue