Description
Today runner.Options.MinMetricInterval is a single process-wide 60s value, defaulted in Options.defaults() (internal/runner/runner.go:97) and never set from internal/config. Runner.clampInterval (:499) raises any construct or workload interval below it and logs the clamp. That has to become per-blueprint and opt-in.
Shape to build:
- A blueprint-level field declaring the intent — the working name is
high_dpm, carrying at minimum the desired metric interval. The blueprint must state it explicitly; there is no inference from a construct declaring a fast interval, because a silent opt-in is how a user bills themselves by accident. internal/blueprintload-time validation rejects a declared interval below the ceiling rather than silently clamping it. A blueprint asking for something the process will not do should fail loudly at load, not run at a cadence it did not ask for.- A ceiling, configurable via
internal/configfrom an environment variable, defaulting to 6 DPM per series — a 10-second interval. Add it to both.envand.env.example;TestEnvSurfaceAlignedenforces that. All config reads use literalget("LIT")/getInt("LIT")keys. clampIntervaltakes the blueprint’s effective floor rather than the process-wide one. Its existing log line is load-bearing — keep a clamp observable, do not make a fast blueprint quiet.
The budget-window trap, which is the reason this subtask is not a two-line change. blueprintLoop builds budgetReset := time.NewTicker(r.opts.MinMetricInterval) (internal/runner/runner.go:698) and resets bp.budget on it. res.SeriesBudget is therefore a per-DPM-floor-window budget. If a blueprint’s floor drops to 10s and the budget ticker follows it, that blueprint’s effective per-minute series allowance multiplies by six with nothing in the logs saying so. Decide the semantics deliberately and write the decision into the code comment: either pin the budget window to a fixed 60s independent of the floor, or keep it tied to the floor and rescale SeriesBudget so the per-minute allowance is unchanged. Do not leave it implicit. RunOnce (:591) also resets the budget once per cycle and must stay consistent with whichever is chosen.
Also check the scheduler can actually deliver the cadence. MasterTick defaults to 5s, so a 10s interval has two master fires per window and works; a floor below MasterTick cannot be honoured at all and must be rejected at load with a message naming TICK_DEFAULT. The dropped-tick detection in blueprintLoop and phaseOffset (:507) both assume instances spread across an interval — verify a short interval does not resynchronise them onto one master tick.
Correct the documentation that states the floor as absolute: ARCHITECTURE.md (§ the Interval contract, and I10), docs/architecture.md, docs/RUNBOOK.md, internal/core/core.go:174, internal/workload/app/app.go:12, internal/construct/host/host.go:34. State the override and its ceiling; do not delete the rationale for the floor existing.
Acceptance Criteria
- #1 A blueprint declares sub-60s metric cadence explicitly, and a blueprint that does not declare it keeps the 60s floor in the same process
- #2 A declared interval below the configured ceiling is rejected at blueprint load with a message naming the ceiling and its environment variable, not silently clamped
- #3 The ceiling is read from config, defaults to a 10-second interval, and is present in both .env and .env.example
- #4 The per-blueprint series-budget window semantics under a lowered floor are decided, implemented and stated in a code comment, and RunOnce agrees with the live loop
- #5 A declared interval below MasterTick is rejected at load with a message naming TICK_DEFAULT
- #6 A test proves two blueprints in one process run at different floors simultaneously
- #7 Every doc and code comment stating 60s as an absolute floor is corrected to describe the opt-in override
Definition of Done
- #1 make gate (build vet test race rw-proto-check spdx-check forbidden-words)
- #2 make blueprint-schema (only if a blueprint field or construct/workload config struct changed)
- #3 DRY_RUN=true go run ./cmd/synthkit -once -dump — inventory diffed against signals/
Implementation Plan
TDD an explicit per-blueprint high_dpm metric interval, validate it against the configurable default-10s ceiling and TICK_DEFAULT, keep each blueprint series budget defined per fixed 60s window in live and RunOnce paths, wire config/docs/schema, and prove two simultaneous floors.
Implementation Notes
Implementation decision: high_dpm.metric_interval is the effective cadence for every metric-bearing instance in that blueprint, not merely a lower clamp bound; otherwise existing 60s catalog intervals would never speed up. SeriesBudget is defined as a fixed 60s per-blueprint window independent of cadence. RunOnce retains the same window boundary across calls. Runtime load rejects intervals below TICK_DEFAULT first, then intervals exceeding MAX_DPM_PER_SERIES (default 6). Focused config/blueprint/bpsource tests are green; runner verification is pending Lane B reaching a stable fenced tree.
Clarification after review: the ceiling is rate-based. Validation derives DPM as 60s / high_dpm.metric_interval and rejects it when that rate exceeds MAX_DPM_PER_SERIES; the TICK_DEFAULT lower-bound check remains separate and runs first.
Implemented high_dpm.metric_interval as an explicit per-blueprint override for metric-bearing instances, MAX_DPM_PER_SERIES default 6, separate TICK_DEFAULT validation, fixed-minute SeriesBudget semantics shared by live and RunOnce paths, and preserved log-only cadences. Generated schema and focused config/blueprint/bpsource/runner tests pass; go build ./…, go vet ./…, and go test ./… pass.
Final evidence: explicit high_dpm.metric_interval applies per blueprint, MAX_DPM_PER_SERIES defaults to 6, intervals below TICK_DEFAULT or above the ceiling fail loudly, log-only cadence is preserved, series budgets reset on a fixed minute, phase spreading remains effective, schema generation passed, and the integrated make gate passed.
Final Summary
Added the explicit per-blueprint high-DPM cadence override with a configurable default-6-DPM ceiling, fixed-minute budget semantics, RunOnce parity, scheduler validation, and corrected architecture/configuration documentation.