Skip to content
Closed
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
162 changes: 162 additions & 0 deletions apps/api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -7384,6 +7384,36 @@
"title": "PromoteDatasetRequest",
"type": "object"
},
"PublishCalibrationRevisionRequest": {
"description": "Body for `POST /calibrations/{id}/revisions/{revision_id}/publish`.",
"properties": {
"peer_facility_id": {
"description": "Opaque id of the peer facility this publication is targeted at. Resolved at the handler via PermitLookup to locate the matching Active outbound Permit; missing or inactive permits raise 409.",
"title": "Peer Facility Id",
"type": "string"
}
},
"required": [
"peer_facility_id"
],
"title": "PublishCalibrationRevisionRequest",
"type": "object"
},
"PublishCalibrationRevisionResponse": {
"description": "Response body for the publish action.",
"properties": {
"receipt_id": {
"format": "uuid",
"title": "Receipt Id",
"type": "string"
}
},
"required": [
"receipt_id"
],
"title": "PublishCalibrationRevisionResponse",
"type": "object"
},
"RateDecisionRequest": {
"description": "Body for `POST /decisions/{decision_id}/ratings`.",
"properties": {
Expand Down Expand Up @@ -15690,6 +15720,138 @@
]
}
},
"/calibrations/{calibration_id}/revisions/{revision_id}/publish": {
"post": {
"operationId": "post_publish_calibration_revision_calibrations__calibration_id__revisions__revision_id__publish_post",
"parameters": [
{
"description": "Target calibration's id.",
"in": "path",
"name": "calibration_id",
"required": true,
"schema": {
"description": "Target calibration's id.",
"format": "uuid",
"title": "Calibration Id",
"type": "string"
}
},
{
"description": "Revision on that calibration to publish.",
"in": "path",
"name": "revision_id",
"required": true,
"schema": {
"description": "Revision on that calibration to publish.",
"format": "uuid",
"title": "Revision Id",
"type": "string"
}
},
{
"in": "header",
"name": "Idempotency-Key",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Idempotency-Key"
}
},
{
"description": "Legacy principal-id header (trust-the-proxy shape). When IDENTITY_PROVIDERS is configured (bearer-auth mode), this header is IGNORED and the verified bearer token from `BearerAuthMiddleware` (Authorization: Bearer) sets the principal. When no IdPs are configured (legacy mode), the application TRUSTS this header (no cryptographic verification) -- production deployments in legacy mode MUST front the API with an auth proxy that strips any client-supplied X-Principal-Id and sets it to the verified principal UUID. Behavior when absent: see Settings.require_authenticated_principal.",
"in": "header",
"name": "X-Principal-Id",
"required": false,
"schema": {
"anyOf": [
{
"format": "uuid",
"type": "string"
},
{
"type": "null"
}
],
"description": "Legacy principal-id header (trust-the-proxy shape). When IDENTITY_PROVIDERS is configured (bearer-auth mode), this header is IGNORED and the verified bearer token from `BearerAuthMiddleware` (Authorization: Bearer) sets the principal. When no IdPs are configured (legacy mode), the application TRUSTS this header (no cryptographic verification) -- production deployments in legacy mode MUST front the API with an auth proxy that strips any client-supplied X-Principal-Id and sets it to the verified principal UUID. Behavior when absent: see Settings.require_authenticated_principal.",
"title": "X-Principal-Id"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PublishCalibrationRevisionRequest"
}
}
},
"required": true
},
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PublishCalibrationRevisionResponse"
}
}
},
"description": "Successful Response"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
},
"description": "Authorize port denied the publish command."
},
"404": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
},
"description": "No calibration or no revision exists with the given ids."
},
"409": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
},
"description": "Publish-time FSM rejection: revision lacks content_hash, or no Active outbound Permit authorizes publishing this artifact to the peer."
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Publish an existing Calibration revision to a peer facility",
"tags": [
"calibration"
]
}
},
"/campaigns": {
"get": {
"operationId": "list_campaigns_campaigns_get",
Expand Down
9 changes: 7 additions & 2 deletions apps/api/src/cora/agent/subscribers/caution_drafter.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,12 +554,17 @@ async def _maybe_sign(self, new_event: NewEvent, *, actor: Actor) -> NewEvent:
"""
if self.signer is None or new_event.event_type not in SIGNED_EVENT_TYPES:
return new_event
signature, kid = await self.signer.sign(
signature, kid, signing_version = await self.signer.sign(
event_type=new_event.event_type,
payload=new_event.payload,
actor_id=actor.id,
)
return replace(new_event, signature=signature, signature_kid=kid)
return replace(
new_event,
signature=signature,
signature_kid=kid,
signature_version=signing_version,
)


def _proposed_target_in_candidates(proposed: Any, valid_target_ids: frozenset[UUID]) -> bool:
Expand Down
9 changes: 7 additions & 2 deletions apps/api/src/cora/agent/subscribers/run_debriefer.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,12 +581,17 @@ async def _maybe_sign(self, new_event: NewEvent, *, actor: Actor) -> NewEvent:
"""
if self.signer is None or new_event.event_type not in SIGNED_EVENT_TYPES:
return new_event
signature, kid = await self.signer.sign(
signature, kid, signing_version = await self.signer.sign(
event_type=new_event.event_type,
payload=new_event.payload,
actor_id=actor.id,
)
return replace(new_event, signature=signature, signature_kid=kid)
return replace(
new_event,
signature=signature,
signature_kid=kid,
signature_version=signing_version,
)


def make_run_debriefer_subscriber(deps: Kernel) -> RunDebrieferSubscriber:
Expand Down
12 changes: 12 additions & 0 deletions apps/api/src/cora/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@
wire_federation,
)
from cora.federation.adapters import PostgresCredentialLookup
from cora.federation.adapters.in_memory_permit_lookup import InMemoryPermitLookup
from cora.federation.adapters.in_memory_publish_port import InMemoryPublishPort
from cora.federation.adapters.in_memory_signature_port import InMemorySignaturePort
from cora.infrastructure.auth.bearer_auth_middleware import BearerAuthMiddleware
from cora.infrastructure.auth.exception_handlers import register_auth_exception_handlers
from cora.infrastructure.config import Settings
Expand Down Expand Up @@ -395,6 +398,15 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
caution_lookup_factory=PostgresCautionLookup,
supply_lookup_factory=PostgresSupplyLookup,
credential_lookup_factory=PostgresCredentialLookup,
# publish_revision slice deps: in-memory adapters
# wired by default until the rule-of-two trigger
# fires per project_federation_port_design.md.
# Production deployments will override these with
# the DSSE+Sigstore + COSE+SCITT wire-tier adapters
# when the wire-tier work lands.
publish_port_factory=InMemoryPublishPort,
signature_port_factory=InMemorySignaturePort,
permit_lookup_factory=InMemoryPermitLookup,
llm_factory=build_llm,
# Pass the create_app-time Settings through so tests
# overriding identity_providers / require_auth / etc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
CalibrationDefined,
CalibrationEvent,
CalibrationRevisionAppended,
CalibrationRevisionPublished,
deserialize_source,
event_type_name,
from_stored,
Expand All @@ -27,10 +28,12 @@
AssertedSource,
Calibration,
CalibrationAlreadyExistsError,
CalibrationCannotPublishRevisionError,
CalibrationDescription,
CalibrationIdentityAlreadyExistsError,
CalibrationNotFoundError,
CalibrationRevision,
CalibrationRevisionNotFoundError,
CalibrationSource,
CalibrationStatus,
ComputedSource,
Expand All @@ -40,6 +43,7 @@
InvalidCalibrationValueError,
InvalidOperatingPointError,
MeasuredSource,
OutboundPermitNotActiveError,
SupersedesRevisionNotFoundError,
reject_empty_against_required,
)
Expand All @@ -49,6 +53,7 @@
"AssertedSource",
"Calibration",
"CalibrationAlreadyExistsError",
"CalibrationCannotPublishRevisionError",
"CalibrationDefined",
"CalibrationDescription",
"CalibrationEvent",
Expand All @@ -57,6 +62,8 @@
"CalibrationNotFoundError",
"CalibrationRevision",
"CalibrationRevisionAppended",
"CalibrationRevisionNotFoundError",
"CalibrationRevisionPublished",
"CalibrationSource",
"CalibrationStatus",
"ComputedSource",
Expand All @@ -66,6 +73,7 @@
"InvalidCalibrationValueError",
"InvalidOperatingPointError",
"MeasuredSource",
"OutboundPermitNotActiveError",
"SupersedesRevisionNotFoundError",
"deserialize_source",
"event_type_name",
Expand Down
Loading