Skip to content

Measurement + ResourceContract: values carry their provenance, limits carry evidence they refuse - #226

Merged
mdheller merged 2 commits into
mainfrom
feat/measurement-provenance
Jul 30, 2026
Merged

Measurement + ResourceContract: values carry their provenance, limits carry evidence they refuse#226
mdheller merged 2 commits into
mainfrom
feat/measurement-provenance

Conversation

@mdheller

@mdheller mdheller commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Two schemas, shipped together because the second $refs the first — a stacked PR would break the child on squash-merge.

Both come from turning today's macOS-forensics lens back on our own spec, and every defect cited below is one of mine.


1. Measurement.json — extraction, not invention

LawfulDispatchReceipt.json already carried source: [measured, declared] on law and evidence, with an allOf forcing evidenceTier: T2. Correct, and inlined in exactly one of 302 schemas — while cross-file $ref is already the house pattern (DataRef 10×, ContentRef 7×, Link 6×). Divergence between an inlined definition and its copies is how extension, Extension, DiagnosticExtension, diagnosticextension and diagnostic came to name one macOS extension point.

Three defects it makes unrepresentable

A census that dropped exactly the members it existed to count. 627 macOS sandbox containers; find -maxdepth 2 -name … 2>/dev/null returned 617. The ten lost — Safari, Safari.WebApp, mail, Notes, MobileSMS, news, stocks, Home, VoiceMemos, archiveutility — were precisely the TCC-protected ones. The instrument printed ten Operation not permitted lines; the redirect swallowed them. The drop rate correlates with sensitivity, so silent omission is worse than a wrong number. As a Measurement, that walk must declare unobserved: 10 and is ineligible by shape.

A fabricated legend. The /var/folders/<shard>/<hash>/{0,C,T} mapping was printed as derived — in the same change that criticised Apple for shipping that tree without one. The three confstr lookups that would have confirmed it had all failed. That is source: "assumed".

Skimmed coverage caveats. Both resource reports carried Duration Sampled shorter than Duration plus samples lost, in headers easy to pass over. Here they are sampling and unobserved — required, not footnotes.

gateEligible is a ceiling

true only when source is measured and unobserved is 0; false always available. A producer may downgrade its own reading — it may have come to distrust an instrument — but never upgrade one. Same asymmetry as the truth-product being a MEET.

Deliberately not named coverage: GuardrailEvalReport.coverage already means declared-vs-enforced clause counts, and one word with two meanings is the failure this prevents.

INVARIANT 5 reads LawfulDispatchReceipt at validation time and asserts its enum stays a subset. Drift is a failing test, not a silent fork.

36 checks, incl. 16 exhaustive source × unobserved × claimed combinations as documents, and a mutation test proving the allOf is what rejects.


2. ResourceContract.jsonAction taken: none made unwritable

limit actual action
Virtualization.VirtualMachine 50% CPU / 180s 66% / 137s none
nsurlsessiond 2147.48 MB / 86400s 2147.50 MB none

Declared, measured, exceeded, nothing happened. A threshold that only observes is not a limit, and carrying one is worse than carrying none, because its existence is read as a guarantee.

invariant rule
1 enforcement != observenegativeControl required and resolvable
2 enforcement == observeobserveOnlyReason required
3 scope == processscopeRationale required
4 firedCount > 0lastFiredAt required
5 negativeControl resolves (validator — uri-reference is syntax only)
6 observedPeak must be a gate-eligible Measurement
7 the $ref target exists

Why scope earns its place. In the nsurlsessiond report the flagged process was 141 of 8,357 samples (2%), while node 24%, the app 17%, the VM 15% and git 9% — 66% between them, all charged to one resource coalition — tripped nothing, each staying under its own per-process threshold. Per-process limits are defeated by fan-out, and fan-out is what a build runner does by design. macOS had the aggregate accounting printed in the header and enforced per-process anyway.

INVARIANT 6 is what makes Measurement load-bearing rather than decorative: a limit calibrated by an unmeasured peak is not calibrated.

40 checks. The shipped negativeControl procedure states plainly that it has not yet been executed against a runner — an unrun procedure is honest; a fabricated observedPeak would be the paper control this schema exists to refuse.

Deliberately not checked

Whether observedPeak.value exceeds limit.value. Measurement has no unit on valuesampling.unit describes what was counted ('hourly samples'), not what the value measures ('bytes'). Comparing numbers whose units cannot be verified is the error class closed elsewhere today, so the check is omitted rather than bodged. Real fix is a unit field on Measurement — recorded as follow-up, not smuggled in.


Three errors caught during construction, all by artifact rather than exit code

the exhaustive sweep failed on first run the validator asserted equality on gateEligible instead of a bound. The schema was right.
make validate printed OK: validate without running the new validator the Makefile edit landed on the .PHONY line, not the validate: dependency line
fixed, and both wirings now carry their own negative control: break the example ⇒ rc=2; restore ⇒ rc=0

Branched off main, independent of #225.

…derived

Extraction, not invention. LawfulDispatchReceipt.json already carried
`source: [measured, declared]` on both `law` and `evidence`, with an allOf
forcing evidenceTier T2 when declared. That invariant was right and inlined in
exactly one of 302 schemas, so nothing else could reuse it — and cross-file
$ref is already the house pattern (DataRef 10x, ContentRef 7x, Link 6x).
Divergence between an inlined definition and its copies is how `extension`,
`Extension`, `DiagnosticExtension`, `diagnosticextension` and `diagnostic` came
to name one macOS extension point.

Three defects observed 2026-07-30 that this makes unrepresentable:

1. A census of 627 macOS sandbox containers, run as
   `find -maxdepth 2 -name … 2>/dev/null`, returned 617. The ten lost were
   Safari, Mail, Notes, Messages, News, Stocks, Home, VoiceMemos,
   Safari.WebApp, archiveutility — exactly the TCC-protected ones, i.e. the
   population the audit existed to measure. The instrument printed ten
   `Operation not permitted` lines; the redirect discarded them. As a
   Measurement that walk must declare `unobserved: 10`, and is then ineligible.

2. A legend for /var/folders/<shard>/<hash>/{0,C,T} was printed as derived, in
   the same change that criticised Apple for shipping that tree without one.
   The three confstr lookups that would have confirmed it had all failed. That
   is `source: assumed`, and unusable.

3. Both macOS resource reports carried `Duration Sampled` shorter than
   `Duration` plus a `samples lost` count, in headers easy to skim. Those are
   `sampling` and `unobserved` — required here, not incidental.

`gateEligible` is a CEILING, not an equality: available as true only when
source is measured AND unobserved is 0; false is always available. A producer
may downgrade its own reading — it may distrust an instrument for reasons the
schema cannot see — but can never upgrade one. Same asymmetry as the
truth-product being a MEET.

Deliberately NOT named `coverage`: GuardrailEvalReport.coverage already means
declared-vs-enforced clause counts, and one word with two meanings is the
failure this schema exists to prevent. Sample coverage is `sampling`.

INVARIANT 5 is the reason extraction is worth doing: LawfulDispatchReceipt's
enum must stay a SUBSET of this one, asserted by reading that file. Drift is a
failing test rather than a silent fork. If LDR is later refactored to $ref
Measurement, the check says so instead of passing quietly.

36 checks, including 16 exhaustive source x unobserved x claimed combinations
fed to the schema as documents, and a mutation test proving the allOf is what
rejects rather than some unrelated constraint.

Two errors this caught in its own construction, both by artifact rather than
exit code:
- the exhaustive sweep failed on first run because this validator asserted
  equality on gateEligible instead of a bound. The schema was right.
- the Makefile edit landed on the .PHONY line instead of the `validate:`
  dependency line, so `make validate` printed OK while never running the new
  validator. Fixed, and the wiring now has its own negative control: forging
  the example makes `make validate` exit 2, restoring it exits 0.
Copilot AI review requested due to automatic review settings July 30, 2026 08:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Introduces a new normative Measurement schema (plus example) to encode how a value was obtained and to make gateEligible derived by contract, with a dedicated validator wired into make validate to enforce invariants and prevent drift with LawfulDispatchReceipt.

Changes:

  • Adds schemas/Measurement.json defining source, instrument, sampling, unobserved, and the derived/ceiling semantics of gateEligible via JSON Schema invariants.
  • Adds tools/validate_measurement.py and a shipped example document, with exhaustive/mutation-style checks and an anti-drift guard against LawfulDispatchReceipt.
  • Wires the new validator into Makefile’s validate target.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
tools/validate_measurement.py New validator enforcing Measurement invariants (including exhaustive combinations and mutation testing).
schemas/Measurement.json New canonical Measurement schema defining eligibility semantics and required measurement metadata.
Makefile Adds validate-measurement target and hooks it into make validate.
examples/measurement.example.json Adds a concrete example Measurement document demonstrating unobserved/ineligibility shape.

Comment thread schemas/Measurement.json
Comment on lines +75 to +84
"anyOf": [
{
"required": ["source"],
"properties": { "source": { "not": { "const": "measured" } } }
},
{
"required": ["unobserved"],
"properties": { "unobserved": { "minimum": 1 } }
}
]
Comment thread Makefile Outdated
@@ -1,6 +1,6 @@
.PHONY: validate validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-lawful-dispatch-receipt validate-architectural-building-block
.PHONY: validate validate-measurement validate-value-type validate-source-locator validate-sourceos-repo-manifest validate-mesh-action-registry validate-control-plane-examples validate-nlboot-examples validate-lattice-data-governai-examples validate-ops-history-examples validate-runtime-observability-examples validate-interpretability-examples validate-lifecycle-boundary-examples validate-svf-contracts validate-sync-cycle-receipts validate-onboarding-examples validate-runtime-causality-examples validate-agentic-os-examples validate-triparty-examples validate-labor-market-examples validate-supply-chain-risk-examples validate-reasoning-examples validate-mpcc-event-examples validate-knowledge-nugget-examples validate-semantic-action-examples validate-epistemic-kernel-examples validate-ab-update-examples validate-device-service-examples validate-duplicate-schema-ids validate-lawful-dispatch-receipt validate-architectural-building-block validate-measurement
Comment thread Makefile
Comment on lines 20 to 26
# Reusable role-named technical components. Zurich E-RDA2 uses the same abstraction —
# an ABB names a FUNCTION (DATABASE, IMPLEMENTATION CONTROLLER) that any vendor can fill.
validate-measurement:
python3 -m pip install --user jsonschema >/dev/null
python3 tools/validate_measurement.py

validate-architectural-building-block:
One string this makes unwritable: `Action taken: none`.

Both macOS 26.3.1 reports, 2026-07-30:
  Virtualization.VirtualMachine  limit 50% CPU / 180s, actual 66% / 137s
  nsurlsessiond                  limit 2147.48 MB / 86400s, actual 2147.50 MB

Declared, measured, exceeded, and neither did anything. A threshold that only
observes is not a limit, and carrying one is worse than carrying none, because
its existence is read as a guarantee.

INVARIANT 1  enforcement != observe requires `negativeControl`, and the
             validator resolves it. "It has never needed to fire" is the shape
             of every control that turns out not to work.
INVARIANT 2  enforcement == observe requires `observeOnlyReason`, so an
             unenforced limit reads as a position rather than an oversight.
INVARIANT 3  scope == process requires `scopeRationale`. In the nsurlsessiond
             report the flagged process was 141 of 8,357 samples (2%) while
             node 24%, app 17%, VM 15% and git 9% — 66% between them, all under
             one resource coalition — tripped nothing, each staying under its
             own per-process threshold. Per-process limits are defeated by
             fan-out, and fan-out is what a build runner does by design. macOS
             had the aggregate accounting in the header and enforced per
             process anyway.
INVARIANT 4  firedCount > 0 requires lastFiredAt.
INVARIANT 5  negativeControl must resolve (validator). `format: uri-reference`
             is syntax and resolves nothing — the same defect this repo shipped
             on ArchitecturalBuildingBlock.conformanceTest.
INVARIANT 6  observedPeak must be a gate-eligible Measurement. A peak that was
             declared, or came from a walk that refused members, cannot
             calibrate a limit. This is the $ref that makes Measurement
             load-bearing rather than decorative.
INVARIANT 7  the $ref target must exist.

`resource` is a closed enum for the reason ABB.role is: freeform names
accumulate spellings. `disk-writes` stays separate from io-bandwidth and
io-operations because the nsurlsessiond limit bounded dirtied file-backed
memory, which is neither, and collapsing them would make it unstateable.

NOT CHECKED, deliberately: whether observedPeak.value exceeds limit.value.
Measurement has no unit on `value` — `sampling.unit` describes what was counted
('hourly samples'), not what the value measures ('bytes'). Comparing numbers
whose units cannot be verified is the error class closed elsewhere today, so
the check is omitted rather than bodged. Real fix is a `unit` on Measurement;
recorded as follow-up, not smuggled in here.

40 checks: 16 exhaustive enforcement x witness combinations as documents, a
mutation test proving the allOf rejects rather than an unrelated constraint,
and negative controls on the pointer resolution and the nested $ref.

The shipped negativeControl procedure states plainly that it has not yet been
executed against a runner. An unrun procedure is honest; a fabricated
observedPeak would be the paper control this schema exists to refuse.

Makefile wiring verified by artifact (`make validate` emits both new banners)
and by negative control (strip the negativeControl, rc=2; restore, rc=0) —
after the previous change silently landed on the .PHONY line instead.
@mdheller mdheller changed the title Measurement: a value carries how it was obtained, and eligibility is derived Measurement + ResourceContract: values carry their provenance, limits carry evidence they refuse Jul 30, 2026
@mdheller
mdheller merged commit e3c5357 into main Jul 30, 2026
7 checks passed
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.

2 participants