Skip to content

feat(spec): checkProfileRegistration — validation for the gateway measurement-profile registration route - #384

Draft
LamaSu wants to merge 19 commits into
feat/sensors-profile-admissionfrom
feat/sensors-profile-registration-check
Draft

LamaSu wants to merge 19 commits into
feat/sensors-profile-admissionfrom
feat/sensors-profile-registration-check

Conversation

@LamaSu

@LamaSu LamaSu commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Stacked on #363 (base feat/sensors-profile-admission). One commit, 9811ab7c. Merge after #363.

Sensors lane 7a438686, goal pcc-reconciliation. It answers pcc-gateway's request (bus #2621) to write the validation for its measurement-profile registration route.

What it adds

checkProfileRegistration({capabilityType, deviceId, profile, claimedDigest?}): the pure check gateway's registration route runs before inserting a row into its append-only capability_measurement_profiles table. The route keeps authentication, the kernel-ownership predicate and storage.

It refuses a profile that:

  • fails validation (profile-invalid)
  • names a different device from the row it would be stored under (device-mismatch)
  • is for another capability type (capability-type-mismatch)
  • has a term admission cannot evaluate (unverifiable-term). Every job under such a profile would fail closed, so it's refused at registration instead of discovered at settlement.

The digest is always computed server-side from the submitted profile; a client-supplied digest is only compared (digest-mismatch), never stored. Every problem is reported at once.

unverifiableTerms is exported from profile-admission.ts as unverifiableProfileTerms, so registration and admission share one definition.

Verified (DGX Spark)

  • 15 tests at 168ec19a (spec 47 files, 1054 passed; tsc clean). Each of the five rules is mutation-checked.
  • Updated for feat(spec): profileAdmitsBundle — admit, reject or hold job evidence against its committed MeasurementProfile #363 round 2 (48a69252). Each version pin now reads its own evidence field, so disjoint adapter and firmware lists are registrable. Registration refuses what admission would fail closed on: a pattern pin (*), a device kind no evidence source can carry, and an inactive primitive id. A profile with an unknown term is profile-invalid, so it can never be committed.
  • @pcc/spec 45 files, 965 passed; tsc --noEmit clean; the test files also type-check.

Route-level test plan (for when gateway builds the route; the table is operator queue item 56)

  1. Unauthenticated → 401. Authenticated non-owner of the device's kernel → 403 (the shared kernel-ownership predicate).
  2. Each checkProfileRegistration problem → 422, with every code returned.
  3. A valid profile → 201. The stored profileDigest is the server-computed one, and recomputes from the stored profileJson bytes.
  4. A mismatching client digest → 422, and nothing is stored.
  5. Re-registering the same (capabilityId, deviceId) inserts a new row and sets the old row's supersededAt. The old row is never edited.
  6. The R10 re-read returns only the current row.
  7. A profile registered for device A can't be read back for device B.

Draft; operator merges.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PGXt1epJimheWaESMGBbXc

LamaSu and others added 4 commits September 24, 2026 14:52
…tration route runs before inserting a row

pcc-gateway stores an operator's measurement profile in its own append-only
table keyed by (capabilityId, deviceId) (bus #2621), and composition's
compiler takes the current row's digest from the server-re-read snapshot
(bus #2238). This is the pure check the registration route calls; the route
keeps authentication, the kernel-ownership predicate and storage.

It refuses a profile that fails validation, names a different device than
its row, is for another capability type, or has a term admission cannot
evaluate (so every job under it would fail closed; better refused at
registration than discovered at settlement). The digest is always computed
server-side from the submitted profile; a client digest is only compared.
Every problem is reported at once.

unverifiableTerms in profile-admission.ts is exported as
unverifiableProfileTerms so registration and admission share one definition.

11 tests; each of the five rules is mutation-checked. Spec 965/965, tsc clean,
test files type-check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGXt1epJimheWaESMGBbXc
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rable; new fail-closed terms and unknown keys are refused

#363 @48a69252 checks each version pin against its own evidence field
(source.adapterVersion, source.firmwareVersion), so disjoint adapter and
firmware lists are now registrable. The "pins can never both be met" test is
replaced by a positive control. Registration refuses what admission would fail
closed on: a pattern pin ("*"), a device kind no evidence source can carry,
an inactive primitive id; and an unknown profile term is profile-invalid, so it
can never be committed.

Tests: registration 15 passed; spec 47 files, 1054 passed; tsc clean; the
test files type-check.
LamaSu added a commit that referenced this pull request Sep 29, 2026
… profile, not the device

adk's review of #431 (bus #3501):
- F1: committed_profile now refuses a profile with a term TS admission fails
  closed on, whatever the evidence says. profile_admission_blockers mirrors
  the vocabulary-free part of unverifiableProfileTerms: a pattern version pin,
  a tolerance, maxIntervalMs, required calibration or witnesses, coverage other
  than one-shot with minFraction 1, and a device kind outside
  EVIDENCE_DEVICE_TYPES. A test keeps that list equal to the TS source. The
  docstring's promise is narrowed: two vocabulary-bound terms (window tokens,
  inactive primitive ids) are refused at registration (#384), before a digest
  exists.
- F2: source_mismatches reports the profile's blockers first, so a pattern
  pin reads as the profile's problem, not the device's.
- F3 (ordering): digest parity for floats below 1e-4 needs #429; land #429
  before or with this.

The pinned print pilot now correctly refuses observations (its firmware pin is
a pattern); its digest golden stands. Observation tests use an admissible
variant, and the TS event-hash golden was recomputed (Python and TS agree).
Tests: 46 passed. pcc-node: the 2 failures are the pre-existing master ones.
End to end, a Python-built signed bundle is still ADMITTED by TS admission.
…-data copy of the request

The discipline astra required of admission (pack 39 round 2) applies here too.
Registration validated request.profile and then read it again for the device,
the capability type and the digest, so a getter or a caller mutation could
split what was checked from what was stored. Every check now runs on one
plainDataCopy of the request (each field read exactly once; anything JSON
cannot carry is refused as profile-invalid), and the profile returned for
storage is that copy.

Tests: registration 18 passed (3 new: a copy is returned, a lying getter is
read once, NaN is refused); spec 47 files, 1113 passed; tsc clean.
…er the plain-data boundary (astra pack 154)

The merged plain-data boundary refuses accessors through their descriptors, so the getter that once ran exactly once now never runs, and the request is profile-invalid. This is the stronger property the old test approximated.
… runtime proxy check, setter-free copy; astra packs 158/159)
…ra pack 162 (HIGH 2, MEDIUM 3, HIGH 4), with the #341 merge it carries
… 170 (load-time intrinsics in canonicalize and the profile path; isProxy from a static node:util import)
…which follows #363

The one conflict, spec/src/evidence/index.ts: #384's profile-registration export and master's
principal-id export are both kept. spec 2319/2319, tsc clean.
LamaSu added a commit that referenced this pull request Oct 4, 2026
…which follows #384

A clean merge. spec green, tsc clean.
…6478) into #384, which follows #363 (a plain merge, no edits)
LamaSu added a commit that referenced this pull request Oct 4, 2026
…leg names the verified signer) into #384, which follows #363 (a plain merge)
LamaSu added a commit that referenced this pull request Oct 4, 2026
A plain merge-up of #384's base, #363, after its review completed (packs 285
and 287, SHIP). It brings signer grants, delegated session keys, executor
non-independence and no-witness-authorized. #384's own files are unchanged.
LamaSu added a commit that referenced this pull request Oct 4, 2026
A plain merge-up of #520's base, #384, which now carries #363's round 13.

This branch had an error being deployed

1 failed deployment
trusted-checks — 8ac9954a Deployed Oct 4, 2026 by LamaSu via post-verdicts #76
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