Skip to content
Merged
2 changes: 1 addition & 1 deletion dsl/gunbc/test_node_wall_clock_ratchet.dag
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// the retired hand-only manifest). Single edit surface: add/remove rows here.
//
// Phase-0 convergence: `TestNodeCostDimension.budget_time` / `measured_time` use
// `NanosecondDuration` (std.measure); this table names libtest tokens until every
// `NanosecondDuration` (std.verification, its timing-authority home); this table names libtest tokens until every
// slow test is either sped up or modeled with explicit budget/measured facts.

module gunbc.test_node_wall_clock_ratchet
Expand Down
16 changes: 12 additions & 4 deletions dsl/product/compute_fabric.dag
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ import std.measure {
Currency,
Micro,
One,
Nano,
ByteSize,
MoneyAmount,
NanosecondDuration,
nanosecond_duration,
nanosecond_duration_count,
HardwareThreadCount,
byte_size,
byte_size_count,
Expand All @@ -44,6 +42,8 @@ import std.measure {
hertz,
}
import std.currency { CurrencyCode }
// Nanosecond timing CHOICE from its single authority (std.verification), not std.measure.
import std.verification { NanosecondDuration, nanosecond_duration, nanosecond_duration_count }
import std.realization_schedule { CostAccount }
import std.realization_measurement { cost_account_measured_from_time }
import std.content_hash {
Expand Down Expand Up @@ -658,6 +658,12 @@ type MeasurementConfidence
type PerformanceReceipt {
provider: ProviderIdentity
work_shape: WorkUnitId
// NANOSECOND scale CHOICE (relocated here per DESIGN §3): the realization /
// measurement framework (std.realization_measurement / std.realization_schedule) is
// scale-AGNOSTIC; this consumer — the host wall-clock observation boundary — OWNS the
// decision to denominate measured cost in nanoseconds, because that is the resolution
// the host clock observes elapsed wall time at. The framework rolls up whatever scale
// arrives; nanosecond is named here, once, where it is measured.
wall_duration: NanosecondDuration
cpu_seconds: Float?
sample_count: Int
Expand All @@ -678,7 +684,9 @@ fn performance_receipts_total_time(receipts: List<PerformanceReceipt>) -> Nanose

// Pure roll-up lens (std.realization_measurement): PerformanceReceipt observations
// → CostAccount.time with Measured basis. Handler emits receipts; this folds them.
fn cost_account_from_performance_receipts(receipts: List<PerformanceReceipt>) -> CostAccount {
// The scale-agnostic roll-up is instantiated at `CostAccount<Nano>` HERE (this consumer's
// owned ns choice), inferred from the NanosecondDuration wall_duration.
fn cost_account_from_performance_receipts(receipts: List<PerformanceReceipt>) -> CostAccount<Nano> {
cost_account_measured_from_time(time: performance_receipts_total_time(receipts: receipts))
}

Expand Down
13 changes: 8 additions & 5 deletions dsl/std/cache_interface.dag
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
module std.cache_interface

import std.types { NonEmptyStr, ContentHash, Bool, Int, LogicalTime }
import std.measure { ByteSize, NanosecondDuration, nanosecond_duration, nanosecond_duration_count }
import std.measure { Nano, ByteSize }
// NanosecondDuration scale CHOICE imported from its timing-authority home (std.verification,
// DESIGN §3), not re-coined here: cache read-latency costs are the consumer's nanosecond facts.
import std.verification { NanosecondDuration, nanosecond_duration, nanosecond_duration_count }
import std.pareto {
AxisComparison,
DominanceVerdict,
Expand Down Expand Up @@ -295,7 +298,7 @@ fn cache_reach_candidate_recompute() -> CacheReachCandidate {
fn cache_reach_candidate_probe(
id: CacheInterfaceId,
catalog: List<CacheInterfaceFacts>,
recompute: CostAccount,
recompute: CostAccount<Nano>,
) -> CacheReachCandidate {
let facts = cache_facts_for_id(id: id, catalog: catalog)
let fetch = read_latency_cost(facts: facts)
Expand Down Expand Up @@ -336,15 +339,15 @@ fn cache_reach_dominates(a: CacheReachCandidate, b: CacheReachCandidate) -> Domi
fn cache_layer_cost_justified(
id: CacheInterfaceId,
catalog: List<CacheInterfaceFacts>,
recompute: CostAccount,
recompute: CostAccount<Nano>,
) -> Bool {
nanosecond_duration_count(d: recompute.time) >= nanosecond_duration_count(d: read_latency_cost(facts: cache_facts_for_id(id: id, catalog: catalog)))
}

fn cache_layer_beats_recompute_path(
id: CacheInterfaceId,
catalog: List<CacheInterfaceFacts>,
recompute: CostAccount,
recompute: CostAccount<Nano>,
) -> Bool {
let probe = cache_reach_candidate_probe(id: id, catalog: catalog, recompute: recompute)
match cache_reach_dominates(
Expand Down Expand Up @@ -377,7 +380,7 @@ fn cache_layer_plan_contains_id(plan: CacheLayerPlan, id: CacheInterfaceId) -> B

fn plan_cache_layers(
deployment: CacheDeploymentBackends,
recompute: CostAccount,
recompute: CostAccount<Nano>,
catalog: List<CacheInterfaceFacts>,
default_order: List<CacheInterfaceId>,
) -> CacheLayerPlan {
Expand Down
64 changes: 26 additions & 38 deletions dsl/std/measure.dag
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,8 @@

module std.measure

import std.nat { Nat, nat_compare, nat_max, nat_min }
import std.nat { Nat }
import std.types { Int }
import std.algebra { Ordering }

// --- Quantity: what is being measured -------------------------------------
//
Expand Down Expand Up @@ -237,6 +236,23 @@ fn measure_count<Q, S, M>(m: Measure<Q, S, M>) -> M {
m.count
}

// Generic time-measure constructor: a `Time` magnitude at ANY scale `<S>`, magnitude
// fixed at the concrete `Nat` node. This is the scale-AGNOSTIC surface the realization /
// measurement framework (std.realization_measurement) builds its roll-up algebra on —
// it names no particular scale, so the framework carries no scale CHOICE (DESIGN §3: the
// agnostic shape stays central, the scale CHOICE sits peripheral at the consumer). A
// consumer pins `<S>` (e.g. `Nano` for measured wall-clock) at its own boundary.
//
// `<M>` is fixed at `Nat` here, NOT generic: a magnitude-generic roll-up would need `+`/
// `max` over `<M>`, and algebra-inhabitance resolves only on concrete numeric nodes —
// the same where-clause substrate gap `GroupCompletion<M>` and the `Measure`
// constrained-inhabitance note above already carry. **Dissolution trigger:** when
// parametric where-clause syntax lands, widen to `<M> where M : CommutativeMonoid` and
// the roll-up algebra generalizes with it.
fn time_measure<S>(count: Nat) -> Measure<Time, S, Nat> {
Measure { count: count }
}

// Memory magnitude at unit scale (Q-Unit composition; P2 single authority).
type ByteSize = Measure<Memory, One, Nat>

Expand All @@ -255,42 +271,14 @@ type Hertz = Measure<Frequency, One, Nat>
// Schedulable hardware thread count (std.cpu catalog/deployment authority).
type HardwareThreadCount = Measure<Count, One, Nat>

// Time magnitude at nanosecond scale — realization `CostAccount.time` consumer
// (Q-Unit-5 minted-with-consumer; aligns with comment reserve for Duration/Milliseconds).
type NanosecondDuration = Measure<Time, Nano, Nat>

fn nanosecond_duration(count: Nat) -> NanosecondDuration {
NanosecondDuration { count: count }
}

fn nanosecond_duration_count(d: NanosecondDuration) -> Nat {
d.count
}

fn nanosecond_duration_compare(a: NanosecondDuration, b: NanosecondDuration) -> Ordering {
nat_compare(
a: nanosecond_duration_count(d: a),
b: nanosecond_duration_count(d: b)
)
}

fn nanosecond_duration_max(a: NanosecondDuration, b: NanosecondDuration) -> NanosecondDuration {
nanosecond_duration(
count: nat_max(
a: nanosecond_duration_count(d: a),
b: nanosecond_duration_count(d: b)
)
)
}

fn nanosecond_duration_min(a: NanosecondDuration, b: NanosecondDuration) -> NanosecondDuration {
nanosecond_duration(
count: nat_min(
a: nanosecond_duration_count(d: a),
b: nanosecond_duration_count(d: b)
)
)
}
// NOTE: the scale-FIXED nanosecond time alias `NanosecondDuration = Measure<Time, Nano,
// Nat>` is deliberately NOT defined here. A scale-fixed alias is a CHOICE, not generic
// dimensional vocabulary — it is parallel to `Milliwatt` (`Measure<Power, Milli, Nat>`,
// homed at its product consumer in #5235), not to the base SI units `Watt` / `Hertz`
// that stay below. std.measure keeps only the generic `Measure<Time, S, M>` carrier +
// `time_measure<S>`; the nanosecond CHOICE + its reason live at the timing-authority
// CONSUMER (std.verification), per DESIGN §3 (the agnostic shape stays central; the
// scale that selects a realization sits peripheral).

// Power magnitude at unit scale — the plain watt (SI derived: W = J/s). The dimension
// TDP specs, PSU ratings, and live draw are all quoted in. A power rating carries the
Expand Down
16 changes: 9 additions & 7 deletions dsl/std/realization.dag
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import std.realization_schedule {
CostAccount,
cost_account_measured,
cost_account_predicted_zero,
cost_account_time_nanos,
cost_account_time_count,
schedule_lens_verdict_for_ci_floor,
schedule_generates_same_batch_count,
schedule_generates_identical_schedule,
Expand All @@ -51,7 +51,7 @@ import std.realization_measurement {
cost_account_measured_from_schedule_critical_path,
schedule_critical_path_time,
schedule_batch_critical_path,
nanosecond_duration_list_total,
time_measure_list_total,
}

// --- Independence classifier (fail-closed) ------------------------------------
Expand Down Expand Up @@ -103,16 +103,18 @@ type Placement = LocalInProcess | LocalFilesystem | RemoteNetwork

type Materialization = Recompute | Memoize | Share

type RealizedStep {
// `<S>` is the consumer-pinned time scale of the carried cost ledger (CostAccount<S>);
// reconcile never inspects the scale — it only threads it (DESIGN §3: framework agnostic).
type RealizedStep<S> {
shape: EffectShape
placement: Placement
materialization: Materialization
cost: CostAccount
cost: CostAccount<S>
}

// Mark every accumulator step whose effect is the same-KeySource create-if-absent
// twin of `s` as Share (s collapses INTO the shared materialization; not appended).
fn mark_collapsible_share(acc: List<RealizedStep>, s: RealizedStep) -> List<RealizedStep> {
fn mark_collapsible_share<S>(acc: List<RealizedStep<S>>, s: RealizedStep<S>) -> List<RealizedStep<S>> {
acc |> map(e =>
if create_double_init_collapsible(a: e.shape, b: s.shape) {
RealizedStep {
Expand All @@ -128,7 +130,7 @@ fn mark_collapsible_share(acc: List<RealizedStep>, s: RealizedStep) -> List<Real
}

// Does `s` have a same-KeySource create-if-absent twin already in `acc`?
fn has_collapsible_peer(acc: List<RealizedStep>, s: RealizedStep) -> Bool {
fn has_collapsible_peer<S>(acc: List<RealizedStep<S>>, s: RealizedStep<S>) -> Bool {
let peers = acc |> filter(e => create_double_init_collapsible(a: e.shape, b: s.shape))
!((peers |> length) == 0)
}
Expand All @@ -138,7 +140,7 @@ fn has_collapsible_peer(acc: List<RealizedStep>, s: RealizedStep) -> Bool {
// materialization is Share; distinct identities (or non-create effects) stay separate.
// The collapse decision is create_double_init_collapsible (std.effects, KeySource-keyed) —
// reconcile only consumes it, never re-derives KeySource equality (§3 single authority).
fn reconcile(steps: List<RealizedStep>) -> List<RealizedStep> {
fn reconcile<S>(steps: List<RealizedStep<S>>) -> List<RealizedStep<S>> {
steps |> fold(init: [], f: (acc, s) =>
if has_collapsible_peer(acc: acc, s: s) {
mark_collapsible_share(acc: acc, s: s)
Expand Down
Loading