Skip to content

Commit e1daae5

Browse files
authored
Merge pull request #1270 from ConductionNL/diag/seed-names-unbound-manifest-schemas
diag(e2e): name the manifest-bound schema the seed cannot resolve
2 parents 031171c + 454dfee commit e1daae5

1 file changed

Lines changed: 93 additions & 3 deletions

File tree

tests/e2e/ci-seed.sh

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,11 +291,15 @@ fi
291291
# loop skipped everything. Verify against OpenRegister directly, by the same
292292
# slugs the fixtures resolve by.
293293
verify() {
294-
python3 - "$1" "$2" <<'PY'
294+
python3 - "$1" "$2" "$APP_DIR" <<'PY'
295295
import json
296+
import os
296297
import sys
297298
298-
path, kind = sys.argv[1], sys.argv[2]
299+
path, kind, app_dir = sys.argv[1], sys.argv[2], sys.argv[3]
300+
301+
# The hand-maintained floor: what the fixtures and workflow specs create and
302+
# read through /objects/openconnector/<schema>.
299303
required = {
300304
'registers': ['openconnector'],
301305
# tests/e2e/workflows/_fixture.ts creates Sources / Mappings /
@@ -306,6 +310,66 @@ required = {
306310
'endpoint', 'consumer', 'event'],
307311
}[kind]
308312
313+
314+
def manifest_schemas():
315+
"""Every schema a manifest page BINDS A ROUTE TO.
316+
317+
🔴 THE HAND-MAINTAINED LIST ABOVE CANNOT KEEP UP WITH THE MANIFEST, and on
318+
2026-08-16 it did not: `SynchronizationRuns` (`/synchronization-runs`,
319+
schema `synchronization_run`, shipped by the `sync-run-progress.json`
320+
register fragment) was never in it. When that binding did not resolve on
321+
the CI instance, the seed said `schemas OK` and the failure surfaced two
322+
jobs later as a console assertion reading
323+
324+
Error fetching openconnector-synchronization_run collection: Proxy(Object)
325+
326+
— which names neither the schema nor the seed. A page whose route is
327+
declared but whose collection cannot be fetched is a seeding failure, and
328+
it belongs here, by name, before any spec runs.
329+
330+
Derived from the manifest rather than listed, so a page added tomorrow is
331+
covered without anyone remembering this file.
332+
"""
333+
manifest_path = os.path.join(app_dir, 'src', 'manifest.json')
334+
try:
335+
with open(manifest_path, encoding='utf-8') as handle:
336+
pages = json.load(handle).get('pages') or []
337+
except (OSError, json.JSONDecodeError) as exc:
338+
# Not fatal: this is an ADDITION to the floor above, and a manifest we
339+
# cannot read must not take the checks that do work down with it.
340+
print(f'::warning::could not read src/manifest.json for schema '
341+
f'verification ({exc}); falling back to the hand-listed slugs.')
342+
return []
343+
344+
found = []
345+
for page in pages:
346+
if not isinstance(page, dict):
347+
continue
348+
config = page.get('config')
349+
if not isinstance(config, dict):
350+
continue
351+
# Only pages bound to THIS register — a page pointing at another app's
352+
# register is that app's to provision.
353+
if config.get('register') != 'openconnector':
354+
continue
355+
schema = config.get('schema')
356+
if isinstance(schema, str) and schema:
357+
found.append(schema)
358+
return found
359+
360+
361+
# ⚠️ ADVISORY, NOT A GATE — deliberately.
362+
#
363+
# The hand-listed floor stays a hard error: those slugs are what the fixtures
364+
# CREATE, so without them the suite cannot run at all. The manifest-derived set
365+
# is reported as a warning instead, because promoting it would turn today's ONE
366+
# failing spec into a failed seed and take the other 165 passing specs with it.
367+
# The job's verdict should keep coming from the specs; this exists so the log
368+
# NAMES the schema instead of leaving `Proxy(Object)` to be reverse-engineered.
369+
advisory = []
370+
if kind == 'schemas':
371+
advisory = sorted(set(manifest_schemas()) - set(required))
372+
309373
with open(path, encoding='utf-8') as handle:
310374
raw = handle.read()
311375
try:
@@ -318,7 +382,33 @@ except json.JSONDecodeError:
318382
items = body if isinstance(body, list) else body.get('results', [])
319383
slugs = {item.get('slug') for item in items if isinstance(item, dict)}
320384
missing = [slug for slug in required if slug not in slugs]
321-
print(f'[ci-seed] {kind} present ({len(slugs)}): {sorted(s for s in slugs if s)[:60]}')
385+
386+
# ⚠️ PRINT THE REQUIRED SLUGS, NOT A TRUNCATED DUMP OF EVERYTHING PRESENT.
387+
# This used to print `sorted(slugs)[:60]` beside a `len(slugs)` of 71 — an
388+
# instrument whose evidence disagreed with its own count, cut off alphabetically
389+
# at 'rule'. `synchronization_run` sorts after that, so the one slug in question
390+
# could not be seen either way, and reading the line gave a false answer in both
391+
# directions. The full count still gets printed; what gets ENUMERATED is the set
392+
# this check is actually about.
393+
present_required = [slug for slug in required if slug in slugs]
394+
print(f'[ci-seed] {kind}: {len(slugs)} present on the instance; '
395+
f'{len(present_required)}/{len(required)} required -> {present_required}')
396+
397+
if advisory:
398+
unbound = [slug for slug in advisory if slug not in slugs]
399+
if unbound:
400+
print(f'::warning::{len(unbound)} manifest page(s) bind a route to a schema '
401+
f'that is NOT on this instance: {unbound}')
402+
print('::warning::Each one\'s index page will fail to fetch its collection, '
403+
'and the spec failure reads "Error fetching openconnector-<schema> '
404+
'collection: Proxy(Object)" — which names neither the schema nor the '
405+
'seed. That is what this warning is for.')
406+
print('::warning::Check that the register.d fragment declaring it is merged '
407+
'into the descriptor AND that the import attached it to the '
408+
'openconnector register.')
409+
else:
410+
print(f'[ci-seed] all {len(advisory)} manifest-bound schema(s) resolve.')
411+
322412
if missing:
323413
print(f'::error::OpenConnector {kind} missing after import: {missing}')
324414
sys.exit(1)

0 commit comments

Comments
 (0)