Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). Thi

### Added
- Model Plane store manifests v0.1 (Tranche 7 / T7-2, resolves #242): `ModelManifest` (content-addressed base-model store manifest — `modelDigest` store key, architecture, quantization, format, tokenizer digest, context length, default tier, modalities) and `ModelAdapterManifest` (LoRA adapter manifest — `adapterDigest`, the `baseModelDigest` it binds to, task, rank, alpha, target modules, eval-report digest). Named `ModelAdapterManifest` (not `AdapterManifest`) to avoid collision with `AdapterDescriptor` (connector/actuation adapters). Complement, not duplicate, `SourceOSModelCarryRef` (a governance/carry *reference* pointing AT a manifest digest via `modelRef`; the manifest back-links via `carryRefs`). **Teeth (verified both ways):** a `ModelManifest` is schema-invalid without a `signature` descriptor (SEAM-014); a `ModelAdapterManifest` is invalid without `baseModelDigest` (SEAM-017, rejected on base mismatch) and without a `signature`; **both require an SPDX `license`** so the MIT/Apache-only rule is checkable before wiring (non-SPDX uses `LicenseRef-<name>` e.g. `LicenseRef-Gemma`/`LicenseRef-Llama-Community`, visibly not Apache/MIT — closes Model Plane spec OQ3 at the contract layer). Includes canonical examples (`examples/model-manifest.json`, `examples/model-adapter-manifest.json`) and `docs/adr/0016-model-plane-store-manifests.md`.
- Model Plane provider classification + review-hardening (Tranche 7, resolves #243; ADR-0017). **#243 resolved without a core-ontology break:** rather than a sixth `AgentPassport.agent_class`, a model-serving daemon is a `system_core` host process (its `AgentPassport`) *plus* an `InferenceProvider` record; the two compose via a new optional `InferenceProvider.passportRef` (`urn:srcos:agent-passport:` URN). **Design-review teeth added to the T7 provenance schemas (verified both ways):** (1) *ledger hash-chain* — `InferenceReceipt`/`EscalationDecision`/`AdapterPromotionDecision` now require a non-null `ledgerPrevHash` for any non-genesis entry (`ledgerSeq >= 1`; `seq 0` exempt), so an enumerated contribution list cannot be retroactively rewritten; (2) *biometric hard boundary* — an `EscalationDecision` whose T0 `sensitivityCheck.sensitiveCategories` contains `biometric` is schema-forced to `verdict: refused` / `refusalReason: biometric-boundary`, so face/speaker embeddings cannot cross a tier boundary under any grant (new example `examples/escalation-decision.refused-biometric.json`).
- 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`.
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
# ADR-0017: Model Plane provider classification + review-hardening (Tranche 7)

**Date:** 2026-08-02
**Status:** `Accepted`

---

## Context

Two open items from ADR-0015 / the Model Plane design review:

1. **T7-5 / #243** — the spec (§VIII) proposed adding a sixth `AgentPassport.agent_class`, `inference_provider`. But the estate already models inference providers as the separate `InferenceProvider` object, and `AgentPassport`'s five-class model (ADR-0014) is enforced as *exactly five* by `tools/validate_agent_passport_examples.py`.
2. **Design-review findings** with a schema-expressible fix: the ledger is the trust anchor for every provenance claim but `ledgerSeq` alone did not bind entry N to N-1 (finding #4); and the biometric boundary was described but not enforced at the escalation contract (finding #7).

## Decision

**#243 — do NOT add a sixth `agent_class`.** A model-serving daemon (`inferenced`, `visiond`, `embeddingd`, `distilld`) is a host process classified by its `AgentPassport` (typically `system_core` for a platform daemon) **and** described operationally by an `InferenceProvider` record (trust posture, network requirement, modalities). The two compose. To make the composition concrete, add an optional `InferenceProvider.passportRef` (`urn:srcos:agent-passport:` URN) linking the operational provider to its host-process classification. This keeps the five-class model intact and honors the estate's deliberate separate-object modeling.

**Review-hardening (schema teeth, verified both ways):**
- **Ledger hash-chain (finding #4):** on `InferenceReceipt`, `EscalationDecision`, and `AdapterPromotionDecision`, any non-genesis entry (`ledgerSeq >= 1`) must carry a non-null `ledgerPrevHash`. `ledgerSeq: 0` (genesis) is exempt. The append-only ledger is now hash-chained, so an enumerated contribution list cannot be retroactively rewritten.
- **Biometric hard boundary (finding #7):** on `EscalationDecision`, if the T0 `sensitivityCheck.sensitiveCategories` contains `biometric`, the crossing is schema-forced to `verdict: refused` with `refusalReason: biometric-boundary`. A `permitted` crossing carrying a biometric category is now schema-invalid — face/speaker embeddings cannot cross a tier boundary under any grant.

## Alternatives considered

| Alternative | Reason not chosen |
|-------------|------------------|
| Add `inference_provider` as a 6th `agent_class` | Breaks the `EXPECTED_CLASSES` "exactly five" invariant + ADR-0014; duplicates governance already carried by `InferenceProvider` + Model Plane receipts/leases. |
| Leave provider↔passport link implicit | Then "compose, don't add a class" is unenforceable/untraceable; the optional `passportRef` makes it explicit without a breaking change. |
| Make `ledgerPrevHash` always required | Breaks a legitimate genesis (`seq: 0`) entry; `>= 1` is the correct condition. |
| Enforce biometric boundary only in SHACL/impl | The escalation contract is exactly where the crossing decision lives; a schema gate is the earliest, cheapest refusal. |

## Consequences

- Positive: #243 resolved without a core-ontology break; two review findings closed as contract-enforced teeth.
- Positive: `passportRef` gives ontogenesis/agent-machine a typed link from an operational provider to its host passport.
- Follow-up (non-schema findings, filed separately): confidence self-report/router-vs-confidence contradiction (spec §II/§VII text), served==receipt emission reconciliation (workstation-contracts T7-19), trust concentration in T0 and the boundary-crossing incentive gradient (spec/ADR discussion), and the cross-document `sensitiveCategories`-completeness guarantee (ontogenesis SHACL T7-8). Schema `if/then` cannot express these.

## References

- SourceOS Model Plane — Architecture Specification v0.1, §II, §VII, §VIII
- ADR-0014 (five-class AgentPassport), ADR-0015 (inference-provenance schemas)
- Issue #243 (this decision); epic #241
- Changed: `schemas/InferenceProvider.json` (+`passportRef`), `schemas/InferenceReceipt.json`, `schemas/EscalationDecision.json`, `schemas/AdapterPromotionDecision.json`
30 changes: 30 additions & 0 deletions examples/escalation-decision.refused-biometric.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
{
"id": "urn:srcos:escalation-decision:2026-06-09-4472-biometric",
"type": "EscalationDecision",
"specVersion": "2.1.0",
"decidedAt": "2026-06-09T22:03:10Z",
"machineRef": "urn:srcos:agent-machine:workstation-01",
"task": "face_recognition_assist",
"fromTier": "T0",
"toTier": "T3",
"fromResidencyClass": "on_device_only",
"toResidencyClass": "sovereign_cluster",
"trigger": "low-confidence",
"sensitivityCheck": {
"performed": true,
"result": "fail",
"classifierDigest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"sensitiveCategories": ["biometric"]
},
"capabilityLeaseRef": null,
"targetProviderRef": null,
"targetExternalProviderRef": null,
"inputHash": "sha256:4444444444444444444444444444444444444444444444444444444444444444",
"verdict": "refused",
"refusalReason": "biometric-boundary",
"policyDecisionRef": "urn:srcos:decision:escalate-4472",
"ledgerSeq": 4472,
"ledgerPrevHash": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
"evidenceHash": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"evidenceRefs": []
}
14 changes: 13 additions & 1 deletion schemas/AdapterPromotionDecision.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,8 @@
"evalReportDigest",
"evalGates",
"verdict",
"rollbackTargetDigest"
"rollbackTargetDigest",
"ledgerSeq"
],
"properties": {
"id": { "type": "string", "pattern": "^urn:srcos:adapter-promotion-decision:", "description": "Stable URN identifier. Pattern: urn:srcos:adapter-promotion-decision:<local-id>" },
Expand DownExpand Up@@ -89,6 +90,17 @@
"required": ["rejectionReason"],
"properties": { "rejectionReason": { "type": "string" } }
}
},
{
"$comment": "REVIEW HARDENING (ledger tamper-evidence): any non-genesis entry (ledgerSeq >= 1) must carry the prior entry's hash, so a promotion's enumerated contribution list cannot be rewritten after the fact.",
"if": {
"properties": { "ledgerSeq": { "minimum": 1 } },
"required": ["ledgerSeq"]
},
"then": {
"required": ["ledgerPrevHash"],
"properties": { "ledgerPrevHash": { "type": "string" } }
}
}
]
}
33 changes: 32 additions & 1 deletion schemas/EscalationDecision.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,8 @@
"trigger",
"sensitivityCheck",
"inputHash",
"verdict"
"verdict",
"ledgerSeq"
],
"properties": {
"id": { "type": "string", "pattern": "^urn:srcos:escalation-decision:", "description": "Stable URN identifier. Pattern: urn:srcos:escalation-decision:<local-id>" },
Expand DownExpand Up@@ -98,6 +99,36 @@
"required": ["targetExternalProviderRef"],
"properties": { "targetExternalProviderRef": { "type": "string" } }
}
},
{
"$comment": "SEAM-015 BIOMETRIC HARD BOUNDARY: if the T0 sensitivity check flags a biometric category (face/speaker embeddings), no crossing is permitted under any grant — the verdict must be refused with refusalReason biometric-boundary. A permitted crossing carrying a biometric category is schema-invalid.",
"if": {
"properties": {
"sensitivityCheck": {
"properties": { "sensitiveCategories": { "contains": { "const": "biometric" } } },
"required": ["sensitiveCategories"]
}
},
"required": ["sensitivityCheck"]
},
"then": {
"required": ["refusalReason"],
"properties": {
"verdict": { "const": "refused" },
"refusalReason": { "const": "biometric-boundary" }
}
}
},
{
"$comment": "REVIEW HARDENING (ledger tamper-evidence): any non-genesis entry (ledgerSeq >= 1) must carry the prior entry's hash, so the append-only ledger is hash-chained.",
"if": {
"properties": { "ledgerSeq": { "minimum": 1 } },
"required": ["ledgerSeq"]
},
"then": {
"required": ["ledgerPrevHash"],
"properties": { "ledgerPrevHash": { "type": "string" } }
}
}
]
}
1 change: 1 addition & 0 deletions schemas/InferenceProvider.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,6 +17,7 @@
"supportedModalities": { "type": "array", "minItems": 1, "description": "Modalities the provider can serve.", "items": { "type": "string", "enum": ["text", "code", "embedding", "reranking", "vision", "audio", "video", "multimodal", "tool-calling"] } },
"openAICompatible": { "type": "boolean", "description": "Whether the provider exposes an OpenAI-compatible API surface." },
"requiresNetwork": { "type": "boolean", "description": "Whether the provider requires network egress beyond loopback/local IPC." },
"passportRef": { "type": ["string", "null"], "pattern": "^urn:srcos:agent-passport:", "description": "Optional AgentPassport URN of the host process that runs this provider daemon. A model-serving daemon is classified by its AgentPassport (typically system_core for a platform daemon) AND described operationally by this InferenceProvider record — the two compose, which is why the Model Plane does not add a sixth agent_class (ADR-0017)." },
"evidenceRefs": { "type": "array", "description": "Evidence URNs or hashes for provider validation, binary provenance, and compatibility testing.", "items": { "type": "string" } }
}
}
11 changes: 11 additions & 0 deletions schemas/InferenceReceipt.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -77,6 +77,17 @@
"then": {
"properties": { "escalatedFrom": { "const": null } }
}
},
{
"$comment": "REVIEW HARDENING (ledger tamper-evidence): any non-genesis entry (ledgerSeq >= 1) must carry the prior entry's hash, so the append-only ledger is hash-chained and an enumerated contribution list cannot be retroactively rewritten.",
"if": {
"properties": { "ledgerSeq": { "minimum": 1 } },
"required": ["ledgerSeq"]
},
"then": {
"required": ["ledgerPrevHash"],
"properties": { "ledgerPrevHash": { "type": "string" } }
}
}
]
}
Loading
Loading