Repository navigation
Closed
Labels
enhancementNew feature or requestNew feature or request
Description
Parent: #333. Blocked by #332.
Goal
Create the performance authority DockerMap currently lacks outside the Atlas: controlled, reproducible measurements for the actual operator path from process start to a useful Review answer.
This issue owns measurement, fixtures, evidence format and promotion gates only. Do not optimize production code here unless a tiny instrumentation hook is strictly required to make a stage measurable.
Required measurements
Use controlled 25 / 100 / 250-container reference fixtures and measure at least:
- daemon process start → HTTP listener accepting requests;
- listener ready → first authoritative Docker publication;
- Docker observation wall time;
- Compose correlation/enrichment wall time as a separate stage;
- daemon publication → Node/SSE observation;
- browser notification → coherent model accepted;
- coherent model accepted → Home/Review useful content rendered;
buildModel();- Findings derivation for representative 25/100/250 topology/evidence sizes;
- legacy Home topology-layout cost;
- Cmd-K open + query-to-results for representative query classes;
- production bundle/startup asset budget.
Methodology
- Pin runner class, CPU class, OS image, browser revision/flags, font environment, production build mode, fixture revision and source revision, following the discipline already used by Atlas performance evidence.
- Use warmed repeated samples and a reviewed percentile/median policy; do not gate on one noisy CI duration.
- Keep raw timings in the evidence artifact; derive summaries during review.
- Ordinary unit tests may validate evidence shape/math but must not pretend to be the controlled benchmark.
- Create an explicit baseline artifact/version so later candidates can be compared under an equivalent environment.
- Define regression limits from measured variance; do not invent aspirational absolute milliseconds before the baseline exists.
Fixture requirements
At minimum:
- 25-container Compose-heavy host;
- 100-container mixed relationship/mount/network density;
- 250-container stress host;
- provider-only revision change with Docker inventory unchanged;
- Docker topology change with providers unchanged;
- slow-but-bounded Compose projection;
- unavailable/slow optional provider.
Fixtures must be deterministic, secret-free and representative of existing contract bounds.
Acceptance criteria
- One documented command/job produces the complete controlled timing matrix.
- Results clearly distinguish backend collection, transport/notification, browser model work, rendering and search.
- Atlas's existing renderer performance evidence is reused where appropriate rather than duplicated.
- The baseline exposes whether Compose, SSE polling, model rebuilds or legacy layout dominate actual time.
- Promotion rules fail closed on incompatible runner/environment metadata.
- No production runtime telemetry/analytics are added.
- No optimization claim is accepted later without comparing against this baseline.
- Full normal checks remain green.
Deliverables
- performance contract/evidence schema;
- benchmark fixture definitions;
- baseline capture procedure;
- checked-in baseline metadata or reviewed external artifact location according to existing repo policy;
- a short architecture/performance document explaining exactly what each number does and does not prove.
Non-goals
- No performance fix itself.
- No browser analytics.
- No user tracking.
- No loosening of security/coherence to make numbers look better.
Risk: LOW · read_only_product_behavior: true · security_sensitive: false · data_migration: false · routing_mode: stable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Activity
Joncallim commentedon Sep 22, 2026
First increment pushed for review as a draft PR #346 — issue stays open, this is not a completion claim.
Landed so far (all green under
npm run check):measures/doesNotProve, the fixture set, the exact fixture × stage matrix, the pinned environment allowlist (incl. Docker revision), raw-sample validation, p95/median-of-three math, and themax(baseline × 1.25, baseline + 2)promotion gate — summaries are recomputed at review time so a supplied summary cannot influence a result;/containers/json,/networks,/volumes— proven end to end against the real daemon build (25 containers / 1 network / 5 volumes,mode: docker,dockerReachable: true);docs/testing/TIME_TO_ANSWER_EVIDENCE.md(what each number does and does not prove);npm run test:perfwired intocheck:js.Still outstanding before #335 can close: the one documented capture command driving all three controlled runs, the browser-side stage probes (reusing the existing Playwright harness), genuine daemon-side separation of Docker observation from Compose enrichment, and the first captured baseline artifact. No performance number is quoted and no optimization may be claimed until that baseline exists.
feat: add inert bench-only stage attribution to the daemon (#335)
docs: record the bench attribution hook and the remaining #335 work i…
feat: add the time-to-answer capture command and browser probes (#335)
feat: capture and interpret the first time-to-answer baseline (#335)
Joncallim commentedon Sep 23, 2026
#335 — completion evidence
Head:
71494a0(PR #346, baseb6904d5^). Product code unchanged since the measured revision —git diff b6904d5..71494a0 -- apps crates packagesshows only the new perf test file.1. One documented capture command
perf:time-to-answerowns the whole lifecycle: deterministic fixture Docker daemon, real daemon with bench attribution, real API (tsx), production web build, benchmark-only probe build, real Chromium. Private OS-reserved ports; process-group teardown (no pattern killing); explicit readiness waits; fail-closed on any incomplete cell; raw samples preserved when assembly fails; live deployment never touched.2. Complete matrix — 12 stages × 7 fixtures × 3 runs × 15 samples = 21 cells
Captured in 16.5 min on the pinned runner. Two harness defects were found by running it for real and fixed before the final capture: samples were nested one level too deep (a run-shape bug), and raw samples were not preserved when artifact assembly failed.
3. Baseline artifact
dockermap-v1/time-to-answer-baseline-1b6904d553bf9062da05e0375e40765820104b70fdockermap-v1/time-to-answer-fixtures-167f9b78b4e358c77d3980fbc8752dbcd33141e1898bb9112a9b9312224eaf9d2/srv/jonas/evidence/dockermap/time-to-answer-baseline-1.json(+-environment.json,-summary.txt)perf:summarizeEnvironment fingerprint:
linux-x86_64-dedicated/cpus-16vcpu/ubuntu-26.04/ kernel7.0.0-31-generic/ Node22.23.2/ rustc1.88.0/ Docker29.8.1/ Chromium1.61.0(flags pinned) / system-default fonts / production build / effectivessePollIntervalMs = 2000(the API's own default, never overridden).4. Interpretation (median of three run p95, ms)
Scenarios: provider-only revision change observation 1964.50; docker topology change 701.60; slow bounded Compose projection 8.46; unavailable optional provider 1918.03.
Top three contributors
Compose contribution, measured: 1.15 / 1.18 / 1.25 ms on the reference fixtures, 8.46 ms on the deliberately large bounded project, while it still executes inside the Docker publication budget. Nothing is decoupled here — #336 owns the change, and this baseline says the win there is real but small, not dominant. #336 should be judged against these numbers rather than an assumed large win.
Browser model work is not a bottleneck: buildModel 0.5–1.5 ms, findings derivation 0.07–0.92 ms, notification→render 12.7–34.6 ms.
High variance:
publicationToNodeObservationMs(min 0.42–396 ms vs max ~1976 ms). AlsodaemonStartToListenerMsandcommandQueryMs(2–4× spread). Stable: layout, bundle, Docker observation.What these numbers do not prove: the fixture daemon is deterministic and local (so Docker observation says nothing about a real engine/host); daemon, API, web and Chromium all ran on one runner over loopback (no network claim); Compose trees are synthetic (40 / 400 services); fixtures are synthetic topologies; one pinned runner class.
5. Promotion/regression gate
timeToAnswerPromotion.test.ts— 24 RED-checks, all failing for evidence reasons: mismatched runner class / CPU / OS image / kernel / Node / Rust / Chromium / Docker revision / fixture revision / SSE poll interval / font environment / browser flags / build mode; missing stage; undeclared stage; malformed sample count; wrong run count; negative or non-finite sample; supplied or fabricated summary; unknown baseline id; candidate abovemax(baseline × 1.25, baseline + 2 ms); plus a compatible candidate passing and boundary cases just inside and just outside the budget.6. Production-isolation proof
productionIsolation.test.mjsasserts the shipped bundle contains none of__dockermapProbe,__dockermapBench,__dockermapBenchHelpers,measureModelAcceptance,browserProbe,benchVite,perf:time-to-answer; that no production source or build script reaches the benchmark; that the daemon hook requires an absolute path, is enabled only byDOCKERMAP_BENCH_STAGE_TIMING_PATHand is referenced only bycache_refresh.rs/main.rs; and that the shipped bundle carries no analytics markers. No response, route, severity, cadence or generated schema changed.7. Gates
npm run checkgreen.npm run test:perf13/13 (8 fixture + 5 isolation);vitest src/lib/performance30/30.deleg_191f0357, 2 parallel read-only reviewers) — one contract/claims reviewer, one harness-vacuity/reproducibility reviewer.Unavailable gates, recorded rather than waived: Architect×2 and the Sol/frontier FINAL gate are not runnable — the canonical router is terminal (ModelArk and OpenCode Go disabled in provider state; Sol reserve-gated;
delegate_taskrefuses the Claude/Opus lane, whose launcher is session-limited). Dispositions are recorded in the delegation override receipt. Thechatgpt-codex-connectorbot is not active on this repository (PRs #327–#331 and #345 have zero bot reviews), so its silence is not a pass.PR #346 stays draft until the review round is clean; #336 and #337 are untouched by this run.
Joncallim commentedon Sep 23, 2026
Review round 1 — dispositions (PR #346 @
71494a0)Two independent read-only adversarial reviews ran on the explicit DeepSeek
overflow lane (
deleg_191f0357): one contract/claim-truth reviewer, oneharness-vacuity/reproducibility reviewer.
Verdicts: REJECT (P1×2, P2×3, P3×7) and APPROVE-WITH-FOLLOW-UPS (P2×2, P3×5).
No P0. The validation machinery, the closed artifact and the recomputed numbers
were verified strong — 46/46 cells exactly 3 runs × 15 samples, all 46 doc rows
recompute exactly, no vacuous cell, no min==max run. Baseline 1 is therefore
NOT merged and must not gate #336/#337/#338 until the items below are fixed and
the baseline is re-captured.
Confirmed defects to remediate
commandQueryMsnever measured query-to-results. It ended on"any
liexists", and the palette renders the full unfiltered listsynchronously on open, so the check passes before React's filtered re-render —
it would pass even with filtering entirely broken. Also
8080matches nothingin the fixture topology. Fix: derive the representative query from the fixture
(a port it actually publishes), require the filtered list to differ from the
pre-query list and to contain a matching item, and fail closed otherwise.
data. Reference-25 samples all sit in a 66 ms band of a 2000 ms interval and
the topology-change runs drift monotonically: the gap is set by the phase
offset between the daemon's fixed 2 s loop and the API's fixed 2 s poll, locked
by the harness's fixed startup sequence. Consequence: the headline
"poll floor = 87/64/68 %" is partly harness-ordering dependent, and the doc's
central distribution claim is wrong. Fix: jitter each trigger by a uniform
sub-interval delay so samples actually describe the poll-wait distribution,
and state the phase relationship and autocorrelation honestly in the doc.
stage-1/2 probe daemons shared
DOCKERMAP_BENCH_STAGE_TIMING_PATH, so the"warmed" samples are an uncontrolled interleave of cold-start first
observations and resident steady-state passes. Fix: give probe daemons no
sink.
pins
sourceRevision: b6904d5, but the harness that produced it existed onlyas uncommitted working-tree changes (the 15-sample loops land in
71494a0).Fix: commit the harness before capturing, refuse to capture from a dirty tree,
and record product and harness revision separately.
findingsDerivationMsis daemon-side work bucketed asbrowser-model,so the bucket's "where the time went" claim is wrong. Fix: bucket it as
backend-collectionand correct both docs.ends on any DOM mutation (a spinner or status pill would qualify) and stage 7
duplicates the same instant, carrying zero independent information. Fix:
require a rendered-text change for stage 6, end stage 7 on a Home-content
region repaint, and declare stage 7 not applicable to the two fixtures whose
revisions need not repaint Home.
46 cells.
ssePollIntervalMshardcoded rather than derived from or assertedagainst the API; the isolation "inert by default" check is string-based
(
sink()'s real env lookup has no behavioural test); the promotion RED-checksdo not pin the median-of-three aggregation;
--fixtureslacks the debug guardand is a guaranteed-waste footgun; the doc's variance ranges contradict its own
table;
dockerRevisionpins a host dimension the fixture never exercises;scenario-fixture premises (provider-only advance, provider unavailability) are
constructed but never asserted.
Verification the reviewers confirmed
row recomputes exactly from the raw samples; no run where min == max.
instant externally; stage 12 is a cold, uninstrumented context; fixture daemon
is byte-deterministic and its control route changes only inventory; teardown is
process-group based with per-cell socket/workdir/ports.
packages/contractsandapps/api: zero changes — no generated schema, noHTTP response, no production behaviour change. The daemon hook is inert when
unset and landed before this PR's base.
reach the real Docker socket; the sole real-Docker contact is a read-only
version query for environment pinning.
Next: remediate 1-8, commit the harness, re-capture baseline 1 (same pinned
environment), re-run the promotion/isolation gates, then a fresh verification
round on the new head before merge. Draft PR #346 stays draft; #336 and #337
remain untouched.
fix: remediate the #335 review findings before recapturing the baseli…
docs: rewrite the baseline interpretation from the corrected capture (#…
Joncallim commentedon Sep 23, 2026
Review round 2 — dispositions (PR #346 @
c0baa05, artifact38e0c650…)Fresh independent review of the reworked head (
deleg_c9f2f9ed), 2 read-onlyreviewers, prior verdict explicitly disregarded.
Verdicts: APPROVE-WITH-FOLLOW-UPS (P0 none, P1 none, P2×2, P3×4) and REJECT
(P0 none, P1×2, P2×1, P3×6). Baseline 2 is not merged and still must not be
the performance authority until the items below are fixed and recaptured.
Confirmed fixed (both reviewers, at code level)
first, success requires the list to change and still contain the token, and
the token is fixture-derived and not self-fulfilling. Mutation that would make
it vacuous: a reorder-only filter (see F5).
whole 2000 ms interval (reference-25 35.0–1991.2, reference-100 0.34–1869.1,
reference-250 0.27–1920.1, provider-only 0.54–1954.0), medians ≈ 776–859 ms.
Contract and docs correctly say only the measurement is de-correlated and the
two fixed 2 s cycles are unchanged. Jitter adds no inclusion bias (the sleep
precedes both timestamps) and the resulting phase is uniform.
3 runs × 15 samples, all 44 doc rows recompute exactly, bucket sums exact.
sourceRevision=harnessRevision=0714c870…, verified to exist and to contain the harness; the dirty-tree andrevision guards fail closed and the debug hatch cannot emit an artifact.
before/after for provider-only; emptied PATH plus a non-fresh provider state
assertion for unavailable-provider).
findingsDerivationMsisbackend-collectionin the contractand in the baseline doc. Guard hardening: the
dockerRevisioninformational change does not weaken the gate (14 other fields stay pinned, no
measured stage touches the host daemon, rationale is RED-tested). Probe-sink
separation is proven by the code path. No behavioural regression from the
jitter.
Defects to fix before the next capture
main.rs:141runsrefresh_cachebefore the listener binds, so the daemon'sfirst-ever observation lands in the bench sink;
capture.tsslices the first15 lines and nearest-rank p95 of 15 is the maximum, so the published
stage-3 numbers (7.91/9.36/15.34) are cold-start samples vs warmed medians of
1.6/3.3/5.3 ms. Same contamination on the slow-Compose fixture's stage 4
(p95 = first sample in all three runs) and a sign-flipped one on stage 9. Fix:
discard the first observation per stage (slice
[k..count+k)) and document thediscard. This is the last remnant of the defect class the rework targeted, and
it loosens that stage's promotion limit ~3×.
emit-metadatachecks only that therelease daemon exists; nothing binds it to
sourceRevision, and stages1/2/3/4/5/9 all come from it. Fix: record the binary's sha256 in the
environment, verify it at capture start, fail closed on mismatch.
in all 180 samples because the first text-changing commit always lands in Home
for the four declared fixtures, so stage 7 carries no independent information.
Decide and implement: either make it a genuinely distinct boundary (a fixture
whose published change alters Home's content, measured from the coherent
commit) or stop declaring it a separate stage.
44cells, not 46; the evidence doc's bucket tablestill lists
findingsDerivationMsunderbrowser-model; the promotionsentence must mention
dockerRevision; the reproduction section must state therequired checkout of the pinned revision; stage 6/7 coincidence must be
explained where the table presents stage 7.
than the unfiltered count, so a reorder-only filter cannot satisfy it.
harness trigger (their spread comes from provider-schedule drift) —
browserRevisionrecords the Playwright package version rather than theChromium build revision — crash teardown relies on process groups.
Next: fix 1-6, commit the harness, recapture baseline 2 (same pinned
environment), rerun all gates, then a third, fresh independent review before
merge. Draft PR #346 stays draft; no production behaviour changes; #336 and #337
remain untouched and must be judged against the corrected baseline.
fix: cold/warm stage split and daemon binary provenance (#335, round …
docs: correct the evidence and baseline docs to match the executable …
docs: state plainly that no accepted baseline exists yet (#335)
feat: instrument the real coherent-model acceptance seam (#335)
33 remaining items
fix(bench): repair capture syntax and guard harness compilation
fix(bench): restore capture entrypoint and type-check the harness
fix(bench): close the capture entrypoint brace
fix(bench): restore capture entrypoint scope
fix(bench): close stage-five observer scope
test(bench): record model layers and browser lifecycle for diagnosis
fix(bench): witness publication phase and lifecycle cleanup
test(bench): add sustained preconditioning gate
test(bench): enforce preconditioning depth
fix(bench): control stage-five publication phase
docs(perf): publish Baseline-4 authority and reconcile methodology-8 …
docs(perf): correct the seam-isolation protocol description (#335)
Establish end-to-end time-to-answer baselines and regression gates (#335
Joncallim commentedon Sep 27, 2026
Resolution Evidence
What changed (squash
aa023a7, basee8562d1, PR #346)fixture topology and fake Docker Engine API, the single end-to-end capture command, the
dedicated controlled Stage-5 sub-benchmark, the dedicated Stage-6/7 seam-isolation control,
composite assembly with per-record protocol provenance, the summarizer and the promotion
gate are now on
main.bdce6ae354d757d3318c514e10d620edb497918e(methodology
dockermap-v1/time-to-answer-methodology-8) and is now published as thecomparison authority in
docs/testing/TIME_TO_ANSWER_BASELINE.md, with the interpretationcontract in
docs/testing/TIME_TO_ANSWER_EVIDENCE.md.Why this resolves
#335 asked for end-to-end time-to-answer baselines and regression gates before any
performance work. The baseline now exists, is reproducible from a committed revision, and
covers the full declared fixture × stage matrix:
end-to-end+ 6controlled-poll-phase, 3controlled runs × 15 measured samples per record, 1 980 raw samples, each record naming
fixture, stage, measurement protocol, source evidence file and checkpoint SHA;
sample is observation 61; every burn-in retained for audit and excluded completely from
timing summaries; no stationarity claim, no adaptive trimming, no per-metric warm-up;
controlled-poll-phaseprotocol (sole owner of arm → mark → trigger→ identity-acknowledgement): 10 declared phases across the 2000 ms poll interval, 45
distinct trigger ids per fixture, maximum absolute observed-vs-declared phase error
≤ 1.0 ms, published as a phase-normalized figure;
end-to-endmeasurements;and Stage-5 phase control PASS across all six declared fixtures (grid span ≈ 1 795–1 799 ms,
tolerance 90 ms); daemon release binary digest identical before and after; capture 105.1 min.
Disclosed, not hidden — the
controlled-stage6-stage7-seam-isolationsupporting controlFAILED at this checkpoint:
after the control had already been re-scoped once. Disclosed limitation:
publication-level causal identity unavailable;validatesDaemonToBrowserAttribution: false.It is supporting validity evidence only, its samples are not Baseline-4 timing rows, and it is
not daemon→browser attribution evidence.
How I checked (all at the certified head
1fae54a, the docs-only tranche tip)runs; 96 warmed cells with complete 75-observation windows, burn-in = observations 1–60 and
measured = 61–75, zero mismatches; 18 cold-start cells with no burn-in; no duplicate cell.
npm run perf:summarize -- --artifact <composite>recomputed every published figure; thepublished tables are byte-identical to that output.
gpt-5.6-luna, fresh): PASS, five of five questions PASS, zerofindings, zero gaps.
claude-sonnet-5, fresh): APPROVE, zero findings, nothingunverified; it independently re-ran the summarizer (byte-identical) and matched the
documented commands to
package.json1:1.Docker image, GitGuardian);
npm run checkexit 0 locally.chatgpt-codex-connectorbot produced no review on this PR — as on this repo's otherrecent PRs. Recorded as unavailable, not treated as a pass.
Evidence
Root:
/srv/jonas/evidence/dockermap/time-to-answer/bdce6ae/(outside the repository, per theevidence-artifact policy) —
time-to-answer-baseline-4.json(sha256916610bdd4767fb01a4f57e29f318aa504836875cbb4d87ba615c70cb6f7300),time-to-answer-stage5.json,time-to-answer-baseline-4-composite.json,time-to-answer-baseline-4-composite.json.supporting-evidence.json,time-to-answer-baseline-4-summary.md,baseline-4-verification.json,metadata.json,preconditioning.log,phase-control.log,stage5.log,independence.log,capture.log,raw-independence/…, andreviews/(prompts, verdicts, run records for both lanes).Remaining risk / follow-up
need a genuine publication-identity mechanism, which is not this issue's scope.
tests/perf/capture.tsretains a vestigial free-running Stage-5 collection that reaches onlythe harness-evidence sidecar and applies no validity gate. It cannot contaminate the artifact
or the composite (the general capture declares no Stage-5 timing cell), and it was not
changed here because any commit touching
tests/perf/**would move the branch head off theharness revision that produced Baseline 4. Recommended follow-up after #333 child: Establish end-to-end time-to-answer baselines and regression gates #335.
assembleCompositeEvidence.tshas nonpm runalias; the documented invocation isnpx tsx tests/perf/assembleCompositeEvidence.ts …. Recommended follow-up.seam-isolation protocol description after review.
Recommendation: closed as complete. Baseline 4 is recorded as the comparison authority for
#336 and #337.