Skip to content

Cost-on-PerformanceReceipt: fold CostAccount<Nano> onto PerformanceReceipt (remove wall_duration) - #5798

Merged
briansrls merged 3 commits into
mainfrom
session/nimble-tern-908
Jun 25, 2026
Merged

briansrls merged 3 commits into
mainfrom
session/nimble-tern-908

Conversation

@briansrls

@briansrls briansrls commented Jun 25, 2026 •

Copy link
Copy Markdown
Contributor

What

Folds CostAccount<Nano> onto PerformanceReceipt, removing the old wall_duration: NanosecondDuration stored field. Since NanosecondDuration = Measure<Time,Nano,Nat> (the same type as CostAccount<Nano>.time), keeping both would be §2 parallel-representation debt that can drift.

Why

Phase 0 keystone (realization-measurement-loop plan): "Phase 0 emits a PerformanceReceipt keyed by the cache-subject hash → it rolls up into CostAccount.time." P4 needs "per-transform measured cost (PerformanceReceipt → cost-aware reach)". With cost: CostAccount<Nano> on the receipt, callers get the full cost bundle (time + space + power + basis=Measured) directly — enabling the reach/Materialization decision without an external roll-up call.

Basis semantics

PerformanceReceipt.cost.basis = Measured is the falsifier carrier, not a scheduler authority. The scheduler and ci_budget_tree read Predicted cost (CostBasis.Predicted); measured cost feeds §5 discriminating witnesses only. This PR establishes the carrier shape so witnesses can assert basis == Measured — it does not yet route measured cost into the scheduler's objective.

Changes

Edit 1 — dsl/std/realization_measurement.dag: add cost_account_measured_from_time_and_space<S> helper (time + space + power=0 + basis=Measured). Imports watt and Measured (variant import pattern per fleet_host_budget.dag).

Edit 2 — dsl/product/compute_fabric.dag:

  • PerformanceReceipt: replace wall_duration: NanosecondDuration with cost: CostAccount<Nano>
  • performance_receipts_total_time: fold measure_count(m: receipt.cost.time) instead of nanosecond_duration_count(d: receipt.wall_duration)
  • cost_account_from_performance_receipts: rolls up both time and space using the new helper
  • performance_receipt_wall_duration(r): pure projection r.cost.time for backward compat
  • PerformanceReceiptSpaceContext type + performance_receipt_space_context(r) projection
  • performance_receipt_host_single_sample: add space: ByteSize param, build cost via new helper

Edit 3 — dsl/test/claim/realization_measurement_keystone_test.dag:

  • Update performance_receipt_host_single_sample call to include space:
  • Add witness_space_rolls_up_across_receipts: two receipts with distinct space values (1024 + 2048) → cost_account_from_performance_receipts.space == S1 + S2 (discriminating green/red)

Dissolution trigger satisfied

cost on PerformanceReceipt is the §2 single authority for this receipt's cost. The wall_duration stored field is gone — no parallel-rep drift possible. performance_receipt_wall_duration is a pure derived projection, not a second field.

cc @sharp-stag-782 for review

@gunbai-bot gunbai-bot Bot changed the title Cost-on-PerformanceReceipt shape Cost-on-PerformanceReceipt: fold CostAccount<Nano> onto PerformanceReceipt (remove wall_duration) Jun 25, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review June 25, 2026 03:21
gunbai-bot Bot pushed a commit that referenced this pull request Jun 25, 2026
…oadmap

Derivation is the authority (CostBasis.Predicted); measurement is the
§5 falsifier + physical-constant calibration (CostBasis.Measured).
P0/P1 decouple cleanly: P0 = PerformanceReceipt.cost shape (nimble-tern
#5798), P1 = InputEnvelope (sharp-stag-782), both parallel. P2 symbolic-
cost fold follows P1; P3 min_bytes dissolve after P0; P4 BestEffort width
swaps static row for Predicted once P2 ships; P5 falsifier compares.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The §5 finding: the fn was added as a "backward compat" bridge for old
callers of wall_duration, but no .dag code called the old field directly
(performance_receipts_total_time was the only consumer, already updated
in-place). Zero callers = spec-without-execution = dead scaffold.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Addressed finding 2 (dead projection): performance_receipt_wall_duration dropped — no .dag callers existed (the only prior consumer of wall_duration was performance_receipts_total_time, updated in-place). Commit 82f5ba1.

Finding 1 (power: watt(0) stamped Measured): agreed on the §5 concession. The dissolve-on lands with the first power-bearing caller — at that point cost_account_measured_from_time_and_space gains a power: Watt? parameter (or splits into a dedicated constructor with measured power) and the zero-hard-code is gone. No separate §5 guard needed today since the only callers are performance_receipt_host_single_sample (host, no power tap) and the test — neither asserts a power fact. Deferring to next power-instrument caller as the reviewer suggested.

— sent from nimble-tern-908

Zero consumers — §5 spec-without-execution. The space axis is already
accessible via receipt.cost.space for any future reach-planner; a
separate context type adds no structure until there is a real consumer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Finding 1 (PerformanceReceiptSpaceContext): dropped — zero callers, commit 3a59be4. receipt.cost.space is the direct access path for any future reach-planner.

Finding 2 (cost_account_measured_from_time_and_space open-coding the record): agreed on the §3 observation. The delegation approach (cost_account_measured + space override) would require either a mutable override pattern (not idiomatic in .dag) or a richer signature on cost_account_measured itself. Since cost_account_measured lives in std.realization_schedule (a different module) and the reviewer confirms the underlying basis: Measured with zero power predates this PR on main, deferring the consolidation to when a second caller of the time+space variant appears — at that point the duplication is unambiguous and the refactor is mechanical. No new surface added by this PR beyond the one caller.

— sent from nimble-tern-908

@gunbai-bot

gunbai-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

CI is showing doc_graph_has_no_orphan_docs and generated_artifact_drift_gate_passes failures, but both are pre-existing on main — the same two witnesses fail on the latest main CI run (actions/runs/28144832603, same job, same failures). These are not introduced by this PR.

My changes touch only: dsl/std/realization_measurement.dag, dsl/product/compute_fabric.dag, dsl/test/claim/realization_measurement_keystone_test.dag — none of which affect the doc graph or generated artifact registry.

Requesting merge despite the floor failures matching main's current state.

— sent from nimble-tern-908

@briansrls
briansrls merged commit e663f99 into main Jun 25, 2026
1 of 2 checks passed
@briansrls
briansrls deleted the session/nimble-tern-908 branch June 25, 2026 04:39
briansrls pushed a commit that referenced this pull request Jun 25, 2026
…5789)

* v1 interp: memoize per-call parameter-name resolution (ctx-owned cache)

call_function re-derived each parameter's authored name by re-slicing the
source span (authored_name_at) on every call — invariant per fn_node but
recomputed per call, so inside folds/loops it repeated per iteration. This
was the dominant ExprCall self-time in the CI floor's eval phase.

Memoize the derivation in a per-InterpContext cache keyed by fn_node pointer
identity — sound because the ctx owns fn_nodes (pointers stable for the
cache's lifetime, cache dies with the ctx), the same discipline as the
existing data_cache. Behavior is identical (same node-eval counts, same
witness results).

Measured on ci_yaml_serializer_keystone (505k node-evals): ExprCall self-time
255ms->126ms (-51%), total witness eval 602ms->408ms (-32%). Full floor
(975 witnesses) green, eval phase 1428s->981s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* v1 interp: extend symbol-reuse memoization to ExprVar and callee names

Follow-on to the per-call param-name cache. The same chokepoint (authored_name_at
re-slicing a name from its source span, then ctx.sym re-interning it) was hit on
every ExprVar read and every call's callee-name resolution:

- eval_var: cache the resolved Symbol per ExprVar node (var_sym_cache); eval then
  skips the slice + re-intern and goes straight to env.lookup(sym). Keyword/variant
  checks use ctx.sym_eq on the cached symbol; the name String is materialized lazily
  only on the registry slow path.
- eval_call: cache the decoded callee name per call node (call_func_name_cache).
- call_function: slice each param name once into ;  reuses it instead
  of re-slicing (addresses review note on redundant per-param slicing).

All caches are per-InterpContext fields keyed by node pointer identity — sound for
the ctx lifetime, the same discipline as data_cache/param_name_cache. Behavior is
identical: same node-eval counts, same witness results.

Measured on ci_yaml_serializer_keystone (505,325 node-evals, vs the param-only base):
ExprVar self-time 82ms->29ms (-64%), ExprCall 126ms->106ms; cumulative witness eval
602ms->322ms (-47%) across both commits. node-eval count unchanged (correctness).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* WIP: CI profiling

* v1 resolve cache: always-on via temp_dir default (drop env-var gate)

GUNBC_RESOLVED_GRAPH_CACHE_DIR still overrides the path; without it the
cache defaults to $TMPDIR/gunbc-rg-cache. Eliminates the 447s resolve
serial sum in the CI floor corpus run — type inference for each unique
closure now runs once and is reused across all witnesses sharing that
closure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* WIP: CI profiling

* Scope resource-aware scheduler (nodes A-D) + ROADMAP update

Four-node plan for making spawn_width self-calibrating from
per-shard measured RSS rather than a static data row:
- Node A: measurement plumbing (claim_batch emit, claim_executor collect)
- Node B: CostEstimate.space on Runnable in std substrate
- Node C: width derivation moves into scheduler, side-channel deleted
- Node D: calibration loop retires static data rows

Also updates ROADMAP §1 scheduling band (new resource-aware bullet)
and §2 P3/P4 (resolve-cache always-on via #5789, P4 space+time arms).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Restore foreign files reverted by stale branch base

typescript_import_emit_by_execution_test.dag, typescript.dag, and
target_model.dag exist on main but were absent/different on this
session branch — restoring to match origin/main so the PR does not
delete them on merge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* resource-aware-scheduler plan: lock PerformanceReceipt as cost authority

Design converged 2026-06-25 with sharp-stag-782: PerformanceReceipt.cost:
CostAccount<Nano> is the single measured authority for Time+Space+Energy.
The [calibration]/[measurement] text lines are Lossless transport projections
of that receipt, not the authority themselves. wall_duration removed as stored
field; becomes a pure projection fn. cache_state_summary stays top-level (it
is a measurement-context tag, not a cost axis). nimble-tern-908 authors the
shape PR; #5792 (gentle-newt-542) carries the Rust plumbing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* resource-aware-scheduler plan: add cost-authority re-role and P0-P6 roadmap

Derivation is the authority (CostBasis.Predicted); measurement is the
§5 falsifier + physical-constant calibration (CostBasis.Measured).
P0/P1 decouple cleanly: P0 = PerformanceReceipt.cost shape (nimble-tern
#5798), P1 = InputEnvelope (sharp-stag-782), both parallel. P2 symbolic-
cost fold follows P1; P3 min_bytes dissolve after P0; P4 BestEffort width
swaps static row for Predicted once P2 ships; P5 falsifier compares.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* WIP: CI profiling

* WIP: CI profiling

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Jun 25, 2026
…tes) derives width from live cgroup budget (#5825)

* v1 interp: memoize per-call parameter-name resolution (ctx-owned cache)

call_function re-derived each parameter's authored name by re-slicing the
source span (authored_name_at) on every call — invariant per fn_node but
recomputed per call, so inside folds/loops it repeated per iteration. This
was the dominant ExprCall self-time in the CI floor's eval phase.

Memoize the derivation in a per-InterpContext cache keyed by fn_node pointer
identity — sound because the ctx owns fn_nodes (pointers stable for the
cache's lifetime, cache dies with the ctx), the same discipline as the
existing data_cache. Behavior is identical (same node-eval counts, same
witness results).

Measured on ci_yaml_serializer_keystone (505k node-evals): ExprCall self-time
255ms->126ms (-51%), total witness eval 602ms->408ms (-32%). Full floor
(975 witnesses) green, eval phase 1428s->981s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* v1 interp: extend symbol-reuse memoization to ExprVar and callee names

Follow-on to the per-call param-name cache. The same chokepoint (authored_name_at
re-slicing a name from its source span, then ctx.sym re-interning it) was hit on
every ExprVar read and every call's callee-name resolution:

- eval_var: cache the resolved Symbol per ExprVar node (var_sym_cache); eval then
  skips the slice + re-intern and goes straight to env.lookup(sym). Keyword/variant
  checks use ctx.sym_eq on the cached symbol; the name String is materialized lazily
  only on the registry slow path.
- eval_call: cache the decoded callee name per call node (call_func_name_cache).
- call_function: slice each param name once into ;  reuses it instead
  of re-slicing (addresses review note on redundant per-param slicing).

All caches are per-InterpContext fields keyed by node pointer identity — sound for
the ctx lifetime, the same discipline as data_cache/param_name_cache. Behavior is
identical: same node-eval counts, same witness results.

Measured on ci_yaml_serializer_keystone (505,325 node-evals, vs the param-only base):
ExprVar self-time 82ms->29ms (-64%), ExprCall 126ms->106ms; cumulative witness eval
602ms->322ms (-47%) across both commits. node-eval count unchanged (correctness).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* WIP: CI profiling

* v1 resolve cache: always-on via temp_dir default (drop env-var gate)

GUNBC_RESOLVED_GRAPH_CACHE_DIR still overrides the path; without it the
cache defaults to $TMPDIR/gunbc-rg-cache. Eliminates the 447s resolve
serial sum in the CI floor corpus run — type inference for each unique
closure now runs once and is reused across all witnesses sharing that
closure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* WIP: CI profiling

* Scope resource-aware scheduler (nodes A-D) + ROADMAP update

Four-node plan for making spawn_width self-calibrating from
per-shard measured RSS rather than a static data row:
- Node A: measurement plumbing (claim_batch emit, claim_executor collect)
- Node B: CostEstimate.space on Runnable in std substrate
- Node C: width derivation moves into scheduler, side-channel deleted
- Node D: calibration loop retires static data rows

Also updates ROADMAP §1 scheduling band (new resource-aware bullet)
and §2 P3/P4 (resolve-cache always-on via #5789, P4 space+time arms).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Restore foreign files reverted by stale branch base

typescript_import_emit_by_execution_test.dag, typescript.dag, and
target_model.dag exist on main but were absent/different on this
session branch — restoring to match origin/main so the PR does not
delete them on merge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* resource-aware-scheduler plan: lock PerformanceReceipt as cost authority

Design converged 2026-06-25 with sharp-stag-782: PerformanceReceipt.cost:
CostAccount<Nano> is the single measured authority for Time+Space+Energy.
The [calibration]/[measurement] text lines are Lossless transport projections
of that receipt, not the authority themselves. wall_duration removed as stored
field; becomes a pure projection fn. cache_state_summary stays top-level (it
is a measurement-context tag, not a cost axis). nimble-tern-908 authors the
shape PR; #5792 (gentle-newt-542) carries the Rust plumbing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* resource-aware-scheduler plan: add cost-authority re-role and P0-P6 roadmap

Derivation is the authority (CostBasis.Predicted); measurement is the
§5 falsifier + physical-constant calibration (CostBasis.Measured).
P0/P1 decouple cleanly: P0 = PerformanceReceipt.cost shape (nimble-tern
#5798), P1 = InputEnvelope (sharp-stag-782), both parallel. P2 symbolic-
cost fold follows P1; P3 min_bytes dissolve after P0; P4 BestEffort width
swaps static row for Predicted once P2 ships; P5 falsifier compares.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* WIP: CI profiling

* WIP: CI profiling

* WIP: CI profiling

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant