agentforge-core 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/PKG-INFO +1 -1
  2. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/pyproject.toml +1 -1
  3. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/__init__.py +9 -1
  4. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/config/schema.py +33 -0
  5. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/__init__.py +3 -0
  6. agentforge_core-0.4.0/src/agentforge_core/contracts/identity.py +88 -0
  7. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/testing/__init__.py +2 -0
  8. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/testing/conformance.py +72 -0
  9. agentforge_core-0.4.0/src/agentforge_core/values/auth.py +36 -0
  10. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/retrieval.py +8 -0
  11. agentforge_core-0.3.0/src/agentforge_core/values/auth.py +0 -20
  12. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/.gitignore +0 -0
  13. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/LICENSE +0 -0
  14. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/README.md +0 -0
  15. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/_bm25.py +0 -0
  16. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/config/__init__.py +0 -0
  17. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/config/app_sections.py +0 -0
  18. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/config/loader.py +0 -0
  19. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/config/module_schemas.py +0 -0
  20. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/auth.py +0 -0
  21. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/chat.py +0 -0
  22. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/embedding.py +0 -0
  23. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/evaluator.py +0 -0
  24. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/finding.py +0 -0
  25. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/graph_store.py +0 -0
  26. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/guardrails.py +0 -0
  27. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/llm.py +0 -0
  28. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/memory.py +0 -0
  29. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/migrator.py +0 -0
  30. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/protocol_bridge.py +0 -0
  31. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/renderer.py +0 -0
  32. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/reranker.py +0 -0
  33. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/strategy.py +0 -0
  34. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/task.py +0 -0
  35. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/tool.py +0 -0
  36. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/contracts/vector_store.py +0 -0
  37. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/migrations/__init__.py +0 -0
  38. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/migrations/discover.py +0 -0
  39. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/migrations/template.py +0 -0
  40. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/observability/__init__.py +0 -0
  41. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/observability/tracing.py +0 -0
  42. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/production/__init__.py +0 -0
  43. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/production/budget.py +0 -0
  44. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/production/exceptions.py +0 -0
  45. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/production/fallback.py +0 -0
  46. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/production/log_filter.py +0 -0
  47. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/production/log_format.py +0 -0
  48. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/production/run_context.py +0 -0
  49. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/py.typed +0 -0
  50. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/resolver/__init__.py +0 -0
  51. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/resolver/discover.py +0 -0
  52. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/resolver/resolve.py +0 -0
  53. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/__init__.py +0 -0
  54. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/chat.py +0 -0
  55. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/claim.py +0 -0
  56. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/graph.py +0 -0
  57. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/guardrails.py +0 -0
  58. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/manifest.py +0 -0
  59. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/messages.py +0 -0
  60. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/module.py +0 -0
  61. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/pipeline.py +0 -0
  62. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/state.py +0 -0
  63. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/src/agentforge_core/values/vector.py +0 -0
  64. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/conftest.py +0 -0
  65. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/integration/test_app_sections_real_discovery.py +0 -0
  66. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/integration/test_config_imports_cli.py +0 -0
  67. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/.gitkeep +0 -0
  68. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_auth_values.py +0 -0
  69. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_bm25.py +0 -0
  70. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_budget.py +0 -0
  71. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_capability_extensions.py +0 -0
  72. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_chat_conformance.py +0 -0
  73. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_chat_values.py +0 -0
  74. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_claim.py +0 -0
  75. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_config_app_passthrough.py +0 -0
  76. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_config_app_sections.py +0 -0
  77. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_config_chat.py +0 -0
  78. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_config_feat012.py +0 -0
  79. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_config_imports.py +0 -0
  80. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_config_module_schemas.py +0 -0
  81. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_config_pipeline.py +0 -0
  82. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_config_retrieval.py +0 -0
  83. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_contracts_auth.py +0 -0
  84. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_contracts_chat.py +0 -0
  85. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_contracts_evaluator.py +0 -0
  86. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_contracts_finding.py +0 -0
  87. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_contracts_llm.py +0 -0
  88. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_contracts_memory.py +0 -0
  89. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_contracts_strategy.py +0 -0
  90. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_contracts_task.py +0 -0
  91. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_contracts_tool.py +0 -0
  92. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_embedding_client.py +0 -0
  93. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_exceptions.py +0 -0
  94. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_fallback_chain.py +0 -0
  95. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_graph_store_contract.py +0 -0
  96. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_guardrails_config.py +0 -0
  97. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_guardrails_contracts.py +0 -0
  98. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_log_filter.py +0 -0
  99. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_log_format.py +0 -0
  100. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_messages.py +0 -0
  101. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_migrations.py +0 -0
  102. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_pipeline_values.py +0 -0
  103. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_provider_errors.py +0 -0
  104. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_reranker_contract.py +0 -0
  105. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_resolver.py +0 -0
  106. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_resolver_discovery.py +0 -0
  107. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_run_context.py +0 -0
  108. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_state.py +0 -0
  109. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_strategy_conformance.py +0 -0
  110. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_strategy_stream_default.py +0 -0
  111. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_task_conformance.py +0 -0
  112. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_values_graph.py +0 -0
  113. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_values_vector.py +0 -0
  114. {agentforge_core-0.3.0 → agentforge_core-0.4.0}/tests/unit/test_vector_store_contract.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentforge-core
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: AgentForge core — stable contracts (ABCs, value types) for the agentic framework
5
5
  Project-URL: Homepage, https://github.com/Scaffoldic/agentforge-py
6
6
  Project-URL: Repository, https://github.com/Scaffoldic/agentforge-py
@@ -9,7 +9,7 @@
9
9
 
10
10
  [project]
11
11
  name = "agentforge-core"
12
- version = "0.3.0"
12
+ version = "0.4.0"
13
13
  description = "AgentForge core — stable contracts (ABCs, value types) for the agentic framework"
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.13"
@@ -11,6 +11,11 @@ the public surface for granular imports.
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
+ # Version is sourced from the installed distribution metadata so it can
15
+ # never drift from pyproject.toml (bug-024).
16
+ from importlib.metadata import PackageNotFoundError as _PkgNotFound
17
+ from importlib.metadata import version as _dist_version
18
+
14
19
  from agentforge_core.config import (
15
20
  AgentConfig,
16
21
  AgentForgeConfig,
@@ -126,7 +131,10 @@ from agentforge_core.values import (
126
131
  VectorMatch,
127
132
  )
128
133
 
129
- __version__ = "0.2.3"
134
+ try:
135
+ __version__ = _dist_version("agentforge-core")
136
+ except _PkgNotFound: # pragma: no cover - source tree without installed metadata
137
+ __version__ = "0.0.0+unknown"
130
138
 
131
139
  __all__ = [
132
140
  "OBSERVABILITY_SCOPE_NAME",
@@ -164,6 +164,10 @@ class GraphExpansionConfig(BaseModel):
164
164
  """Edge-type filter. YAML lists deserialize to `list[str]`;
165
165
  converted to a tuple by `build_retriever_from_config` before
166
166
  constructing the `GraphExpansion` value."""
167
+ direction: Literal["out", "in", "any"] = "any"
168
+ """Edge direction to follow during expansion (enh-005): `out`
169
+ (callees / what-X-cites), `in` (callers / who-cites-X), or `any`
170
+ (default — the original undirected `traverse` behaviour)."""
167
171
  text_property: str = "text"
168
172
  decay: float = Field(default=0.5, gt=0.0, le=1.0)
169
173
 
@@ -456,6 +460,34 @@ class OutputConfig(BaseModel):
456
460
  thresholds: dict[str, list[str]] = Field(default_factory=dict)
457
461
 
458
462
 
463
+ class IdentityConfig(BaseModel):
464
+ """`governance.identity:` — the agent's identity (feat-029).
465
+
466
+ `provider` resolves against the `identity_providers` entry-point
467
+ category; `name` / `owner` / `attributes` describe the principal the
468
+ framework issues for this agent; `config` is provider-specific (issuer
469
+ URL, trust domain, role_arn, …)."""
470
+
471
+ model_config = ConfigDict(strict=True, extra="forbid")
472
+
473
+ provider: str = "local"
474
+ name: str | None = None
475
+ owner: str | None = None
476
+ attributes: dict[str, str] = Field(default_factory=dict)
477
+ config: dict[str, Any] = Field(default_factory=dict)
478
+
479
+
480
+ class GovernanceConfig(BaseModel):
481
+ """`governance:` — the governance spine (feat-029+).
482
+
483
+ Identity ships first; `registry` / `policy` / `audit` sub-blocks are
484
+ added as those pillars land."""
485
+
486
+ model_config = ConfigDict(strict=True, extra="forbid")
487
+
488
+ identity: IdentityConfig | None = None
489
+
490
+
459
491
  class AgentForgeConfig(BaseModel):
460
492
  """Root model — `agentforge.yaml` shape.
461
493
 
@@ -472,6 +504,7 @@ class AgentForgeConfig(BaseModel):
472
504
  logging: LoggingConfig = Field(default_factory=LoggingConfig)
473
505
  output: OutputConfig = Field(default_factory=OutputConfig)
474
506
  guardrail_policy: GuardrailPolicy = Field(default_factory=GuardrailPolicy)
507
+ governance: GovernanceConfig = Field(default_factory=GovernanceConfig)
475
508
  app: dict[str, Any] = Field(default_factory=dict)
476
509
  """Reserved namespace for **application** config (enh-002, feat-026
477
510
  Phase 1). The framework accepts this subtree but does not interpret
@@ -13,6 +13,7 @@ from agentforge_core.contracts.embedding import EmbeddingClient
13
13
  from agentforge_core.contracts.evaluator import EvalResult, Evaluator
14
14
  from agentforge_core.contracts.finding import Finding
15
15
  from agentforge_core.contracts.graph_store import GraphStore
16
+ from agentforge_core.contracts.identity import IdentityError, IdentityProvider
16
17
  from agentforge_core.contracts.llm import LLMClient
17
18
  from agentforge_core.contracts.memory import MemoryStore
18
19
  from agentforge_core.contracts.migrator import (
@@ -38,6 +39,8 @@ __all__ = [
38
39
  "FindingRenderer",
39
40
  "GraphStore",
40
41
  "HistoryTruncationStrategy",
42
+ "IdentityError",
43
+ "IdentityProvider",
41
44
  "LLMClient",
42
45
  "MemoryStore",
43
46
  "Migration",
@@ -0,0 +1,88 @@
1
+ """`IdentityProvider` — locked governance-identity contract (feat-029).
2
+
3
+ Every agent (and the tools / services it speaks to) gets a stable, portable
4
+ `Principal` so every action has a name. A provider:
5
+
6
+ - **issues** a principal for an agent from its declared name + owner,
7
+ - **resolves** a principal by id,
8
+ - **verifies** an inbound credential into a principal (prove who's calling),
9
+ - mints an outbound **credential** for a principal (prove who we are),
10
+ - **rotates** a principal's credential.
11
+
12
+ Per ADR-0007 the methods on this ABC are locked once the feature ships;
13
+ adding a method is a major-version bump. Optional behaviour goes behind
14
+ `capabilities()` / `supports()`.
15
+
16
+ The contract is vendor-neutral by construction: the `local` driver is
17
+ self-contained and offline (deterministic, no network); OIDC / SPIFFE /
18
+ cloud-IAM drivers map onto the same surface without the framework taking a
19
+ hard dependency on any of them.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from abc import ABC, abstractmethod
25
+ from collections.abc import Mapping
26
+
27
+ from agentforge_core.values.auth import Principal
28
+
29
+
30
+ class IdentityError(ValueError):
31
+ """Raised when a credential cannot be verified into a principal, or a
32
+ principal cannot be resolved for an operation that requires one."""
33
+
34
+
35
+ class IdentityProvider(ABC):
36
+ """Issues, resolves, verifies, and rotates `Principal` identities."""
37
+
38
+ @abstractmethod
39
+ async def issue(
40
+ self,
41
+ *,
42
+ name: str,
43
+ owner: str,
44
+ attributes: Mapping[str, str] | None = None,
45
+ ) -> Principal:
46
+ """Mint (or return the existing) principal for an agent.
47
+
48
+ `name` is the agent's logical name; `owner` the accountable team /
49
+ human; `attributes` free-form string tags (env, region, …). The
50
+ returned `Principal.id` is stable across calls for the same
51
+ `name` (idempotent issue)."""
52
+
53
+ @abstractmethod
54
+ async def resolve(self, principal_id: str) -> Principal | None:
55
+ """Return the principal with this id, or ``None`` if unknown."""
56
+
57
+ @abstractmethod
58
+ async def verify(self, token: str) -> Principal:
59
+ """Verify an inbound credential and return its principal.
60
+
61
+ Raises:
62
+ IdentityError: the token is missing, malformed, expired, or
63
+ not issued by this provider.
64
+ """
65
+
66
+ @abstractmethod
67
+ async def credential(self, principal: Principal) -> str:
68
+ """Mint an outbound credential (token / assertion) that another
69
+ party can `verify()` back into `principal`."""
70
+
71
+ @abstractmethod
72
+ async def rotate(self, principal_id: str) -> Principal:
73
+ """Rotate the principal's signing material / credential. The
74
+ principal id is unchanged; previously minted credentials stop
75
+ verifying.
76
+
77
+ Raises:
78
+ IdentityError: `principal_id` is unknown.
79
+ """
80
+
81
+ def capabilities(self) -> set[str]:
82
+ """Optional capabilities this provider honours, from a closed
83
+ vocabulary (e.g. ``"rotation"``, ``"oidc"``, ``"spiffe"``).
84
+ Default: none."""
85
+ return set()
86
+
87
+ def supports(self, capability: str) -> bool:
88
+ return capability in self.capabilities()
@@ -17,6 +17,7 @@ from agentforge_core.testing.conformance import (
17
17
  run_embedding_conformance,
18
18
  run_graph_conformance,
19
19
  run_hybrid_search_conformance,
20
+ run_identity_conformance,
20
21
  run_input_validator_conformance,
21
22
  run_memory_conformance,
22
23
  run_output_validator_conformance,
@@ -33,6 +34,7 @@ __all__ = [
33
34
  "run_embedding_conformance",
34
35
  "run_graph_conformance",
35
36
  "run_hybrid_search_conformance",
37
+ "run_identity_conformance",
36
38
  "run_input_validator_conformance",
37
39
  "run_memory_conformance",
38
40
  "run_output_validator_conformance",
@@ -32,6 +32,7 @@ from agentforge_core.contracts.guardrails import (
32
32
  OutputValidator,
33
33
  ToolCallGate,
34
34
  )
35
+ from agentforge_core.contracts.identity import IdentityError, IdentityProvider
35
36
  from agentforge_core.contracts.memory import MemoryStore
36
37
  from agentforge_core.contracts.reranker import Reranker
37
38
  from agentforge_core.contracts.strategy import ReasoningStrategy
@@ -1162,3 +1163,74 @@ async def run_hybrid_search_conformance(store: VectorStore) -> None:
1162
1163
  )
1163
1164
  finally:
1164
1165
  await store.delete(["a", "b", "c"])
1166
+
1167
+
1168
+ # ----------------------------------------------------------------------
1169
+ # Identity (feat-029)
1170
+ # ----------------------------------------------------------------------
1171
+
1172
+
1173
+ async def run_identity_conformance(provider: IdentityProvider) -> None:
1174
+ """Assert an `IdentityProvider` honours the locked contract.
1175
+
1176
+ The provider must start empty of the principals issued here. Covers
1177
+ issue→resolve round-trip + idempotency, credential/verify round-trip,
1178
+ rotation, and the unknown/invalid paths.
1179
+ """
1180
+ # 1. issue → resolve round-trip; issued fields are preserved.
1181
+ p = await provider.issue(
1182
+ name="invoice-reconciler",
1183
+ owner="finance-platform",
1184
+ attributes={"env": "prod"},
1185
+ )
1186
+ assert p.id, "issued principal must have a non-empty id"
1187
+ assert p.owner == "finance-platform", "issue must preserve owner"
1188
+ assert p.metadata.get("env") == "prod", "issue must preserve attributes"
1189
+
1190
+ resolved = await provider.resolve(p.id)
1191
+ assert resolved is not None, "resolve must find an issued principal"
1192
+ assert resolved.id == p.id, "resolve must return the same id"
1193
+
1194
+ # 2. issue is idempotent on name — same name → same id.
1195
+ again = await provider.issue(name="invoice-reconciler", owner="finance-platform")
1196
+ assert again.id == p.id, "issue must be idempotent on name (stable id)"
1197
+
1198
+ # 3. resolve of an unknown id returns None (no raise).
1199
+ assert await provider.resolve("agentforge:agent:nobody@0") is None, (
1200
+ "resolve of an unknown id must return None"
1201
+ )
1202
+
1203
+ # 4. credential → verify round-trip yields the same principal.
1204
+ token = await provider.credential(p)
1205
+ assert isinstance(token, str), "credential must return a string"
1206
+ assert token, "credential must return a non-empty token"
1207
+ verified = await provider.verify(token)
1208
+ assert verified.id == p.id, "verify(credential(p)) must round-trip to p"
1209
+
1210
+ # 5. verify of a bogus credential raises IdentityError.
1211
+ raised = False
1212
+ try:
1213
+ await provider.verify("not-a-real-credential")
1214
+ except IdentityError:
1215
+ raised = True
1216
+ assert raised, "verify of an invalid credential must raise IdentityError"
1217
+
1218
+ # 6. rotation keeps the id but invalidates prior credentials.
1219
+ rotated = await provider.rotate(p.id)
1220
+ assert rotated.id == p.id, "rotate must keep the principal id"
1221
+ stale = False
1222
+ try:
1223
+ await provider.verify(token)
1224
+ except IdentityError:
1225
+ stale = True
1226
+ assert stale, "a credential minted before rotate() must stop verifying"
1227
+ fresh = await provider.credential(rotated)
1228
+ assert (await provider.verify(fresh)).id == p.id, "post-rotation credential must verify"
1229
+
1230
+ # 7. rotate of an unknown principal raises IdentityError.
1231
+ unknown = False
1232
+ try:
1233
+ await provider.rotate("agentforge:agent:nobody@0")
1234
+ except IdentityError:
1235
+ unknown = True
1236
+ assert unknown, "rotate of an unknown principal must raise IdentityError"
@@ -0,0 +1,36 @@
1
+ """Auth value types (feat-014; widened by feat-029).
2
+
3
+ `Principal` is the identity carried through the framework — returned by an
4
+ `AuthPolicy` after a successful authentication (feat-014) and issued /
5
+ resolved by an `IdentityProvider` (feat-029, governance identity). It
6
+ carries an opaque ``id`` (the token itself by default; a stable URN once a
7
+ real identity provider is wired — e.g.
8
+ ``agentforge:agent:<org>/<name>@<version>``) plus optional ``kind`` /
9
+ ``owner`` and a free-form ``metadata`` attribute bag (role tags, tenant
10
+ id, env, region, …).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, field
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class Principal:
20
+ """Identity returned by `AuthPolicy.authenticate(...)` and issued /
21
+ resolved by `IdentityProvider` (feat-029).
22
+
23
+ Attributes:
24
+ id: Stable identifier — a bearer token for env-backed auth, or a
25
+ URN for a governance identity provider.
26
+ kind: What the principal *is* — ``"agent"`` (default), ``"tool"``,
27
+ ``"service"``, or ``"human"``.
28
+ owner: The team / human accountable for the principal, if known.
29
+ metadata: Free-form string attributes (env, region, role tags,
30
+ tenant id) — the governance ``attributes`` bag.
31
+ """
32
+
33
+ id: str
34
+ kind: str = "agent"
35
+ owner: str | None = None
36
+ metadata: dict[str, str] = field(default_factory=dict)
@@ -13,6 +13,8 @@ Pydantic model.
13
13
 
14
14
  from __future__ import annotations
15
15
 
16
+ from typing import Literal
17
+
16
18
  from pydantic import BaseModel, ConfigDict, Field
17
19
 
18
20
  from agentforge_core.contracts.graph_store import GraphStore
@@ -32,6 +34,11 @@ class GraphExpansion(BaseModel):
32
34
  fan-out — tune cautiously.
33
35
  edge_types: If set, restricts traversal to these edge
34
36
  types. ``None`` means all edge types.
37
+ direction: Which way to follow edges during expansion
38
+ (enh-005). ``"out"`` follows ``src → dst`` (e.g.
39
+ callees, what-X-cites); ``"in"`` follows ``dst → src``
40
+ (e.g. callers, who-cites-X); ``"any"`` (default) is the
41
+ original undirected behaviour via ``store.traverse``.
35
42
  text_property: Graph-node property used to populate the
36
43
  synthesised ``VectorMatch.text``. Defaults to
37
44
  ``"text"``.
@@ -49,5 +56,6 @@ class GraphExpansion(BaseModel):
49
56
  store: GraphStore
50
57
  max_hops: int = Field(default=2, ge=1)
51
58
  edge_types: tuple[str, ...] | None = None
59
+ direction: Literal["out", "in", "any"] = "any"
52
60
  text_property: str = Field(default="text", min_length=1)
53
61
  decay: float = Field(default=0.5, gt=0.0, le=1.0)
@@ -1,20 +0,0 @@
1
- """Auth value types (feat-014).
2
-
3
- `Principal` is the identity returned by an `AuthPolicy` after a
4
- successful authentication. Carries an opaque ``id`` (the token
5
- itself by default; an opaque user id once a real identity
6
- provider is wired) plus optional metadata for downstream use
7
- (role tags, tenant id, etc.).
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- from dataclasses import dataclass, field
13
-
14
-
15
- @dataclass(frozen=True)
16
- class Principal:
17
- """Identity returned by `AuthPolicy.authenticate(...)`."""
18
-
19
- id: str
20
- metadata: dict[str, str] = field(default_factory=dict)
File without changes