main was 16 days and 259 commits behind the line the product is actually built on, at contract 0.30.0 against 0.35.0, with no per-PC credential code at all. It was neither current nor curated - just stale. This brings it forward and, at the same time, makes it mean something: main is what a receiving site installs, and nothing else. WHAT IT CARRIES. Twelve plugins - backups, computers, endpoints, guides, knowledgebase, machines, network, notifications, printers, slides, tools, warranty - plus applications, which is manifest-less core and always ships. The only manifest dependency inside that set is endpoints on computers, and both are in it, so the closure is self-contained. WHAT IT DOES NOT. employees, geenforce, measuringtools, printedparts and usb stay on the site line. So do the Windows installer tree, the VM rehearsal helpers, the agent tooling config, one site's data loader, the internal audit and findings records, and the planner spreadsheet. 1,135 tracked files against 1,438. A REMOVAL THAT BREAKS THE APP IS NOT A REMOVAL, and two nearly shipped: deploy/site-profile-universal.json still named all seventeen plugins. It is the file docs/DEPLOY.md tells a site to provision from, so a fresh install would have failed at `flask plugin apply-profile` with PluginError - the first thing a receiving site does, broken by the branch meant to serve them. It now names the twelve that exist, and its comment says the two must agree. agent/ was never refreshed. Whoever populated this branch copied shopdb/, plugins/, frontend/ and tests/ from the site line and left the agent tree at its September state: 22 C# and PowerShell sources missing, including AgentCredentials, EnrollmentService, AgentHttpFactory and the fleet-alert client, plus one file the site line had deleted still present. The old tree is internally consistent, so it compiles, and nothing in CI builds it - every gate was green over a server that speaks enrollment and per-PC credentials to a bundled agent source that cannot. Resynced whole. DECISIONS THIS ENCODES, rather than defers: - The plugin-authoring lab builds printedparts, whose manifest depends on employees, so it could not stay. The lab remains on the site line and main ships no build-along tutorial. That is a real loss for an adopting developer and is recorded here rather than discovered later. - Recognition, Training and Recertification notifications are no longer seeded. Each splits one notification into a card per employee and renders a photo, which needs a plugin this branch does not bundle. Seeding them would offer an author a type that cannot resolve anybody. A site that installs employees gets them from that plugin's own seed. - Windows/IIS is documented as the legacy path throughout, and the README leads with Docker, because that is what a receiving site gets. The API description is regenerated for THIS branch - 347 paths and 483 operations, down from 415 and 573 - so the published contract describes what is here rather than what the site line serves. docs/CONFIG.md lost the two measuring-tool label keys the code no longer declares, which two lanes had decided opposite ways. Suite: 3124 passed, 6 skipped. Naming and style gates pass. The docs gates run rather than skip, and every index line count is derived from the file it names.
153 lines
6.3 KiB
Bash
Executable File
153 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Prove the gates actually RAN.
|
|
#
|
|
# Several checks skip themselves when the file they read is absent:
|
|
#
|
|
# tests/test_api_inventory_parity.py skipif docs/api-inventory.json missing
|
|
# tests/test_openapi_spec.py skipif docs/openapi.json missing
|
|
# tests/test_docs_publishable.py skipif docs/*.md missing
|
|
#
|
|
# That is correct in a published checkout, where docs/ becomes the wiki and
|
|
# there is genuinely nothing to check. It was silently wrong on GitHub, which
|
|
# runs against exactly that checkout: the modules skipped, CI reported green,
|
|
# and an outside branch added nine routes documented nowhere and passed.
|
|
#
|
|
# So the list is split. Some gates must run everywhere; the docs-dependent ones
|
|
# must run HERE, where the files exist, and are allowed to be absent there.
|
|
#
|
|
# A gate that stops running is worse than no gate: nobody looks at it again.
|
|
# This asserts each named module reported at least one PASS and no SKIPS.
|
|
#
|
|
# Usage:
|
|
# bash scripts/check-gates-ran.sh # runs the gates itself
|
|
# bash scripts/check-gates-ran.sh report.txt # reads an existing -rs report
|
|
set -uo pipefail
|
|
|
|
cd "$(dirname "$0")/.."
|
|
|
|
# Gates that must run in EVERY checkout, published or internal. A skip here is
|
|
# always a fault.
|
|
GATES_EVERYWHERE=(
|
|
"tests/test_openapi_spec.py"
|
|
"tests/test_api_inventory_parity.py"
|
|
"tests/test_core/test_public_read_surface.py"
|
|
"tests/test_core/test_authz.py"
|
|
"tests/test_plugin_migrations.py"
|
|
)
|
|
|
|
# Gates that need files the publication export deliberately leaves behind -
|
|
# docs/*.md becomes the wiki, so a published checkout genuinely has nothing to
|
|
# check and skipping is correct there. In THIS repo, skipping means something
|
|
# broke.
|
|
#
|
|
# The inventory parity gate used to be here, and that was the hole it exists to
|
|
# close. docs/api-inventory.json is not wiki prose: it is a generated artifact,
|
|
# tracked exactly like docs/openapi.json beside it, because the app publishes the
|
|
# spec built from it. Keying the gate to prose meant a checkout with no docs/*.md
|
|
# - which is every fresh clone and every worktree - called itself published and
|
|
# ran the route check nowhere. That is where 24 undocumented routes came in.
|
|
GATES_INTERNAL_ONLY=(
|
|
"tests/test_docs_publishable.py"
|
|
)
|
|
|
|
# A published checkout is one with no docs/ prose. openapi.json alone does not
|
|
# make it internal: that file ships because the app serves it at runtime.
|
|
#
|
|
# NOTE: docs/wiki-drafts/*.md does NOT count here, and that is a standing
|
|
# question rather than an oversight - see the comment on the skipif in
|
|
# tests/test_docs_publishable.py. Keep the two definitions identical.
|
|
if compgen -G "docs/*.md" > /dev/null; then
|
|
GATES=("${GATES_EVERYWHERE[@]}" "${GATES_INTERNAL_ONLY[@]}")
|
|
echo "==> Internal checkout: every gate must run"
|
|
else
|
|
GATES=("${GATES_EVERYWHERE[@]}")
|
|
echo "==> Published checkout: docs-dependent gates are expected to be absent"
|
|
fi
|
|
|
|
PYTHON="${PYTHON:-python}"
|
|
[ -x venv/bin/python ] && PYTHON=venv/bin/python
|
|
|
|
# REFUSE TO RUN WITHOUT A REAL INTERPRETER. In a git worktree there is no venv,
|
|
# `python` is often not on PATH at all, and the pytest line below ends in
|
|
# `|| true` - so the run died 127, the report was a single "command not found"
|
|
# line, every gate matched nothing, and the script still printed "All gates ran."
|
|
# and exited 0. A gate-checker that passes when it ran nothing is worse than no
|
|
# gate-checker, because its exit code is what everything else trusts.
|
|
if [ -z "${1:-}" ] && ! "$PYTHON" -c 'import pytest' >/dev/null 2>&1; then
|
|
echo "FAIL: '$PYTHON' cannot import pytest, so no gate can run here." >&2
|
|
echo " Set PYTHON=/path/to/python (a venv that has pytest) and re-run." >&2
|
|
exit 1
|
|
fi
|
|
|
|
report="${1:-}"
|
|
if [ -z "$report" ]; then
|
|
report="$(mktemp)"
|
|
# -rs lists every skip with its reason, which is the thing being checked.
|
|
# -rf and -rE add the "FAILED <nodeid>" and "ERROR <nodeid>" lines, which -rs
|
|
# alone does NOT emit: this script grepped for them anyway, so on a real
|
|
# failure it printed its header and then nothing at all.
|
|
"$PYTHON" -m pytest "${GATES[@]}" -q -rsfE > "$report" 2>&1 || true
|
|
fi
|
|
|
|
echo "==> Checking that every gate ran"
|
|
|
|
failed=0
|
|
for gate in "${GATES[@]}"; do
|
|
if [ ! -f "$gate" ]; then
|
|
echo " MISSING $gate - the file is not in this checkout"
|
|
failed=1
|
|
continue
|
|
fi
|
|
if grep -q "SKIPPED.*$(basename "$gate")" "$report"; then
|
|
reason=$(grep "SKIPPED.*$(basename "$gate")" "$report" | head -1 | sed 's/.*-- //')
|
|
echo " SKIPPED $gate"
|
|
echo " reason: ${reason:-not given}"
|
|
failed=1
|
|
continue
|
|
fi
|
|
echo " ran $gate"
|
|
done
|
|
|
|
# Name what failed, from whatever the report actually contains. This script can
|
|
# be handed a report somebody else produced, and that one may have been run
|
|
# without -rf, so the "FAILED <nodeid>" lines are not guaranteed to be there.
|
|
# The FAILURES section underlines are: pytest prints "____ test_name ____" above
|
|
# every traceback whether the short summary was asked for or not.
|
|
list_failures() {
|
|
if grep -qE '^(FAILED|ERROR) ' "$1"; then
|
|
grep -E '^(FAILED|ERROR) ' "$1"
|
|
return
|
|
fi
|
|
if grep -qE '^_{3,} .+ _{3,}$' "$1"; then
|
|
sed -n 's/^_\{3,\} \(.*[^_]\) _\{3,\}$/\1/p' "$1"
|
|
return
|
|
fi
|
|
# Nothing named. Hand over the count line rather than an empty list, so the
|
|
# reader knows to open the report instead of believing there is nothing here.
|
|
grep -E '[0-9]+ (failed|error)' "$1" | tail -1
|
|
echo "(no test names in this report; run without an argument to get them)"
|
|
}
|
|
|
|
# The old condition was "^[0-9]+ failed" or the bare word "error" anywhere,
|
|
# which matches a test name, a warning or a docstring. Anchor on the shapes
|
|
# pytest actually prints: a short-summary line, a count in the tally, or an
|
|
# interrupted collection.
|
|
if grep -qE '^(FAILED|ERROR) |(^|, )[0-9]+ (failed|error)|^!+ Interrupted' "$report"; then
|
|
echo
|
|
echo "Gate tests FAILED (not merely skipped):"
|
|
list_failures "$report" | sed 's/^/ /'
|
|
failed=1
|
|
fi
|
|
|
|
echo
|
|
if [ "$failed" -ne 0 ]; then
|
|
echo "FAIL: a gate did not run. A skipped gate reports green and checks nothing."
|
|
echo " If a docs-dependent gate skipped in THIS repo, the file it reads"
|
|
echo " was not generated: run scripts/gen_openapi.py and"
|
|
echo " scripts/gen_project_map.py. If one skipped in the PUBLISHED repo,"
|
|
echo " the export dropped a file the gate needs - see tools/export-github.sh."
|
|
exit 1
|
|
fi
|
|
echo "All gates ran."
|