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
7 changes: 7 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,13 @@ and accepted ADRs, not by this glossary.

- **DecisionAudit:** restricted security lineage for authorization decisions;
it is not tenant-visible Learning content.
- **RuntimeCapability:** a server-owned closed designation of one Runtime
operation or required adapter behavior. It is never caller-authored authority,
and availability is checked before Provider, index, or source-content I/O.
- **UNSUPPORTED_CAPABILITY:** the restricted internal refusal category emitted
when a declared RuntimeCapability has no active carrier. It is not a public
response code and carries no token, locator, Provider, Source, or Resource
detail.
- **CandidateRef:** an opaque, content-free retrieval candidate; it is neither a
ContextFragment nor Evidence.
- **AuthorizedProjection:** content projected only after exact authorization for
Expand Down
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ paired Runtime/HTTP gate 进一步证明 cross-Organization、same-Organization
non-owner Control 事务原子撤销 seeded access 并推进 epoch,sealed Acquire 在交付前复核当前
epoch,因此相同 query、CandidateRef 与持久 Fragment 在第一次 post-revoke 请求中返回
零 Evidence,且 Org B 不受影响。该测试能力不等于生产 grant/admin workflow。
Issue #16 已把公开 Runtime wire 固定为 closed `Acquire | Continue | OpenCitation`
union,并在 server-owned `RuntimeCapabilityGate` 激活 M0 拒绝路径:已知但尚无真实
carrier 的 Continue、OpenCitation、federated discovery 与 source-native authorization
在任何 Provider/index/source-content I/O 前分别返回通用 domain-level
`request_not_available` 或 `citation_not_available`;unknown variant 或 caller 自报
capability 仍为通用 422。该激活只证明 deterministic refusal,不表示 continuation、
citation、federated/source-native Provider 或 File publication 已实现。

### 当前 HTTP exact-authorized Evidence tracer

Expand All @@ -110,8 +117,10 @@ projection 与 sealed AuthorizationKernel,返回唯一 exact-authorized Eviden
默认显式返回七个 missing trusted operands,因此不会接受任何生产 credential,也不会
产生可交付 scope。

请求体仅允许 `kind: "acquire"`、`need.query`、可选的有限 `packageBudget` 和可选的
`requestNarrowing`(ref 长度与集合数量均受 active profile 限制),每层 unknown field、重复 JSON key
请求体是 closed `kind` union:Acquire 允许 `need.query`、可选的有限
`packageBudget` 和可选 `requestNarrowing`;Continue 允许 opaque
`continuationToken` 与可选更小的 `packageBudget`;OpenCitation 只允许 opaque
`citationOpenRef`。所有 ref/token 长度与集合数量均受 active profile 限制;每层 unknown field、重复 JSON key
以及重复 singleton security/transport header 都 fail closed;pre-auth body bytes 和
JSON nesting 由 `adapters/http/transport.py` 的 versioned profile 限制。非法
JSON/media type、
Expand All @@ -130,7 +139,10 @@ refs/timestamps 后完全相同;响应不含 Resource 标识、名称、Candid
确定性 authorities 与 real-PostgreSQL seeded composition 只属于测试组合。生产 OAuth/JWT、durable
Principal/Agent grant authority、真实 Source/Resource ACL、通用检索与 continuation
不属于这个已激活 tracer。Policy Epoch V0 也不激活 UI/外部 admin、DecisionAudit、
outbox、cleanup、Continue、OpenCitation、WorkerLease 或 ticket revocation carrier。
outbox、cleanup、真实 Continue/OpenCitation、WorkerLease 或 ticket revocation carrier。
其中 Continue/OpenCitation 的 M0 通用拒绝已经激活,但真实 issuance/redemption carrier
仍保持 future;restricted in-process audit 只保留 `UNSUPPORTED_CAPABILITY` 类别,
durable DecisionAudit 仍为 `NOT_ACTIVE`。

本次公开候选 bundle 包含实现权威、ADR、安全契约、PRD、Tech Spec
与四个公开参考仓的证据基线;经维护者批准并提交后,它们将与实现一同
Expand Down
110 changes: 93 additions & 17 deletions adapters/http/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Annotated, Final, Literal, cast
from uuid import UUID, uuid4

from fastapi import Depends, FastAPI, Header, Request, Response, Security
from fastapi import Body, Depends, FastAPI, Header, Request, Response, Security
from fastapi.exception_handlers import http_exception_handler
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
Expand All @@ -26,11 +26,17 @@
AuthenticationFailureWire,
BlockWire,
BudgetUsageWire,
CitationNotAvailableWire,
ContextPackageWire,
ContinueWire,
CoverageWire,
EvidenceWire,
InvalidRequestWire,
OpenCitationWire,
RequestNotAvailableWire,
ResolutionOutcomeWire,
ResolvedWire,
ResolveWire,
ServiceUnavailableWire,
)
from adapters.http.membership_authority import (
Expand Down Expand Up @@ -60,7 +66,18 @@
MembershipIdentity,
MembershipNotCurrent,
)
from engine.runtime import AuthenticatedInvocation, Runtime
from engine.runtime import (
AuthenticatedInvocation,
CitationNotAvailable,
CitationOpenRef,
ContinuationToken,
Continue,
OpenCitation,
RequestNotAvailable,
ResolutionOutcome,
Runtime,
RuntimeRequest,
)
from engine.runtime.actor import MembershipRejectionAuditReceipt
from engine.runtime.budget import PackageBudgetRequest
from engine.runtime.construction import required_kernel_dependencies
Expand Down Expand Up @@ -100,6 +117,10 @@ class InvalidJsonTransport(Exception):
"""Resolve received malformed JSON or a duplicate object key."""


class InvalidClosedRequest(Exception):
"""Resolve received input outside its closed request surface."""


class DuplicateJsonObjectKey(ValueError):
"""Strict JSON decoding found an ambiguous object member."""

Expand All @@ -113,6 +134,7 @@ def _new_request_id() -> str:


DIRECT_ACQUIRE_PURPOSE: Final = "context.answer"
DIRECT_CITATION_PURPOSE: Final = "citation.open"


def _reject_duplicate_json_object_keys(
Expand Down Expand Up @@ -203,15 +225,18 @@ async def invalid_media_type(
return JSONResponse(INVALID_REQUEST_RESPONSE, status_code=400)

@app.exception_handler(RequestValidationError)
@app.exception_handler(InvalidClosedRequest)
async def invalid_request(
request: Request,
error: RequestValidationError,
error: RequestValidationError | InvalidClosedRequest,
) -> JSONResponse:
status_code = (
400
if any(detail.get("type") == "json_invalid" for detail in error.errors())
if isinstance(error, RequestValidationError)
and any(detail.get("type") == "json_invalid" for detail in error.errors())
else 422
)
del request, error
return JSONResponse(INVALID_REQUEST_RESPONSE, status_code=status_code)

@app.exception_handler(StarletteHTTPException)
Expand All @@ -228,6 +253,8 @@ async def require_closed_json_transport(request: Request) -> None:
request_id_values = request.headers.getlist("x-context-request-id")
if len(content_type_values) != 1 or len(request_id_values) > 1:
raise InvalidRequestMediaType
if request.scope.get("query_string", b""):
raise InvalidClosedRequest
media_type = content_type_values[0].partition(";")[0].strip().casefold()
if media_type != "application/json":
raise InvalidRequestMediaType
Expand Down Expand Up @@ -272,7 +299,7 @@ def health() -> dict[str, str]:
@app.post(
RESOLVE_PATH,
status_code=200,
response_model=ResolvedWire,
response_model=ResolutionOutcomeWire,
response_model_by_alias=True,
dependencies=[Depends(require_closed_json_transport)],
responses={
Expand Down Expand Up @@ -304,7 +331,7 @@ def health() -> dict[str, str]:
},
)
def resolve_context(
body: AcquireWire,
body: Annotated[ResolveWire, Body()],
authentication: Annotated[
VerifiedAuthenticationContext,
Depends(verified_authentication),
Expand All @@ -319,8 +346,9 @@ def resolve_context(
),
] = None,
) -> JSONResponse:
"""Map one authenticated Acquire to the single sealed Runtime entry."""
"""Map one authenticated closed request to the sealed Runtime entry."""

runtime_request = _runtime_request_from_wire(body)
request_id = context_request_id or request_id_factory()
received_at = clock()
try:
Expand Down Expand Up @@ -364,7 +392,7 @@ def resolve_context(
policy_epoch=current_membership_verification.policy_epoch,
principal_ref=current_membership_verification.principal_ref,
agent_version_ref=authentication.agent_version_ref,
purpose=DIRECT_ACQUIRE_PURPOSE,
purpose=_purpose_for_wire(body),
request_id=current_membership_verification.request_id,
authentication_binding_ref=(
current_membership_verification.authentication_binding_ref
Expand All @@ -375,8 +403,15 @@ def resolve_context(
raise TransportAuthenticationFailed from None
with ExitStack() as scope_stack:
try:
scope_authority = (
selected_scope_authority
if selected_runtime._requires_active_scope_authority(
runtime_request
)
else MissingTrustedScopeAuthority()
)
scope_snapshot = scope_stack.enter_context(
selected_scope_authority.current_scope(scope_identity)
scope_authority.current_scope(scope_identity)
)
except (TypeError, ValueError):
raise TrustedAuthorityUnavailable from None
Expand All @@ -401,7 +436,7 @@ def resolve_context(
authentication_binding_ref=(
authentication.authentication_binding_ref
),
trusted_purpose=DIRECT_ACQUIRE_PURPOSE,
trusted_purpose=_purpose_for_wire(body),
received_at=received_at,
trusted_scope_snapshot=scope_snapshot,
)
Expand All @@ -412,7 +447,7 @@ def resolve_context(
if invocation_observer is not None:
invocation_observer(invocation)
delivery_context = _construct_direct_delivery_context(
purpose=DIRECT_ACQUIRE_PURPOSE,
purpose=_purpose_for_wire(body),
authenticated_application_ref=(
authentication.authenticated_application_ref
),
Expand All @@ -421,14 +456,13 @@ def resolve_context(
),
established_at=invocation.received_at,
)
request = _acquire_from_wire(body)
outcome = selected_runtime.resolve(
invocation,
delivery_context,
request,
runtime_request,
)
response = _resolved_to_wire(outcome)
if resolution_observer is not None:
response = _resolution_outcome_to_wire(outcome)
if type(outcome) is Resolved and resolution_observer is not None:
resolution_observer(outcome)
return JSONResponse(
response.model_dump(
Expand Down Expand Up @@ -460,7 +494,9 @@ def resolve_context(
return app


def _acquire_from_wire(body: AcquireWire) -> Acquire:
def _package_budget_from_wire(
body: AcquireWire | ContinueWire,
) -> PackageBudgetRequest | None:
package_budget = None
if body.packageBudget is not None:
package_budget = PackageBudgetRequest(
Expand All @@ -469,6 +505,10 @@ def _acquire_from_wire(body: AcquireWire) -> Acquire:
max_cost_microunits=body.packageBudget.maxCostMicrounits,
max_elapsed_ms=body.packageBudget.maxElapsedMs,
)
return package_budget


def _acquire_from_wire(body: AcquireWire) -> Acquire:
narrowing = None
if body.requestNarrowing is not None:
narrowing = RequestNarrowing(
Expand All @@ -477,11 +517,47 @@ def _acquire_from_wire(body: AcquireWire) -> Acquire:
)
return Acquire(
need=ContextNeed(query=body.need.query),
package_budget=package_budget,
package_budget=_package_budget_from_wire(body),
narrowing=narrowing,
)


def _purpose_for_wire(body: ResolveWire) -> str:
return (
DIRECT_CITATION_PURPOSE
if type(body) is OpenCitationWire
else DIRECT_ACQUIRE_PURPOSE
)


def _runtime_request_from_wire(body: ResolveWire) -> RuntimeRequest:
if type(body) is AcquireWire:
return _acquire_from_wire(body)
if type(body) is ContinueWire:
return Continue(
continuation_token=ContinuationToken(body.continuationToken),
package_budget=_package_budget_from_wire(body),
)
if type(body) is OpenCitationWire:
return OpenCitation(citation_open_ref=CitationOpenRef(body.citationOpenRef))
raise TypeError("wire body must be one closed resolve variant")


def _resolution_outcome_to_wire(
outcome: ResolutionOutcome,
) -> ResolvedWire | RequestNotAvailableWire | CitationNotAvailableWire:
if type(outcome) is Resolved:
return _resolved_to_wire(outcome)
if type(outcome) is RequestNotAvailable:
return RequestNotAvailableWire(
kind=outcome.kind,
retryable=outcome.retryable,
)
if type(outcome) is CitationNotAvailable:
return CitationNotAvailableWire(kind=outcome.kind)
raise TypeError("Runtime returned an unknown resolution outcome")


def _resolved_to_wire(outcome: Resolved) -> ResolvedWire:
package = outcome.package
blocks = tuple(
Expand Down
55 changes: 54 additions & 1 deletion adapters/http/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
DECISION_REF_PATTERN,
MAX_NARROWING_REF_LENGTH,
MAX_NARROWING_REFS,
MAX_OPAQUE_CAPABILITY_LENGTH,
ORGANIZATION_PACKAGE_REF_PATTERN,
)

Expand Down Expand Up @@ -115,14 +116,47 @@ def require_nonempty_unique_sets(self) -> Self:


class AcquireWire(ClosedWireModel):
"""Only the closed untrusted Acquire variant currently activated."""
"""Closed untrusted Acquire variant."""

kind: Literal["acquire"]
need: ContextNeedWire
packageBudget: PackageBudgetWire | None = None
requestNarrowing: RequestNarrowingWire | None = None


OpaqueCapabilityInput = Annotated[
str,
Field(
strict=True,
min_length=1,
max_length=MAX_OPAQUE_CAPABILITY_LENGTH,
pattern=r"^\S+$",
repr=False,
),
]


class ContinueWire(ClosedWireModel):
"""Closed known continuation variant; its carrier is unavailable at M0."""

kind: Literal["continue"]
continuationToken: OpaqueCapabilityInput
packageBudget: PackageBudgetWire | None = None


class OpenCitationWire(ClosedWireModel):
"""Closed known citation variant; its locator carries no authority."""

kind: Literal["open_citation"]
citationOpenRef: OpaqueCapabilityInput


type ResolveWire = Annotated[
AcquireWire | ContinueWire | OpenCitationWire,
Field(discriminator="kind"),
]


class BudgetUsageWire(ClosedWireModel):
"""Actual resources consumed by this Package."""

Expand Down Expand Up @@ -240,6 +274,25 @@ class ResolvedWire(ClosedWireModel):
package: ContextPackageWire


class RequestNotAvailableWire(ClosedWireModel):
"""Caller-safe outcome for an unavailable known request."""

kind: Literal["request_not_available"]
retryable: Literal[False]


class CitationNotAvailableWire(ClosedWireModel):
"""Caller-safe outcome for an unavailable citation open."""

kind: Literal["citation_not_available"]


type ResolutionOutcomeWire = Annotated[
ResolvedWire | RequestNotAvailableWire | CitationNotAvailableWire,
Field(discriminator="kind"),
]


class AuthenticationFailureWire(ClosedWireModel):
"""Closed public response for every transport authentication rejection."""

Expand Down
Loading
Loading