diff --git a/CHANGELOG.md b/CHANGELOG.md index d49a5fc..5507d66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Thi ### Added - Ingestion-Pipeline contract family v0.1 (the parse → chunk → embed seam): `IngestedDocument` (a source parsed to one canonical plaintext offset space, `extractionDigest` RECOMPUTED from the text), `Chunk` (a span of that text with an optional embedding), `ExtractedEntity` (an entity grounded at exact document spans), and `EmbeddingRequest` (the one OpenAI-compatible embed-call shape for the whole estate). Generalizes the Noetica `agent-machine/doc-store` pipeline (`extractText`/`chunkTextWithSpans`/`linkDocGrounds`/`hgPutChunk` + an ollama embedder) into a portable contract, ending the platform-services-not-Noetica-only duplication against `prophet-platform/apps/embeddings`. **Normative invariant — one vector space or none**: `EmbeddingRequest` pins the sovereign model (`nomic-ai/nomic-embed-text-v1.5`) and dimension (`768`) by `const`, and every embedded `Chunk` must name that same model/dimension and carry a vector of exactly that length — a request for another model or a Matryoshka-truncated dimension fails by construction, so two producers cannot write into two silently-incomparable spaces. **Span soundness**: the validator recomputes `documentText[start:end] == chunk.text` and `sha256(text) == contentHash`, so a chunk can never silently misquote the document it cites; grounding soundness holds the same bar for `ExtractedEntity` mentions. Includes measured/pre-embedding chunk examples, seven negative conformance vectors (`fixtures/ingestion-pipeline/conformance.json`), a `validate-ingestion-pipeline-examples` target, and a normative spec note (`specs/ingestion-pipeline-contract.md`). Intended reference implementations: Noetica `doc-store` (rewired to the shared service) as producer, `prophet-platform/apps/embeddings` as service. +- Model Plane inference-provenance contracts v0.1 (Tranche 7 / T7-1, T7-3, T7-4): `InferenceReceipt` (the per-completion provenance primitive — tier, content-addressed base/adapter/tokenizer digests, serving daemon, data-residency class, escalation chain, ledger-bound), `EscalationDecision` (the governed tier / data-residency boundary crossing) and `AdapterPromotionDecision` (human-governed LoRA-adapter promotion). These sit **atop** the existing Agent Machine / Model Carry family and reference `InferenceProvider`, `ModelResidency`, `SourceOSModelCarryRef`, and `ExternalModelProviderProfile` rather than restate them; grant references use the estate's `urn:srcos:lease:` (AgentCapabilityLease), not the spec's illustrative `urn:srcos:grant:`. **The governance gates are schema `if/then` teeth, verified to fire both ways:** an off-device `InferenceReceipt` cannot validate without an authorizing lease and a non-empty escalation chain (SEAM-015); an `EscalationDecision` cannot be `permitted` without an authorizing `capabilityLeaseRef` *and* a passing T0 sensitivity check, with `prompt-unanswered` as the fail-closed resolution for non-interactive workloads (SEAM-015); an `AdapterPromotionDecision` cannot be `promoted` without a verified signature, per-event training consent, all eval gates passing (including an adversarial-poisoning probe), a named human promoter, and a mandatory rollback target (SEAM-016/017). All three carry optional `ledgerPrevHash` (append-only-ledger hash-chaining, so an enumerated contribution list cannot be retroactively rewritten) and the receipt carries optional `confidenceMethod` (the escalation trigger is self-reported — recording the method makes it auditable). Note: `AdapterPromotionDecision` governs *model* (LoRA) adapters and is distinct from `AdapterDescriptor` (connector/actuation adapters). Includes canonical examples (`examples/inference-receipt.json`, `examples/escalation-decision.json`, `examples/adapter-promotion-decision.json`) and `docs/adr/0015-model-plane-inference-provenance.md`, which records the reconciliation and the two deferred T7 decisions — `ModelManifest`/`AdapterManifest` (T7-2, overlaps `SourceOSModelCarryRef` + name collision) and adding `inference_provider` to `AgentPassport.agent_class` (T7-5, a core-ontology change vs the deliberate separate-object `InferenceProvider` modeling). - DeviceService contract v0.1 (FOG & CITIZEN PLANE W8.7 — the estate's first southbound device abstraction): `DeviceProfile` (what a device IS — protocol, typed protocol binding, and the exact metric set it produces with units, value types, operating ranges and protocol-native source addresses; digest-pinned by a `definitionDigest` the validator RECOMPUTES) and `DeviceReading` (one observation — deviceRef, metric, value, unit, quality, observedAt, carried on the MPCC `ConversationEvent` envelope with byte-identical sub-schemas, parity machine-enforced). EdgeX Foundry's lesson landed as a contract: ONE southbound interface, N protocol drivers — a driver speaks a protocol, it does not invent a vocabulary. **Normative invariant — a reading is ATTRIBUTABLE OR IT IS NOTHING**: `deviceRef`, `deviceProfileRef`, `profileDigest`, `metric`, `sourceAddress` and `unit` are all required, and the validator resolves every one of them across the example set (profile exists, digest matches the recomputed declaration, metric is declared, unit and source address agree, value is of the declared type and inside the declared range, `provenanceLinks` independently name both device and profile). Digest-pinning closes the retroactive-legalisation path — widening a range after the fact produces a new digest and orphans the readings it was meant to legalise, the `UpdateHealthProbe` construct applied to metrology. Quality is closed at five values with `unavailable` schema-bound to a null value plus a `NullAbsenceRecord` reference (the existing 12-kind MPCC absence taxonomy is reused, not re-invented); `stale` and `substituted` are normatively not `ok`. Simulated devices are a first-class labelled member of the protocol taxonomy (`protocol: "virtual"` ⇒ `synthetic:simulated-device`, enforced in both directions) — the `model-generated` admissibility rule applied to sensors. `observedAt`/`receivedAt`/`wallTime` stay distinct because `observedAt` → `receivedAt` is the southbound latency a twin's sync budget is spent on. Read-only at v0.1 (`access` closed to `"read"`): actuation must travel the `EffectRequest` → `EffectDecision` lifecycle. Distinct from `DeviceIdentity` (operator-workstation trust, bound via `identityRef`) and `TelemetryEvent` (agent-session diagnostics). Includes a BLE-GATT and a virtual profile plus measured and typed-absence reading examples, twenty-one negative conformance vectors (`fixtures/device-service/conformance.json`), a `validate-device-service-examples` target, and a normative spec note (`specs/device-service-contract.md`). Reference implementation: `device-service` in `SocioProphet/prophet-platform`. - A/B fallback update contract v0.1 (TRUST FABRIC W9.2): `UpdateSlot` (one of exactly two slots, carrying the GPT priority-boot attribute triple `bootPriority`/`triesRemaining`/`successful`, the installed payload digest, and a deliberate `role` vs `currentlyRunning` split so the fallback slot stays describable during a trial boot), `UpdateTransaction` (one apply attempt: write target, pinned probe, per-attempt boot record naming where each failure fell back to, terminal `promoted`/`rolled-back`/`refused` outcome with a closed `rollbackReason` set), and `UpdateHealthProbe` (the digest-pinned promotion gate: non-empty check set with at least one blocking check, mandatory hardware+software watchdog pair enforced by two `contains` clauses, `evaluatedIn` closed to post-boot userspace, `onProbeUnavailable` closed to `fail`). **Normative invariant — the currently-good slot is never overwritten by the update being applied** — enforced three ways: by schema within a document (a top-level `not`/`anyOf` enumerating the two illegal `(fromSlot, toSlot)` pairs, plus `UpdateSlot`'s `state: writing` ⇒ `role: candidate`), by schema on the settle path (four if/then clauses pinning `settledOnSlot` to `fromSlot` on rollback/refusal and to `toSlot` on promotion), and across documents by the validator (a settled transaction's `preservedPayloadDigest` still equals the active slot's `payloadDigest`). The attempt budget follows the GPT attribute — decremented by the bootloader *before* control transfers, so a payload that hangs before userspace still consumes an attempt — and `refused` is terminal, which is what ends the boot loop. Includes a refused-update and a promoted-update example set telling two coherent end-to-end stories, fourteen negative conformance vectors (`fixtures/ab-update/conformance.json`), a `validate-ab-update-examples` target whose probe-digest check is *recomputed* rather than read back, and a normative spec note (`specs/ab-fallback-update-contract.md`). Reference implementation: `AbUpdateMachine` in `sourceos-boot`. - `make validate` now also runs the duplicate schema `$id` guardrail, which previously ran only in CI — a local run could not reproduce the check that gates the PR. diff --git a/docs/adr/0015-model-plane-inference-provenance.md b/docs/adr/0015-model-plane-inference-provenance.md new file mode 100644 index 0000000..7e26b42 --- /dev/null +++ b/docs/adr/0015-model-plane-inference-provenance.md @@ -0,0 +1,60 @@ +# ADR-0015: Model Plane inference-provenance schemas (Tranche 7) + +**Date:** 2026-08-02 +**Status:** `Proposed` + +--- + +## Context + +The SourceOS Model Plane spec (SP-SESSION-DOSSIER-20260609, "Tiered On-Device Intelligence with Governed Distillation") introduces Tranche 7 and calls for six sourceos-spec artifacts (T7-1..T7-6): `InferenceReceipt`, `ModelManifest`, `AdapterManifest`, `EscalationDecision`, `AdapterPromotionDecision`, and the addition of an `inference_provider` value to `AgentPassport.agent_class`. + +The spec was drafted 2026-06-09 as pre-implementation. Since then the estate has landed an **Agent Machine / Model Carry** family that already covers a substantial part of the intended surface: + +- `InferenceProvider` — inference providers modelled as a **separate typed object** (providerClass, endpointMode, executionProfile, `trustPosture`, `supportedModalities`, `requiresNetwork`), *not* as an `AgentPassport` class. +- `ModelResidency` — the spec's `ModelResidencyEvent` (residency state, cache tier, quantization). +- `SourceOSModelCarryRef` — the governed model/adapter reference (`carryPolicy`, `cachePolicy`, `mutableModelState: false`, router/governance refs). +- `ExternalModelProviderProfile` — the T4 external-frontier egress-governance surface (`promptEgressDefault`, `allowTrainingUse`, `promptHashOnly`). +- `AdapterDescriptor` — a **connector/actuation** adapter (api/cli/sdk/event/file), a different sense of "adapter" than a LoRA model adapter. + +Authoring the T7 schemas naïvely would fork these canonical types. This ADR records what was added, what was reused, and what was deliberately deferred. + +## Decision + +Add three new schemas that sit **atop** the existing Model Carry family and reference it rather than restate it: + +1. `InferenceReceipt` (T7-1) — the per-completion provenance primitive. References `InferenceProvider`, `ModelResidency`, `SourceOSModelCarryRef`. SEAM-015 teeth: any off-device (`sovereign_cluster`/`external_permitted`) receipt is schema-required to carry an authorizing `capabilityLeaseRef` and a non-empty `escalationChain`; an `on_device_only` receipt cannot have been escalated. +2. `EscalationDecision` (T7-3) — the governed tier / data-residency boundary crossing. SEAM-015 teeth: `verdict: permitted` is schema-impossible without a `capabilityLeaseRef` **and** `sensitivityCheck.result: pass`; a T4 crossing must name an `ExternalModelProviderProfile`; a refusal must state a `refusalReason` (including `prompt-unanswered`, the fail-closed resolution for non-interactive workloads). +3. `AdapterPromotionDecision` (T7-4) — governed LoRA-adapter promotion. SEAM-016/017 teeth: `verdict: promoted` is schema-impossible without `signatureVerified: true`, `allEventsConsented: true`, a named `promotedBy`, all `evalGates` passing (including `adversarialProbePassed`), and it always requires a `rollbackTargetDigest`. + +Grant references use the existing `urn:srcos:lease:` (AgentCapabilityLease) prefix, not the spec's illustrative `urn:srcos:grant:`, which does not exist in the estate. + +All three carry an optional `ledgerPrevHash` (append-only-ledger hash-chaining) and the receipt carries an optional `confidenceMethod`, both introduced from the design review as low-risk additive hardening. + +**Deferred, pending decision (tracked as issues, not authored here):** + +- **T7-2 `ModelManifest` / `AdapterManifest`** — a content-addressed store manifest overlaps `SourceOSModelCarryRef`, and "AdapterManifest" collides in name with `AdapterDescriptor`. Needs a decision on whether these are new types or extensions of the carry-ref, and a disambiguated name (e.g. `ModelAdapterManifest`). +- **T7-5 add `inference_provider` to `AgentPassport.agent_class`** — the estate already models inference providers as the separate `InferenceProvider` object, which appears to be a deliberate choice that keeps the five-class host-process model intact. Adding a sixth class is a core-ontology change (it breaks `tools/validate_agent_passport_examples.py` `EXPECTED_CLASSES` and touches ADR-0014's five-class model). This is Michael's call, not a mechanical widening. + +## Alternatives considered + +| Alternative | Reason not chosen | +|-------------|------------------| +| Author all six T7 schemas verbatim from the spec | Would fork `SourceOSModelCarryRef` / `InferenceProvider` / `ModelResidency` and collide `AdapterManifest` with `AdapterDescriptor` — stranded duplicates. | +| Add `inference_provider` to `AgentPassport` now | The separate `InferenceProvider` object is an apparent deliberate resolution; a sixth host-process class is a core-ontology decision that deserves explicit sign-off. | +| Fold provenance into `AgentMachineReceipt` | `AgentMachineReceipt` records machine-runtime events (probe/placement/model-load); per-completion inference provenance with escalation chains and confidence is a distinct concern. | +| Mint a new `urn:srcos:grant:` prefix per the spec text | The estate's grant primitive is `AgentCapabilityLease` (`urn:srcos:lease:`); a new prefix would fork the capability model. | + +## Consequences + +- Positive: the three headline properties the Model Plane claims over Apple Intelligence — visible inference provenance, consent-gated escalation, enumerable/governed distillation — are now schema-enforced with `if/then` teeth verified to fire both ways, and reuse the existing Model Carry vocabulary. +- Positive: the schema-level gates encode the design-review findings (∅-grant ⇒ no crossing; signature ≠ safety, so eval gates are also required; fail-closed background escalation; ledger tamper-evidence and confidence-method as optional hardening). +- Negative / follow-up: schema `if/then` cannot express the cross-document invariants (served-count == receipt-count reconciliation; ledger hash-chain continuity; base-model-digest match between promotion and adapter manifest; biometric-never-crosses-the-socket). These are delegated to ontogenesis SHACL (T7-8) and workstation-contracts conformance (T7-19) and are called out there. +- Negative: `ModelManifest`/`AdapterManifest` and the `inference_provider` class remain open, so Tranche 7 is not complete with this change. + +## References + +- SourceOS Model Plane — Architecture Specification v0.1 (2026-06-09), §VII–§X, §XIV +- ADR-0014 — Agent System domain (the five-class `AgentPassport` model) +- Reused: `schemas/InferenceProvider.json`, `schemas/ModelResidency.json`, `schemas/SourceOSModelCarryRef.json`, `schemas/ExternalModelProviderProfile.json`, `schemas/AgentMachineReceipt.json` +- SEAM-011 (non-local ledger), SEAM-014..017 (Model Plane seam registry, source-os T7-20) diff --git a/examples/adapter-promotion-decision.json b/examples/adapter-promotion-decision.json new file mode 100644 index 0000000..5ba46a7 --- /dev/null +++ b/examples/adapter-promotion-decision.json @@ -0,0 +1,30 @@ +{ + "id": "urn:srcos:adapter-promotion-decision:2026-06-10-summ-v3", + "type": "AdapterPromotionDecision", + "specVersion": "2.1.0", + "decidedAt": "2026-06-10T09:15:00Z", + "candidateAdapterDigest": "sha256:8888888888888888888888888888888888888888888888888888888888888888", + "replacesAdapterDigest": "sha256:9999999999999999999999999999999999999999999999999999999999999999", + "baseModelDigest": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + "task": "summarization", + "evalReportDigest": "sha256:6666666666666666666666666666666666666666666666666666666666666666", + "evalGates": { + "regressionPassed": true, + "taskImprovementPassed": true, + "safetyPassed": true, + "determinismPassed": true, + "adversarialProbePassed": true + }, + "signatureVerified": true, + "contributingOverrideEvents": [4471, 4488, 4512], + "contributingEventCount": 3, + "allEventsConsented": true, + "promotedBy": "michael@socioprophet.ai", + "verdict": "promoted", + "rejectionReason": null, + "rollbackTargetDigest": "sha256:7777777777777777777777777777777777777777777777777777777777777777", + "ledgerSeq": 9001, + "ledgerPrevHash": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "evidenceHash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "evidenceRefs": ["urn:srcos:decision:promote-summ-v3"] +} diff --git a/examples/escalation-decision.json b/examples/escalation-decision.json new file mode 100644 index 0000000..6408ee8 --- /dev/null +++ b/examples/escalation-decision.json @@ -0,0 +1,30 @@ +{ + "id": "urn:srcos:escalation-decision:2026-06-09-4471", + "type": "EscalationDecision", + "specVersion": "2.1.0", + "decidedAt": "2026-06-09T21:59:58Z", + "machineRef": "urn:srcos:agent-machine:workstation-01", + "task": "agent_classification", + "fromTier": "T1", + "toTier": "T3", + "fromResidencyClass": "on_device_only", + "toResidencyClass": "sovereign_cluster", + "trigger": "low-confidence", + "sensitivityCheck": { + "performed": true, + "result": "pass", + "classifierDigest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "sensitiveCategories": [] + }, + "capabilityLeaseRef": "urn:srcos:lease:noetica-t3-escalation-9f2a", + "targetProviderRef": "urn:srcos:inference-provider:sovereign-cluster-teacher-70b", + "targetExternalProviderRef": null, + "inputHash": "sha256:4444444444444444444444444444444444444444444444444444444444444444", + "verdict": "permitted", + "refusalReason": null, + "policyDecisionRef": "urn:srcos:decision:escalate-4471", + "ledgerSeq": 4471, + "ledgerPrevHash": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "evidenceHash": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "evidenceRefs": [] +} diff --git a/examples/inference-receipt.json b/examples/inference-receipt.json new file mode 100644 index 0000000..ead5f12 --- /dev/null +++ b/examples/inference-receipt.json @@ -0,0 +1,33 @@ +{ + "id": "urn:srcos:inference-receipt:2026-06-09-8823", + "type": "InferenceReceipt", + "specVersion": "2.1.0", + "issuedAt": "2026-06-09T22:00:00Z", + "requestingAgentRef": "urn:srcos:agent-passport:com.socioprophet.noetica", + "requestingAgentClass": "third_party", + "capabilityLeaseRef": "urn:srcos:lease:noetica-t3-escalation-9f2a", + "providerDaemon": "inferenced", + "providerRef": "urn:srcos:inference-provider:sovereign-cluster-teacher-70b", + "tier": "T3", + "baseModelDigest": "sha256:1111111111111111111111111111111111111111111111111111111111111111", + "adapterDigest": null, + "tokenizerDigest": "sha256:3333333333333333333333333333333333333333333333333333333333333333", + "modelCarryRef": "urn:srcos:model-carry-ref:teacher-70b-q4k", + "modelResidencyRef": "urn:srcos:model-residency:cluster-node-01-teacher-70b", + "task": "agent_classification", + "inputHash": "sha256:4444444444444444444444444444444444444444444444444444444444444444", + "inputTokenCount": 1840, + "outputHash": "sha256:5555555555555555555555555555555555555555555555555555555555555555", + "outputTokenCount": 210, + "confidence": 0.94, + "confidenceMethod": "logit-entropy", + "latencyMs": 1240, + "escalatedFrom": "T1", + "escalationChain": ["urn:srcos:escalation-decision:2026-06-09-4471"], + "dataResidencyClass": "sovereign_cluster", + "computeDevice": "remote", + "ledgerSeq": 8823, + "ledgerPrevHash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "evidenceHash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "evidenceRefs": ["urn:srcos:decision:route-8823"] +} diff --git a/schemas/AdapterPromotionDecision.json b/schemas/AdapterPromotionDecision.json new file mode 100644 index 0000000..04adbaf --- /dev/null +++ b/schemas/AdapterPromotionDecision.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/AdapterPromotionDecision.json", + "title": "AdapterPromotionDecision", + "description": "Governed decision artifact for promoting (or rejecting) a candidate LoRA adapter produced by the Model Plane distillation loop (Tranche 7). Adapter promotion is a human-governed decision, never an automatic OS update (Model Plane §VII). A promotion enumerates every override event that contributed to the adapter — the auditable property Apple's silent loop lacks — and cannot proceed without a verified signature, per-event training consent, a passing eval gate, and a mandatory rollback target (SEAM-016, SEAM-017). Note: this governs a *model* (LoRA) adapter and is distinct from AdapterDescriptor, which types connector/actuation adapters.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "specVersion", + "decidedAt", + "candidateAdapterDigest", + "baseModelDigest", + "task", + "evalReportDigest", + "evalGates", + "verdict", + "rollbackTargetDigest" + ], + "properties": { + "id": { "type": "string", "pattern": "^urn:srcos:adapter-promotion-decision:", "description": "Stable URN identifier. Pattern: urn:srcos:adapter-promotion-decision:" }, + "type": { "const": "AdapterPromotionDecision", "description": "Discriminator constant — always \"AdapterPromotionDecision\"." }, + "specVersion": { "type": "string", "description": "Spec version of this document, e.g. \"2.1.0\"." }, + "decidedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the promotion decision was made." }, + "candidateAdapterDigest": { "type": "string", "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Content-addressed digest of the candidate LoRA adapter under decision." }, + "replacesAdapterDigest": { "type": ["string", "null"], "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Digest of the adapter this candidate would replace, or null if it is the first adapter for the task." }, + "baseModelDigest": { "type": "string", "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Digest of the base model the adapter binds to. The candidate adapter's manifest MUST declare this same digest; a mismatch is rejected (SEAM-017)." }, + "task": { "type": "string", "description": "Task the adapter specializes, e.g. summarization, translation-de-en, agent_classification." }, + "evalReportDigest": { "type": "string", "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Content-addressed digest of the eval report stored alongside the adapter in the model store." }, + "evalGates": { + "type": "object", + "additionalProperties": false, + "required": ["regressionPassed", "taskImprovementPassed", "safetyPassed", "determinismPassed", "adversarialProbePassed"], + "description": "Promotion gate results. Every gate must pass for a `promoted` verdict (enforced below).", + "properties": { + "regressionPassed": { "type": "boolean", "description": "Candidate does not degrade prior tasks." }, + "taskImprovementPassed": { "type": "boolean", "description": "Candidate meets the target-task improvement threshold." }, + "safetyPassed": { "type": "boolean", "description": "Candidate passes the safety evaluation." }, + "determinismPassed": { "type": "boolean", "description": "Same input yields the same output within tolerance." }, + "adversarialProbePassed": { "type": "boolean", "description": "REVIEW HARDENING (SEAM-016 finding: a signed, consented, poisoned override event passes a generic safety suite while carrying a targeted backdoor). Candidate passes adversarial probes targeting the training-data-poisoning / backdoor vector specifically, not only generic safety." } + } + }, + "signatureVerified": { "type": "boolean", "description": "Whether the candidate adapter's detached signature verified against the trust store. Must be true to promote (SEAM-017). Note: signature proves origin, not safety — the eval gate is the safety authority." }, + "contributingOverrideEvents": { "type": "array", "description": "Ledger sequence numbers of every OverrideEvent that contributed to this adapter. This is the enumerable-contribution property (Model Plane §XII).", "items": { "type": "integer", "minimum": 0 } }, + "contributingEventCount": { "type": ["integer", "null"], "minimum": 0, "description": "Count of contributing override events. Should equal the length of contributingOverrideEvents." }, + "allEventsConsented": { "type": "boolean", "description": "Whether every contributing override event carried valid training-data consent. Must be true to promote (SEAM-016)." }, + "promotedBy": { "type": ["string", "null"], "description": "Human decision-maker (identity/email) who promoted the adapter. Required for a `promoted` verdict (enforced below) — promotion is a governed human decision, not automatic." }, + "verdict": { "type": "string", "enum": ["promoted", "rejected", "held"], "description": "Decision outcome." }, + "rejectionReason": { "type": ["string", "null"], "enum": ["eval-failed", "signature-unverified", "unconsented-events", "base-model-mismatch", "insufficient-batch", "manual-hold", null], "description": "Why the candidate was not promoted. Required when verdict is rejected." }, + "rollbackTargetDigest": { "type": "string", "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Digest the running adapter set rolls back to if this promotion is reverted. Mandatory for every decision (SEAM-017) — rollback is a single operation and does not require retraining. For a first-adapter promotion this is the base-only (no-adapter) marker digest." }, + "ledgerSeq": { "type": "integer", "minimum": 0, "description": "Monotonic sequence number of this decision in the append-only ledger." }, + "ledgerPrevHash": { "type": ["string", "null"], "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "REVIEW HARDENING (ledger tamper-evidence): hash of the prior ledger entry, so a promotion's enumerated contribution list cannot be rewritten after the fact." }, + "evidenceHash": { "type": ["string", "null"], "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Hash of the canonical decision evidence payload." }, + "evidenceRefs": { "type": "array", "description": "Additional evidence URNs or content hashes.", "items": { "type": "string" } } + }, + "allOf": [ + { + "$comment": "SEAM-016 + SEAM-017 TEETH: a `promoted` verdict requires a verified signature, per-event training consent, ALL eval gates passing, and a human promoter. Missing any one cannot yield `promoted`.", + "if": { + "properties": { "verdict": { "const": "promoted" } }, + "required": ["verdict"] + }, + "then": { + "required": ["signatureVerified", "allEventsConsented", "promotedBy"], + "properties": { + "signatureVerified": { "const": true }, + "allEventsConsented": { "const": true }, + "promotedBy": { "type": "string", "minLength": 1 }, + "evalGates": { + "properties": { + "regressionPassed": { "const": true }, + "taskImprovementPassed": { "const": true }, + "safetyPassed": { "const": true }, + "determinismPassed": { "const": true }, + "adversarialProbePassed": { "const": true } + } + } + } + } + }, + { + "$comment": "A rejection must state why.", + "if": { + "properties": { "verdict": { "const": "rejected" } }, + "required": ["verdict"] + }, + "then": { + "required": ["rejectionReason"], + "properties": { "rejectionReason": { "type": "string" } } + } + } + ] +} diff --git a/schemas/EscalationDecision.json b/schemas/EscalationDecision.json new file mode 100644 index 0000000..369b72d --- /dev/null +++ b/schemas/EscalationDecision.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/EscalationDecision.json", + "title": "EscalationDecision", + "description": "Governed decision recorded whenever a request crosses (or is refused at) a tier / data-residency boundary in the Model Plane (Tranche 7). Escalation is a governed event, never a silent fallback (SEAM-015): a crossing is PERMITTED only when an authorizing capability lease is present AND the T0 sensitivity check passes for the target residency class; otherwise it is REFUSED. Fail-closed by construction — absence of a grant or an unanswered consent prompt resolves to refusal, not to a downgrade.", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "specVersion", + "decidedAt", + "machineRef", + "task", + "fromTier", + "toTier", + "fromResidencyClass", + "toResidencyClass", + "trigger", + "sensitivityCheck", + "inputHash", + "verdict" + ], + "properties": { + "id": { "type": "string", "pattern": "^urn:srcos:escalation-decision:", "description": "Stable URN identifier. Pattern: urn:srcos:escalation-decision:" }, + "type": { "const": "EscalationDecision", "description": "Discriminator constant — always \"EscalationDecision\"." }, + "specVersion": { "type": "string", "description": "Spec version of this document, e.g. \"2.1.0\"." }, + "decidedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the escalation decision was made." }, + "machineRef": { "type": "string", "pattern": "^urn:srcos:agent-machine:", "description": "Agent Machine URN where the decision was made. The routing/sensitivity decision is made by the always-resident T0 tier; the router itself never escalates (Model Plane §II)." }, + "task": { "type": "string", "description": "Task label of the request being considered for escalation." }, + "fromTier": { "type": "string", "enum": ["T0", "T1", "T2", "T3"], "description": "Tier the request is escalating from." }, + "toTier": { "type": "string", "enum": ["T1", "T2", "T3", "T4"], "description": "Tier the request would escalate to." }, + "fromResidencyClass": { "type": "string", "enum": ["on_device_only", "sovereign_cluster", "external_permitted"], "description": "Data-residency class of the source tier." }, + "toResidencyClass": { "type": "string", "enum": ["on_device_only", "sovereign_cluster", "external_permitted"], "description": "Data-residency class of the target tier — the boundary being crossed." }, + "trigger": { "type": "string", "enum": ["low-confidence", "user-request", "downstream-validation-failure", "capability-exceeds-tier"], "description": "What triggered the escalation consideration (Model Plane §VII step 2)." }, + "sensitivityCheck": { + "type": "object", + "additionalProperties": false, + "required": ["performed", "result"], + "description": "The T0 PII/sensitivity gate evaluated before any crossing. A crossing cannot be permitted unless this ran and passed (enforced below; SEAM-015).", + "properties": { + "performed": { "type": "boolean", "description": "Whether the T0 sensitivity check actually ran. A crossing where this is false must be refused." }, + "result": { "type": "string", "enum": ["pass", "fail", "not-run"], "description": "Outcome of the sensitivity check for the target residency class." }, + "classifierDigest": { "type": ["string", "null"], "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Content-addressed digest of the T0 sensitivity classifier that produced this result, so the gate itself is auditable." }, + "sensitiveCategories": { "type": "array", "description": "Categories detected (e.g. pii, biometric, credential, health). A biometric detection forbids any crossing.", "items": { "type": "string" } } + } + }, + "capabilityLeaseRef": { "type": ["string", "null"], "pattern": "^urn:srcos:lease:", "description": "AgentCapabilityLease URN authorizing the crossing to the target residency class. Null when no grant exists — in which case the verdict must be refused (enforced below)." }, + "targetProviderRef": { "type": ["string", "null"], "pattern": "^urn:srcos:inference-provider:", "description": "InferenceProvider URN that would serve the escalated request (for on-device / sovereign-cluster targets)." }, + "targetExternalProviderRef": { "type": ["string", "null"], "pattern": "^urn:srcos:external-model-provider-profile:", "description": "ExternalModelProviderProfile URN for a T4 external-frontier target. Required when toTier is T4 (enforced below)." }, + "inputHash": { "type": "string", "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Hash of the canonical input under consideration. Content is not recorded here." }, + "verdict": { "type": "string", "enum": ["permitted", "refused"], "description": "Decision outcome. `permitted` requires a grant and a passing sensitivity check; anything else is `refused`." }, + "refusalReason": { "type": ["string", "null"], "enum": ["no-grant", "sensitivity-fail", "residency-forbidden", "biometric-boundary", "no-consent-surface", "prompt-unanswered", "provider-unavailable", null], "description": "Why the crossing was refused. Required when verdict is refused. `prompt-unanswered` is the fail-closed resolution for a non-interactive/background workload where an escalation consent prompt could not be answered." }, + "policyDecisionRef": { "type": ["string", "null"], "pattern": "^urn:srcos:decision:", "description": "PolicyDecision URN backing this escalation decision." }, + "ledgerSeq": { "type": "integer", "minimum": 0, "description": "Monotonic sequence number of this decision in the append-only ledger." }, + "ledgerPrevHash": { "type": ["string", "null"], "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "REVIEW HARDENING (ledger tamper-evidence): hash of the prior ledger entry, binding this decision into the hash-chain." }, + "evidenceHash": { "type": ["string", "null"], "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Hash of the canonical decision evidence payload." }, + "evidenceRefs": { "type": "array", "description": "Additional evidence URNs or content hashes.", "items": { "type": "string" } } + }, + "allOf": [ + { + "$comment": "SEAM-015 TEETH: a crossing is PERMITTED only if an authorizing lease is present AND the sensitivity check ran and passed. ∅ grant or a non-passing check cannot yield `permitted`.", + "if": { + "properties": { "verdict": { "const": "permitted" } }, + "required": ["verdict"] + }, + "then": { + "required": ["capabilityLeaseRef"], + "properties": { + "capabilityLeaseRef": { "type": "string" }, + "sensitivityCheck": { + "properties": { + "performed": { "const": true }, + "result": { "const": "pass" } + } + } + } + } + }, + { + "$comment": "A refusal must state why.", + "if": { + "properties": { "verdict": { "const": "refused" } }, + "required": ["verdict"] + }, + "then": { + "required": ["refusalReason"], + "properties": { "refusalReason": { "type": "string" } } + } + }, + { + "$comment": "A T4 external-frontier crossing must name the governed external provider profile it egresses through.", + "if": { + "properties": { "toTier": { "const": "T4" }, "verdict": { "const": "permitted" } }, + "required": ["toTier", "verdict"] + }, + "then": { + "required": ["targetExternalProviderRef"], + "properties": { "targetExternalProviderRef": { "type": "string" } } + } + } + ] +} diff --git a/schemas/InferenceReceipt.json b/schemas/InferenceReceipt.json new file mode 100644 index 0000000..054bb2e --- /dev/null +++ b/schemas/InferenceReceipt.json @@ -0,0 +1,82 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.srcos.ai/v2/InferenceReceipt.json", + "title": "InferenceReceipt", + "description": "Provenance receipt emitted for every model-plane completion (Tranche 7 / Model Plane). Identifies the tier, the content-addressed base model / adapter / tokenizer digests, the serving daemon and provider, the data-residency class the request was served under, and the escalation chain if any. This is the provenance primitive that makes on-device inference auditable: it references the existing Model Carry family (InferenceProvider, ModelResidency, SourceOSModelCarryRef) rather than restating it. Receipts are ledger-bound; a local-only ledger is not permitted (SEAM-011).", + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "type", + "specVersion", + "issuedAt", + "providerDaemon", + "tier", + "baseModelDigest", + "task", + "inputHash", + "outputHash", + "dataResidencyClass", + "ledgerSeq" + ], + "properties": { + "id": { "type": "string", "pattern": "^urn:srcos:inference-receipt:", "description": "Stable URN identifier. Pattern: urn:srcos:inference-receipt:" }, + "type": { "const": "InferenceReceipt", "description": "Discriminator constant — always \"InferenceReceipt\"." }, + "specVersion": { "type": "string", "description": "Spec version of this document, e.g. \"2.1.0\"." }, + "issuedAt": { "type": "string", "format": "date-time", "description": "Timestamp when the completion finished and the receipt was issued." }, + "requestingAgentRef": { "type": ["string", "null"], "pattern": "^urn:srcos:agent-passport:", "description": "AgentPassport URN of the agent that requested the inference. Null only for internal system-initiated inference." }, + "requestingAgentClass": { "type": ["string", "null"], "enum": ["system_core", "intelligence_automation", "app_helper", "legacy_bridge", "third_party", null], "description": "Mirror of the requesting agent's AgentPassport.agent_class, for at-a-glance audit. Authoritative source is the referenced passport." }, + "capabilityLeaseRef": { "type": ["string", "null"], "pattern": "^urn:srcos:lease:", "description": "AgentCapabilityLease URN authorizing this provider connection. Null is only valid for on_device_only T0 system inference; any non-on_device_only receipt MUST carry a lease (enforced below)." }, + "providerDaemon": { "type": "string", "enum": ["inferenced", "embeddingd", "visiond", "distilld"], "description": "The model-plane serving daemon that produced this completion. Note: modelplaned is a catalog authority and does not perform inference, so it never emits an InferenceReceipt." }, + "providerRef": { "type": ["string", "null"], "pattern": "^urn:srcos:inference-provider:", "description": "InferenceProvider URN backing the serving daemon." }, + "tier": { "type": "string", "enum": ["T0", "T1", "T2", "T3", "T4"], "description": "Placement tier that served the request. Tier boundaries are data-residency boundaries (Model Plane §II)." }, + "baseModelDigest": { "type": "string", "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Content-addressed digest of the base model weights that served the request." }, + "adapterDigest": { "type": ["string", "null"], "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Content-addressed digest of the active LoRA adapter, or null when the base model served the request with no adapter." }, + "tokenizerDigest": { "type": ["string", "null"], "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Content-addressed digest of the tokenizer used." }, + "modelCarryRef": { "type": ["string", "null"], "pattern": "^urn:srcos:model-carry-ref:", "description": "SourceOSModelCarryRef URN under which the model was carried/authorized." }, + "modelResidencyRef": { "type": ["string", "null"], "pattern": "^urn:srcos:model-residency:", "description": "ModelResidency URN observed at serving time." }, + "task": { "type": "string", "description": "Task label the request was routed as, e.g. summarization, embedding, agent_classification, ocr-recognize." }, + "inputHash": { "type": "string", "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Hash of the canonical input. The receipt records that an inference occurred and over what, not the content." }, + "inputTokenCount": { "type": ["integer", "null"], "minimum": 0, "description": "Input token count, or null for non-text modalities." }, + "outputHash": { "type": "string", "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Hash of the canonical output." }, + "outputTokenCount": { "type": ["integer", "null"], "minimum": 0, "description": "Output token count, or null for non-text modalities." }, + "confidence": { "type": ["number", "null"], "minimum": 0, "maximum": 1, "description": "Model-reported confidence used for the escalation trigger, or null when not estimated." }, + "confidenceMethod": { "type": ["string", "null"], "enum": ["logit-entropy", "verbalized", "calibration-head", "downstream-validation", "none", null], "description": "How `confidence` was derived. REVIEW HARDENING (Model Plane OQ1 / finding: confidence is the load-bearing escalation gate and is self-reported): recording the method makes the number auditable and lets downstream calibration checks run. A null method with a non-null confidence should be treated as uncalibrated." }, + "latencyMs": { "type": ["integer", "null"], "minimum": 0, "description": "End-to-end serving latency in milliseconds." }, + "escalatedFrom": { "type": ["string", "null"], "enum": ["T0", "T1", "T2", "T3", null], "description": "The tier this request was escalated FROM, or null if served at first placement. Must be null when dataResidencyClass is on_device_only (enforced below)." }, + "escalationChain": { "type": "array", "description": "Ordered EscalationDecision URNs recording each tier/residency boundary this request crossed. Empty when served at first placement.", "items": { "type": "string", "pattern": "^urn:srcos:escalation-decision:" } }, + "dataResidencyClass": { "type": "string", "enum": ["on_device_only", "sovereign_cluster", "external_permitted"], "description": "The residency class this completion was served under. Any class other than on_device_only means the input crossed a data boundary and MUST be accompanied by a lease and a non-empty escalation chain (enforced below; SEAM-015)." }, + "computeDevice": { "type": ["string", "null"], "enum": ["ane", "gpu", "cpu", "npu", "remote", null], "description": "Hardware path that executed the compute." }, + "ledgerSeq": { "type": "integer", "minimum": 0, "description": "Monotonic sequence number of this receipt in the append-only ledger." }, + "ledgerPrevHash": { "type": ["string", "null"], "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "REVIEW HARDENING (finding: the ledger is the trust anchor for every Model Plane provenance claim but `ledgerSeq` alone does not bind entry N to N-1). Hash of the prior ledger entry, making the ledger tamper-evident/hash-chained so an enumerated contribution list cannot be retroactively rewritten." }, + "evidenceHash": { "type": ["string", "null"], "pattern": "^sha256:[a-fA-F0-9]{64}$", "description": "Hash of the canonical receipt evidence payload." }, + "evidenceRefs": { "type": "array", "description": "Additional evidence URNs or content hashes.", "items": { "type": "string" } } + }, + "allOf": [ + { + "$comment": "SEAM-015: a completion served off-device (not on_device_only) must carry both an authorizing capability lease and a non-empty escalation chain. Possession of the output is not authorization for the crossing.", + "if": { + "properties": { "dataResidencyClass": { "enum": ["sovereign_cluster", "external_permitted"] } }, + "required": ["dataResidencyClass"] + }, + "then": { + "required": ["capabilityLeaseRef", "escalatedFrom", "escalationChain"], + "properties": { + "capabilityLeaseRef": { "type": "string" }, + "escalatedFrom": { "type": "string", "enum": ["T0", "T1", "T2", "T3"] }, + "escalationChain": { "minItems": 1 } + } + } + }, + { + "$comment": "An on_device_only completion cannot have been escalated from a lower tier.", + "if": { + "properties": { "dataResidencyClass": { "const": "on_device_only" } }, + "required": ["dataResidencyClass"] + }, + "then": { + "properties": { "escalatedFrom": { "const": null } } + } + } + ] +} diff --git a/schemas/README.md b/schemas/README.md index b1609a7..cb1d355 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -5,6 +5,27 @@ This directory contains the JSON Schema (draft 2020-12) files that make up the S --- +## Recent additions — Model Plane Provenance v0.1 (Tranche 7) + +The governed-inference provenance layer atop the existing Agent Machine / Model Carry family (`InferenceProvider`, `ModelResidency`, `SourceOSModelCarryRef`, `ExternalModelProviderProfile`, `AgentMachineReceipt`, all reused, not restated): + +| File | Type | URN prefix | +|------|------|-----------| +| `InferenceReceipt.json` | InferenceReceipt | `urn:srcos:inference-receipt:` | +| `EscalationDecision.json` | EscalationDecision | `urn:srcos:escalation-decision:` | +| `AdapterPromotionDecision.json` | AdapterPromotionDecision | `urn:srcos:adapter-promotion-decision:` | + +These types make on-device inference auditable in the ways Apple Intelligence's silent loop does not: +- **`InferenceReceipt`** — the provenance primitive emitted for every completion: tier, content-addressed base/adapter/tokenizer digests, serving daemon, the data-residency class served under, and the escalation chain. Ledger-bound (a local-only ledger is not permitted, SEAM-011). Off-device receipts (`sovereign_cluster`/`external_permitted`) are schema-required to carry an authorizing lease and a non-empty escalation chain — possession of the output is not authorization for the crossing (SEAM-015). +- **`EscalationDecision`** — the governed record of a tier / data-residency boundary crossing. Fail-closed by construction: a `permitted` verdict is schema-impossible without an authorizing capability lease **and** a passing T0 sensitivity check; ∅-grant or an unanswered background consent prompt (`refusalReason: prompt-unanswered`) resolves to refusal, never a silent downgrade (SEAM-015). +- **`AdapterPromotionDecision`** — adapter promotion as a human-governed decision, never an automatic OS update. Enumerates every contributing `OverrideEvent` (the property Apple's loop lacks), and a `promoted` verdict is schema-impossible without a verified signature, per-event training consent, all eval gates passing (including an adversarial-poisoning probe), a named human promoter, and a mandatory rollback target (SEAM-016, SEAM-017). Governs *model* (LoRA) adapters — distinct from `AdapterDescriptor` (connector/actuation adapters). + +All three carry an optional `ledgerPrevHash` (hash-chain the append-only ledger so an enumerated contribution list cannot be retroactively rewritten) and, on the receipt, an optional `confidenceMethod` (the escalation trigger is self-reported — recording the method makes it auditable). + +Validation: `ajv validate -s schemas/.json -d examples/.json`. Canonical examples: `examples/inference-receipt.json`, `examples/escalation-decision.json`, `examples/adapter-promotion-decision.json`. ADR: `docs/adr/0015-model-plane-inference-provenance.md`. + +--- + ## Recent additions — DeviceService Contract v0.1 (the southbound device plane, W8.7) The southbound device abstraction — the estate's first — adds the following top-level schemas: @@ -360,6 +381,9 @@ These types support: | `TruthSurface` | Signed truth summary emitted by a plane (system/user/agent/witness) | | `DeltaSurface` | Signed diff between two TruthSurfaces with gate results | | `SourceOSInteractionEvent` | Shared noetic/chat/task lifecycle event envelope for Noetica, AgentTerm, governance traces, task submission, and evidence handoff | +| `InferenceReceipt` | Model Plane (T7): per-completion provenance receipt — tier, model/adapter/tokenizer digests, residency class, escalation chain; ledger-bound (SEAM-011/015) | +| `EscalationDecision` | Model Plane (T7): governed tier/data-residency boundary crossing; fail-closed — `permitted` requires a lease and a passing T0 sensitivity check (SEAM-015) | +| `AdapterPromotionDecision` | Model Plane (T7): human-governed LoRA-adapter promotion; enumerates contributing override events; requires signature + consent + eval + rollback (SEAM-016/017) | ### Agent Plane