Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5534dd0
feat(sync): W1 measurement contract — source-attributed sync instrume…
Jurij89 Aug 2, 2026
3cc2e8a
test(sync): close W1's R1/R2/R5 ALS conditions with discriminating tests
Jurij89 Aug 2, 2026
9bc84f4
fix(sync): add `control-plane` source and protect the I9 bucket contract
Jurij89 Aug 3, 2026
6bf6fbb
test(sync): delete a responder assertion that executed no responder code
Jurij89 Aug 3, 2026
2456be4
test(sync): drop the dead import and name the real responder entry point
Jurij89 Aug 3, 2026
737dd38
fix(w1): close the review round — Prometheus byte spelling, teardown …
Jurij89 Aug 3, 2026
ce0e398
docs(w1): record that the teardown guard resolves #2029 without maski…
Jurij89 Aug 3, 2026
9e6dd5d
fix(w1): wire the two gates that could not fire, and narrow the lane …
Jurij89 Aug 3, 2026
b2be6f8
fix(w1): bind the queried metric names to the core declaration, count…
Jurij89 Aug 3, 2026
df1ef6c
fix(w1): close D4 — a releaseCatchupJob that deletes nothing passed t…
Jurij89 Aug 3, 2026
904b210
fix(w1): zero-fill each byte leg, bind the source vocabulary, make te…
Jurij89 Aug 3, 2026
a701c1a
fix(w1): one source of truth per label vocabulary, and actually run t…
Jurij89 Aug 3, 2026
487fc0d
fix(ci): make the W1 promtool step lint-clean and correct on the suc…
Jurij89 Aug 3, 2026
55af936
fix(w1): pre-send abort boundary on the changelog lane, and bind I7/I…
Jurij89 Aug 3, 2026
2c65f37
fix(w1): classify changelog terminals from the captured stop signal, …
Jurij89 Aug 3, 2026
4ce70ec
fix(w1): make the I4 operation classifier causal too, and fix the tes…
Jurij89 Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,33 @@ jobs:
- name: Test repository scripts
run: pnpm run test:scripts

# W1 §8.3 packet reachability. This job is the right home because it is
# the one lane that installs the full workspace without sharding, and the
# gate asks the REAL Vitest resolver (`vitest list --filesOnly`) which
# files each pinned config would run.
#
# It exists because a packet file can be present on disk, tracked, and
# still contribute nothing: Vitest treats positionals as FILTERS against
# `include`, never as additions, so a suite missing from the include array
# is silently skipped and the command still exits 0. Leaving the gate
# uninvoked would have reproduced that exact failure one level up — a
# check that cannot fire is not a check.
- name: Verify the W1 §8.3 test packet is reachable
run: pnpm run verify:w1-packet

# A18's benchmark was previously only syntax-checked, which proves nothing
# about the thing it exists to measure: it loads the REAL record helpers
# out of `packages/agent/dist`, so a missing build output, a renamed
# export or a changed OTel constructor shape would leave A18 with no
# usable benchmark and `node --check` would still pass.
#
# `--ci` records the verdict WITHOUT gating on it, deliberately: a shared
# runner's timing variance must never fail a build. What is gated here is
# the mechanism — real seam loads, both arms run, JSON report is produced.
# Tiny page/round counts keep it a smoke test, not a measurement.
- name: Smoke the W1 A18 benchmark (real seam, non-gating budget)
run: pnpm run bench:w1-sync-telemetry:smoke

- name: Package build outputs
run: |
set -euo pipefail
Expand Down
90 changes: 90 additions & 0 deletions .github/workflows/observability-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,32 @@ name: Observability artifacts
# smoke-tests the operator-facing render mode (concrete datasource UIDs +
# node-label profile), so a regression there can't hide behind the
# committed-defaults check.
#
# The W1 sync-measurement artifacts (tools/observability/w1/) get two further
# steps, because "generated" is not "correct" and neither one is "valid
# PromQL": verify-w1-render.mjs asserts the semantic contract (instrument
# inventory, dual native/translated metric spellings, the source-family
# mapping, the all-source denominators, a node filter on every selector, both
# observation windows, and report/fixture expression identity), and a PINNED
# promtool container really PARSES the emitted rule fixture. Without those the
# path filter below would trigger on a W1 edit while validating none of its
# contents.
on:
pull_request:
paths:
- 'tools/observability/**'
# the gate must also verify itself when the gate changes
- '.github/workflows/observability-artifacts.yml'
# …and it must run when the SOURCES it mirrors change, not only when the
# mirror does. verify-w1-render.mjs holds its own copies of the W1
# instrument inventory (names + units) and the eight-member source
# vocabulary, so a rename of `dkg.sync.attempt.request_bytes` or a new
# SYNC_ADMISSION_SOURCES member in a PR that never touches
# tools/observability would leave every W1 query stale AND skip the only
# check that could say so. A verifier that mirrors a contract has to be
# triggered by that contract.
- 'packages/core/src/telemetry-api.ts'
- 'packages/agent/src/sync/policy.ts'
workflow_dispatch:

concurrency:
Expand Down Expand Up @@ -42,6 +62,17 @@ jobs:
- name: Verify committed artifacts match the generator
run: node tools/observability/generate-observability.mjs --check

# The step above normalizes CRLF/LF before comparing, so a Windows
# checkout (core.autocrlf=true rewrites every artifact to CRLF — the
# metrics dashboard alone holds ~660 pairs) is not permanently red. But a
# comparison that answered "equal" to EVERYTHING would also pass that
# step, forever. This one builds its own CRLF and LF artifact trees plus
# four corrupted ones and runs the real command against each, pinning
# both directions — which also exercises the CRLF path here on Linux,
# where git only ever produces LF.
- name: Verify check mode is line-ending agnostic but still catches drift
run: node tools/observability/verify-check-mode.mjs

# verify-profile-render.mjs PARSES the rendered JSON and asserts the
# whole profile-sensitive surface (every metrics target, the $node
# variable, per-node alert exprs + summaries, notification group-bys,
Expand All @@ -57,3 +88,62 @@ jobs:
--vm-uid test-vm-uid --loki-uid test-loki-uid --prom-node-label service_instance_id
node tools/observability/verify-profile-render.mjs /tmp/render \
--prom-node-label service_instance_id --vm-uid test-vm-uid --loki-uid test-loki-uid

# W1 decision queries: the semantic contract behind the numbers, checked
# per selector rather than by sampling. Run against BOTH renders so the
# W1 queries are proven to follow --prom-node-label like every other
# profile-sensitive surface (the /tmp/render tree is produced by the
# step above).
- name: Verify the W1 sync-measurement artifacts

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: W1 semantic verification is not triggered by the source contracts it mirrors

What's wrong
The new W1 verifier gives good coverage when observability artifacts change, but the workflow does not run for changes to the production telemetry contract that the verifier is supposed to guard. That leaves an important stale-query regression unverified.

Example
A later PR renames dkg.sync.attempt.request_bytes or adds a new SYNC_ADMISSION_SOURCES member without touching tools/observability; the W1 queries stay stale, but this workflow does not run because the changed path is outside its filter.

Suggested direction
Expand the workflow path filter or add an equivalent CI check in the main test workflow so telemetry-contract-only edits cannot skip the W1 render verifier.

For Agents
Update .github/workflows/observability-artifacts.yml so the W1 verification job also runs when the production sources that define W1 metric names/units/source families change. At minimum include packages/core/src/telemetry-api.ts and packages/agent/src/sync/policy.ts; consider any other source files the verifier intentionally mirrors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9e6dd5d30. Same class as the packet-gate finding, and equally right: a verifier that mirrors a contract has to be triggered by that contract, or it is a check that cannot fire on the change that matters.

verify-w1-render.mjs holds its own copies of the W1 instrument inventory (names + units) and the eight-member source vocabulary — deliberately, so the artifacts are checked against an independent statement of the contract rather than against themselves. But that independence is exactly what makes the path filter wrong: a PR renaming dkg.sync.attempt.request_bytes or adding a SYNC_ADMISSION_SOURCES member without touching tools/observability/ would leave every W1 query stale and skip the only check that could say so.

Added both sources you named to the filter:

- 'packages/core/src/telemetry-api.ts'
- 'packages/agent/src/sync/policy.ts'

Those are the two the verifier actually mirrors — instrument names/units come from telemetry-api.ts, and SYNC_ADMISSION_SOURCES is declared in sync/policy.ts (catchup-policy.ts and index.ts only re-export or consume it). The workflow already re-triggers on itself, which is the same principle applied to the gate.

Both workflow files re-parsed after the edit.

run: |
set -euo pipefail
node tools/observability/verify-w1-render.mjs tools/observability
node tools/observability/verify-w1-render.mjs /tmp/render --prom-node-label service_instance_id

# Real PromQL validation. `promtool` is not installed on the developer
# host, so the container IS the reproducible path on both Windows and
# Linux; the tag is readability, the DIGEST is the reproducibility
# contract. `--entrypoint promtool` is REQUIRED: the image entrypoint is
# ["/bin/prometheus"], so the un-overridden form fails with
# `prometheus: error: unexpected promtool`.
- name: Parse the W1 rule fixture (pinned promtool)
run: |
set -euo pipefail
docker run --rm -v "${PWD}/tools/observability:/w" --entrypoint promtool \
prom/prometheus@sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218 \
check rules /w/w1/w1-rules.yaml

# `check rules` proves the expressions PARSE. It is blind to what they
# RETURN, and the defect that motivated these tests parsed perfectly:
# PromQL binary `+` yields an EMPTY result when either operand is empty,
# so the byte totals went blank in any window where no response ever
# arrived — while real request bytes had been recorded.
#
# The `${node:regex}` Grafana variable is substituted with `.*` first: it
# is a dashboard placeholder, not PromQL, and against real series it
# matches nothing — so without this every rule would evaluate empty and
# the tests would pass vacuously. ONLY that variable is substituted; the
# expression structure under test is the committed one.
- name: Unit-test the W1 rule semantics (pinned promtool)
run: |
set -euo pipefail
mkdir -p /tmp/w1-promtool
# `[$]` is a literal dollar. A bare backslash-dollar inside single
# quotes trips SC2016, and in BRE `$` is only an anchor at
# end-of-pattern anyway, so this form is both lint-clean and exact.
# (Do not start a comment line with the linter's name — it is then
# parsed as a directive, which is how this step first failed.)
sed 's/[$]{node:regex}/.*/g' tools/observability/w1/w1-rules.yaml > /tmp/w1-promtool/w1-rules.yaml
cp tools/observability/w1/w1-rules.test.yaml /tmp/w1-promtool/
# Fail loudly if the substitution did nothing — a silently unchanged
# file would make every assertion below vacuous. Written as `if`
# rather than `grep … && { exit 1; }`: under `set -e` that form
# returns 1 from the whole list on the SUCCESS path (grep finds
# nothing), which would fail the step exactly when it should pass.
if grep -q 'node:regex' /tmp/w1-promtool/w1-rules.yaml; then
echo 'placeholder substitution failed; tests would be vacuous' >&2
exit 1
fi
docker run --rm -v /tmp/w1-promtool:/t:ro --entrypoint promtool \
prom/prometheus@sha256:6559acbd5d770b15bb3c954629ce190ac3cbbdb2b7f1c30f0385c4e05104e218 \
test rules /t/w1-rules.test.yaml
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"build:runtime": "pnpm run build:runtime:packages && pnpm --filter @origintrail-official/dkg-node-ui run build:ui",
"test": "turbo test && pnpm run test:scripts",
"test:scripts": "node --test scripts/lib/__tests__/*.test.mjs",
"verify:w1-packet": "node scripts/verify-w1-packet.mjs",
"bench:w1-sync-telemetry:smoke": "node packages/agent/scripts/bench-sync-telemetry.mjs --ci --json --pages 20 --warmup 5 --rounds 2",
"test:watch": "vitest --config vitest.config.ts",
"test:coverage": "turbo test:coverage",
"bench": "pnpm --filter @origintrail-official/dkg-storage build && esbench --config esbench.config.mjs",
Expand Down
Loading
Loading