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
11 changes: 11 additions & 0 deletions .github/workflows/ci-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ jobs:
- name: Check governance traceability contract (template + indexes + TODO linkage)
run: ./scripts/ci/check_governance_traceability.sh

failure-ownership-map:
name: failure-ownership-map

runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Verify failure-test ownership mapping
run: python scripts/ci/check_test_failure_ownership.py

governance-intent-gate:
name: governance-intent-gate
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
Expand Down
200 changes: 200 additions & 0 deletions dare_framework/checkpoint/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
"""Supported default checkpoint implementations and contributors.

This module keeps legacy checkpoint symbols importable after checkpoint internals
were consolidated into ``dare_framework.checkpoint.kernel``.
"""

from __future__ import annotations

from dataclasses import asdict
from typing import Any
from uuid import uuid4

from dare_framework.context.types import Message

STM = "stm"
SESSION_STATE = "session_state"
SESSION_CONTEXT = "session_context"
WORKSPACE_FILES = "workspace_files"


class MemoryCheckpointStore:
"""In-memory checkpoint payload store kept for facade compatibility."""

def __init__(self) -> None:
self._store: dict[str, dict[str, Any]] = {}

def put(self, checkpoint_id: str, payload: dict[str, Any]) -> None:
self._store[checkpoint_id] = dict(payload)

def get(self, checkpoint_id: str) -> dict[str, Any] | None:
if checkpoint_id not in self._store:
return None
return dict(self._store[checkpoint_id])

def delete(self, checkpoint_id: str) -> bool:
if checkpoint_id in self._store:
del self._store[checkpoint_id]
return True
return False


def _scope_keys(scope: Any, method_name: str) -> list[str]:
method = getattr(scope, method_name, None)
if callable(method):
values = method()
if isinstance(values, (list, tuple, set)):
return [str(v) for v in values]
if isinstance(scope, (list, tuple, set)):
return [str(v) for v in scope]
return []


class DefaultCheckpointSaveRestore:
"""Legacy save/restore coordinator over contributor payload components."""

def __init__(self, store: MemoryCheckpointStore, contributors: list[Any]) -> None:
self._store = store
self._contributors = {
str(c.component_key): c
for c in contributors
if getattr(c, "component_key", None) is not None
}

def save(self, scope: Any, ctx: Any) -> str:
payload: dict[str, Any] = {}
for key in _scope_keys(scope, "keys_for_save"):
contributor = self._contributors.get(key)
if contributor is None:
continue
payload[key] = contributor.serialize(ctx)
checkpoint_id = uuid4().hex[:16]
self._store.put(checkpoint_id, payload)
return checkpoint_id

def restore(self, checkpoint_id: str, scope: Any, ctx: Any) -> None:
payload = self._store.get(checkpoint_id)
if payload is None:
raise LookupError(f"Checkpoint not found: {checkpoint_id!r}")
for key in _scope_keys(scope, "keys_for_restore"):
if key not in payload:
continue
contributor = self._contributors.get(key)
if contributor is None:
continue
contributor.deserialize_and_apply(payload[key], ctx)


class StmContributor:
"""Serialize/restore short-term memory messages."""

component_key = STM

def serialize(self, ctx: Any) -> list[dict[str, Any]]:
context = getattr(ctx, "context", None)
if context is None:
return []
messages = context.stm_get()
return [
{
"role": m.role,
"content": m.content,
"name": m.name,
"metadata": dict(getattr(m, "metadata", {}) or {}),
}
for m in messages
]

def deserialize_and_apply(self, payload: list[Any], ctx: Any) -> None:
context = getattr(ctx, "context", None)
if context is None:
return
context.stm_clear()
for item in payload or []:
if not isinstance(item, dict):
continue
context.stm_add(
Message(
role=item.get("role", "user"),
content=item.get("content", ""),
name=item.get("name"),
metadata=dict(item.get("metadata") or {}),
)
)


class SessionStateContributor:
"""Serialize/restore minimal session-state fields."""

component_key = SESSION_STATE

def serialize(self, ctx: Any) -> dict[str, Any] | None:
state = getattr(ctx, "session_state", None)
if state is None:
return None
try:
return asdict(state)
except Exception:
return {
"current_milestone_idx": getattr(state, "current_milestone_idx", None),
"task_id": getattr(state, "task_id", None),
"run_id": getattr(state, "run_id", None),
}

def deserialize_and_apply(self, payload: Any, ctx: Any) -> None:
state = getattr(ctx, "session_state", None)
if state is None or not isinstance(payload, dict):
return
if "current_milestone_idx" in payload and hasattr(state, "current_milestone_idx"):
state.current_milestone_idx = payload["current_milestone_idx"]


class SessionContextContributor:
"""Serialize session context for audit trails (restore intentionally no-op)."""

component_key = SESSION_CONTEXT

def serialize(self, ctx: Any) -> dict[str, Any] | None:
session_context = getattr(ctx, "session_context", None)
if session_context is None:
return None
try:
serialized = asdict(session_context)
except Exception:
return {
"session_id": getattr(session_context, "session_id", None),
"task_id": getattr(session_context, "task_id", None),
}
config_value = serialized.get("config")
if config_value is not None and not isinstance(config_value, dict):
try:
serialized["config"] = asdict(config_value)
except Exception:
serialized["config"] = None
return serialized

def deserialize_and_apply(self, payload: Any, ctx: Any) -> None:
_ = (payload, ctx)
# SessionContext is construction-time state; legacy restore path keeps this as no-op.


class WorkspaceGitContributor:
"""Compatibility no-op contributor after workspace-git checkpoint removal."""

component_key = WORKSPACE_FILES

def serialize(self, ctx: Any) -> dict[str, Any]:
_ = ctx
return {}

def deserialize_and_apply(self, payload: Any, ctx: Any) -> None:
_ = (payload, ctx)

__all__ = [
"MemoryCheckpointStore",
"DefaultCheckpointSaveRestore",
"StmContributor",
"WorkspaceGitContributor",
"SessionStateContributor",
"SessionContextContributor",
]
6 changes: 3 additions & 3 deletions dare_framework/embedding/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""embedding domain facade."""

from dare_framework.embedding.interfaces import IEmbeddingAdapter
from dare_framework.embedding.types import EmbeddingOptions, EmbeddingResult
from dare_framework.embedding._internal.openai_embedding import OpenAIEmbeddingAdapter
from dare_framework.embedding.interfaces import IEmbeddingAdapter
from dare_framework.embedding.types import EmbeddingOptions, EmbeddingResult
from dare_framework.embedding.defaults import OpenAIEmbeddingAdapter

__all__ = [
"IEmbeddingAdapter",
Expand Down
5 changes: 5 additions & 0 deletions dare_framework/embedding/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Supported default embedding implementations."""

from dare_framework.embedding._internal.openai_embedding import OpenAIEmbeddingAdapter

__all__ = ["OpenAIEmbeddingAdapter"]
2 changes: 1 addition & 1 deletion dare_framework/event/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""event domain facade."""

from dare_framework.event._internal.sqlite_event_log import DefaultEventLog, SQLiteEventLog
from dare_framework.event.defaults import DefaultEventLog, SQLiteEventLog
from dare_framework.event.kernel import IEventLog
from dare_framework.event.types import Event, RuntimeSnapshot

Expand Down
5 changes: 5 additions & 0 deletions dare_framework/event/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Supported default event-log implementations."""

from dare_framework.event._internal.sqlite_event_log import DefaultEventLog, SQLiteEventLog

__all__ = ["SQLiteEventLog", "DefaultEventLog"]
2 changes: 1 addition & 1 deletion dare_framework/hook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dare_framework.hook.interfaces import IHookManager
from dare_framework.hook.kernel import IExtensionPoint, IHook, HookFn
from dare_framework.hook._internal.hook_extension_point import HookExtensionPoint
from dare_framework.hook.defaults import HookExtensionPoint
from dare_framework.hook.types import HookDecision, HookEnvelope, HookPhase, HookResult

__all__ = [
Expand Down
5 changes: 5 additions & 0 deletions dare_framework/hook/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Supported default extension-point implementation."""

from dare_framework.hook._internal.hook_extension_point import HookExtensionPoint

__all__ = ["HookExtensionPoint"]
3 changes: 1 addition & 2 deletions dare_framework/plan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
ValidatedStep,
VerifyResult,
)
from dare_framework.plan._internal.default_planner import DefaultPlanner
from dare_framework.plan._internal.default_remediator import DefaultRemediator
from dare_framework.plan.defaults import DefaultPlanner, DefaultRemediator

__all__ = [
# Interfaces
Expand Down
6 changes: 6 additions & 0 deletions dare_framework/plan/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Supported default planner/remediator implementations."""

from dare_framework.plan._internal.default_planner import DefaultPlanner
from dare_framework.plan._internal.default_remediator import DefaultRemediator

__all__ = ["DefaultPlanner", "DefaultRemediator"]
7 changes: 5 additions & 2 deletions dare_framework/security/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"""Security domain facade."""

from dare_framework.security._internal.default_security_boundary import DefaultSecurityBoundary
from dare_framework.security.errors import (
SECURITY_APPROVAL_MANAGER_MISSING,
SECURITY_POLICY_CHECK_FAILED,
SECURITY_POLICY_DENIED,
SECURITY_TRUST_DERIVATION_FAILED,
SecurityBoundaryError,
)
from dare_framework.security.impl import NoOpSecurityBoundary, PolicySecurityBoundary
from dare_framework.security.impl import (
DefaultSecurityBoundary,
NoOpSecurityBoundary,
PolicySecurityBoundary,
Comment on lines +10 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve DefaultSecurityBoundary semantics in facade export

Importing DefaultSecurityBoundary from dare_framework.security.impl changes the behavior exposed by the public facade: impl aliases DefaultSecurityBoundary to PolicySecurityBoundary, which is stricter than the previously exported _internal default and can return APPROVE_REQUIRED for non-idempotent tool calls. That means callers relying on from dare_framework.security import DefaultSecurityBoundary (including default agent wiring) now get different policy outcomes without opting into a new boundary, so this refactor silently changes runtime behavior rather than only import structure.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e2a2475.

DefaultSecurityBoundary is now routed to the legacy permissive implementation again (via dare_framework.security.impl.default_security_boundary) so from dare_framework.security import DefaultSecurityBoundary keeps historical ALLOW behavior instead of inheriting PolicySecurityBoundary approval gating.

Added regression coverage in tests/unit/test_security_boundary.py::test_default_security_boundary_remains_permissive_for_high_risk.

)
from dare_framework.security.kernel import ISecurityBoundary
from dare_framework.security.types import PolicyDecision, RiskLevel, SandboxSpec, TrustedInput

Expand Down
7 changes: 5 additions & 2 deletions dare_framework/security/impl/default_security_boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import inspect
from typing import Any, Callable, Iterable

from dare_framework.security._internal.default_security_boundary import (
DefaultSecurityBoundary as LegacyDefaultSecurityBoundary,
)
from dare_framework.security.errors import (
SECURITY_TRUST_DERIVATION_FAILED,
SecurityBoundaryError,
Expand Down Expand Up @@ -259,8 +262,8 @@ async def execute_safe(
return await _execute_callable(fn)


# Backward-compatible alias for legacy references.
DefaultSecurityBoundary = PolicySecurityBoundary
# Preserve historical facade behavior where `DefaultSecurityBoundary` is permissive.
DefaultSecurityBoundary = LegacyDefaultSecurityBoundary


__all__ = [
Expand Down
2 changes: 1 addition & 1 deletion dare_framework/transport/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
ActionHandlerDispatcher,
ResourceAction,
)
from dare_framework.transport._internal import (
from dare_framework.transport.adapters import (
DefaultAgentChannel,
DirectClientChannel,
StdioClientChannel,
Expand Down
15 changes: 15 additions & 0 deletions dare_framework/transport/adapters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Supported default transport channel adapters."""

from dare_framework.transport._internal import (
DefaultAgentChannel,
DirectClientChannel,
StdioClientChannel,
WebSocketClientChannel,
)

__all__ = [
"DefaultAgentChannel",
"DirectClientChannel",
"StdioClientChannel",
"WebSocketClientChannel",
]
5 changes: 5 additions & 0 deletions docs/design/Framework_MinSurface_Review.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,8 @@ MCP adapters, etc.
- Tight coupling to concrete classes prevents safe refactors.
- External users rely on internal DTOs that must then be supported forever.
- Increased likelihood of breaking changes with each internal improvement.

## 5. Implementation Note (2026-03-04)
- T0-4 facade compliance batch moved public-domain `__init__.py` imports away from direct `._internal` references into explicit public export layers (`defaults.py`, `impl`, `adapters`).
- The following facades now import only public modules directly: `checkpoint`, `embedding`, `event`, `hook`, `plan`, `security`, `transport`.
- A regression rule was added in `tests/unit/test_package_initializers_facade_pattern.py` to block future direct `._internal` imports from public facades.
9 changes: 5 additions & 4 deletions docs/features/enhance-doc-governance-traceability.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Unify documentation management structure, lifecycle governance, and SOP-to-skill
### Results
- `../../.venv/bin/python -m pytest -q tests/unit/test_governance_intent_gate.py tests/unit/test_governance_traceability_gate.py tests/unit/test_governance_evidence_truth_gate.py`: passed (`56 passed`) after adding intent-gate regression coverage for docs-only skip behavior, implementation-path gating, governed feature-doc requirement, intent PR link extraction, merged-state enforcement, and draft-status exclusion while preserving existing traceability/evidence truth coverage.
- `./scripts/ci/check_governance_traceability.sh`: passed against the real repository tree after tightening active/archive index membership to canonical sections, rejecting index entries outside the correct feature-doc path family, excluding `docs/features/README.md` and `docs/features/archive/README.md` from valid feature-entry targets, requiring explicit checkpoint-to-skill pair rows in Section 7, and resolving pilot `todo_ids` only through Claim Ledger records, including same-claim scope ranges where the TODO id is only implied by the claim range.
- Closeout sync update: traceability checks now also cover archived-feature frontmatter validation and master/detail claim consistency against OpenSpec `tasks.md` completeness for executable claims.
- `./scripts/ci/check_governance_evidence_truth.sh`: passed, confirming the new traceability assets do not break the existing evidence-first contract.
- `GOVERNANCE_INTENT_GATE_CHANGED_FILES=$'client/main.py\ndocs/features/enhance-doc-governance-traceability.md' GOVERNANCE_INTENT_GATE_PR_STATE_FIXTURE='zts212653/Deterministic-Agent-Runtime-Engine#126=merged' ./scripts/ci/check_governance_intent_gate.sh`: passed, confirming implementation-path changes are now hard-blocked unless governed feature docs carry a merged `Intent PR`.
- `openspec validate enhance-doc-governance-traceability --type change --strict --json --no-interactive`: passed (`1/1` change valid, `0` issues).
Expand Down Expand Up @@ -74,17 +75,17 @@ Unify documentation management structure, lifecycle governance, and SOP-to-skill

### Structured Review Report
- Changed Module Boundaries / Public API: governance scope only; no new runtime public API added.
- New State: adds intent gate policy state (`implementation-path diff -> governed feature doc -> merged intent PR`) with one new gate script and one new regression test suite.
- New State: adds intent gate policy state (`implementation-path diff -> governed feature doc -> merged intent PR`) and extends traceability closeout checks for claim-to-task and master/detail consistency.
- Concurrency / Timeout / Retry: no concurrency change; gate runs are single-process document scans with deterministic rerun behavior after fixes.
- Side Effects and Idempotency: side effects are limited to CI/log output; repeated runs are idempotent against unchanged docs.
- Coverage and Residual Risk: intent-merge gating plus existing template/index/skill-mapping/TODO-linkage checks are covered; residual risk is that broader frontmatter enforcement across `docs/guides/**` and `docs/design/**` is still pending.
- Coverage and Residual Risk: intent-merge gating plus template/index/skill-mapping/TODO-linkage checks are covered; residual risk remains broader frontmatter enforcement expansion across additional governance doc families.

### Behavior Verification
- Happy path: when implementation files change, CI now requires a same-PR governed feature-doc update and validates that the referenced `Intent PR` is already merged before allowing merge.
- Error/fallback path: the intent gate fails deterministically when implementation changes omit governed feature docs, when feature docs miss `Intent PR` links, or when the referenced intent PR state is not `merged`.

### Risks and Rollback
- Risk: `3.2-3.4` are still open, so the governance suite still does not enforce full frontmatter coverage for every governance-tracked doc family or full master-TODO/task completeness.
- Risk: promoting `p0-gate` to a required check still depends on repository-admin permissions outside this change scope.
- Risk: active/archive indexes are now explicit manual ledgers, so closeout changes that forget to update them will fail the new gate.
- Risk: local runs without `GITHUB_TOKEN` need PR-state fixture or CI context for merged-state lookup.
- Rollback: remove `governance-intent-gate` from `.github/workflows/ci-gate.yml` and revert `scripts/ci/check_governance_intent_gate.sh` if false positives block delivery.
Expand All @@ -110,4 +111,4 @@ Unify documentation management structure, lifecycle governance, and SOP-to-skill
- `https://github.com/zts212653/Deterministic-Agent-Runtime-Engine/pull/175#discussion_r2881421738`

## Next Milestone
Implement the remaining CI depth tasks: widen frontmatter enforcement beyond feature docs and add machine-checkable TODO/task and master-TODO/change-slice consistency checks before closeout.
Promote `p0-gate` to branch protection required check and then archive this change entry in the next closeout pass.
Loading