Measurement + ResourceContract: values carry their provenance, limits carry evidence they refuse - #226
Merged
Merged
Conversation
…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.
There was a problem hiding this comment.
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.jsondefiningsource,instrument,sampling,unobserved, and the derived/ceiling semantics ofgateEligiblevia JSON Schema invariants. - Adds
tools/validate_measurement.pyand a shipped example document, with exhaustive/mutation-style checks and an anti-drift guard againstLawfulDispatchReceipt. - Wires the new validator into
Makefile’svalidatetarget.
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 on lines
+75
to
+84
| "anyOf": [ | ||
| { | ||
| "required": ["source"], | ||
| "properties": { "source": { "not": { "const": "measured" } } } | ||
| }, | ||
| { | ||
| "required": ["unobserved"], | ||
| "properties": { "unobserved": { "minimum": 1 } } | ||
| } | ||
| ] |
| @@ -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 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 inventionLawfulDispatchReceipt.jsonalready carriedsource: [measured, declared]onlawandevidence, with anallOfforcingevidenceTier: T2. Correct, and inlined in exactly one of 302 schemas — while cross-file$refis already the house pattern (DataRef10×,ContentRef7×,Link6×). Divergence between an inlined definition and its copies is howextension,Extension,DiagnosticExtension,diagnosticextensionanddiagnosticcame 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/nullreturned 617. The ten lost — Safari, Safari.WebApp, mail, Notes, MobileSMS, news, stocks, Home, VoiceMemos, archiveutility — were precisely the TCC-protected ones. The instrument printed tenOperation not permittedlines; 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 declareunobserved: 10and 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 threeconfstrlookups that would have confirmed it had all failed. That issource: "assumed".Skimmed coverage caveats. Both resource reports carried
Duration Sampledshorter thanDurationplussamples lost, in headers easy to pass over. Here they aresamplingandunobserved— required, not footnotes.gateEligibleis a ceilingtrueonly whensourceismeasuredandunobservedis 0;falsealways 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.coveragealready means declared-vs-enforced clause counts, and one word with two meanings is the failure this prevents.INVARIANT 5 reads
LawfulDispatchReceiptat 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×claimedcombinations as documents, and a mutation test proving theallOfis what rejects.2.
ResourceContract.json—Action taken: nonemade unwritableVirtualization.VirtualMachinensurlsessiondDeclared, 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.
enforcement != observe⇒negativeControlrequired and resolvableenforcement == observe⇒observeOnlyReasonrequiredscope == process⇒scopeRationalerequiredfiredCount > 0⇒lastFiredAtrequirednegativeControlresolves (validator —uri-referenceis syntax only)observedPeakmust be a gate-eligible Measurement$reftarget existsWhy
scopeearns its place. In the nsurlsessiond report the flagged process was 141 of 8,357 samples (2%), whilenode24%, the app 17%, the VM 15% andgit9% — 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
negativeControlprocedure states plainly that it has not yet been executed against a runner — an unrun procedure is honest; a fabricatedobservedPeakwould be the paper control this schema exists to refuse.Deliberately not checked
Whether
observedPeak.valueexceedslimit.value. Measurement has no unit onvalue—sampling.unitdescribes 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 aunitfield on Measurement — recorded as follow-up, not smuggled in.Three errors caught during construction, all by artifact rather than exit code
gateEligibleinstead of a bound. The schema was right.make validateprintedOK: validatewithout running the new validator.PHONYline, not thevalidate:dependency linerc=2; restore ⇒rc=0Branched off
main, independent of #225.