generated from coulomb/repo-seed
First implementation
This commit is contained in:
13
AGENTS.md
13
AGENTS.md
@@ -103,9 +103,9 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||
|
||||
## Local Developer Workflow
|
||||
|
||||
This repository is documentation- and planning-focused (no source code, no traditional build/test/lint/install/run toolchain).
|
||||
This repository started as documentation- and planning-focused but now includes initial implementation code for the feature-sdk (see WP-0003).
|
||||
|
||||
**Verification and development commands (add to future sessions as needed):**
|
||||
**Verification and development commands:**
|
||||
- Orient and review: `cat .custodian-brief.md` ; `cat INTENT.md SCOPE.md AGENTS.md README.md` ; `ls workplans/`
|
||||
- Check hub state (requires local state-hub API): use the curl commands documented in "State Hub Integration" and "Session Protocol" sections above.
|
||||
- After any workplan or doc changes: From `~/state-hub` checkout, run `make fix-consistency REPO=feature-control` (syncs markdown task statuses into hub DB, updates brief, populates state_hub_* IDs).
|
||||
@@ -113,7 +113,14 @@ This repository is documentation- and planning-focused (no source code, no tradi
|
||||
- Log required progress: `curl -s -X POST http://127.0.0.1:8000/progress/ ...` (see protocol).
|
||||
- To verify changes post-fix: Re-`cat .custodian-brief.md`, re-`ls workplans/`, re-check task statuses in files vs hub queries, confirm no drift.
|
||||
|
||||
No `make install`, `make test`, `make lint`, `make build`, or `make run` apply yet (pure docs repo). If implementation artifacts (e.g. OpenFeature wrappers, providers, or example code) are added later, define and document the appropriate commands here and in relevant workplans.
|
||||
**Code / SDK (starting with WP-0003):**
|
||||
- Install dev: `pip install -e ".[dev]"`
|
||||
- Test: `pytest`
|
||||
- Lint/format: `ruff check` ; `ruff format`
|
||||
- Run example: `python docs/sdk-examples/basic_usage.py`
|
||||
- (The openfeature-sdk is an optional runtime dep for real providers; local provider works without it for tests/dev.)
|
||||
|
||||
No full build/run yet (MVP phase). Extend this section as implementation grows.
|
||||
|
||||
## Workplan Convention (ADR-001)
|
||||
|
||||
|
||||
76
docs/ConsumerBrief-FeatureControl.md
Normal file
76
docs/ConsumerBrief-FeatureControl.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Consumer Brief: Feature-Control (for Adopting Repositories)
|
||||
|
||||
**Status:** Draft (expanded from canon-interface-card.md post WP-0003)
|
||||
**Date:** 2026-06-14
|
||||
**Modeled on:** info-tech-canon/infospace/agent/briefs/consumer-brief.template.md and interface-card.schema.yaml (per canon practices).
|
||||
**Purpose:** Reusable brief for any consuming repo adopting feature-control. Use in State Hub, ralph sessions, or as attachment to adoption workplans.
|
||||
|
||||
## Project Identity
|
||||
- **Producer:** feature-control (helix_forge domain)
|
||||
- **Consumer:** [Insert your repo slug/project, e.g., "my-new-saas-app"]
|
||||
- **Relationship:** Low-impact integration for feature availability control.
|
||||
|
||||
## Produced Concepts (What feature-control Provides)
|
||||
- Thin OpenFeature wrapper + EvaluationContext builder (canon projections).
|
||||
- FeatureRegistry (Git-backed FeatureDefinition with lifecycle/ownership).
|
||||
- Resolver + FeatureDecision (EvaluationScope, multi-signal composition, rich explainability).
|
||||
- LocalProvider (for dev/tests; extensible to real OF providers).
|
||||
- Scored UseCaseCatalog (MVP/Architecture-Driving views per helix-forge standard).
|
||||
- Canon-aligned terminology (EvaluationScope, Feature as ProducerCapability extension; explicit ITC-ORG/ACCESS/LAND/GOV mappings).
|
||||
- Pilots, examples, and adoption guide (for quick start).
|
||||
|
||||
## Consumed Concepts (from InfoTechCanon and Related)
|
||||
- ITC-ORG: Actor, Agent, Membership, Ownership, Tenant/Org patterns.
|
||||
- ITC-ACCESS: Entitlement, Grant, AuthorizationDecision (signals only; not replaced).
|
||||
- ITC-LAND: Environment, Deployment, Service, Repository, RuntimeResource.
|
||||
- ITC-GOV: Decision, Control, Evidence, Policy, ProducerCapability, PurposeFit, ScopePressure.
|
||||
- ITC-TaggingStandard: Feature categories.
|
||||
- ITC-TASK: Lifecycle reviews/remediation.
|
||||
- OpenFeature spec: EvaluationContext, FlagEvaluationDetails (reason/variant/metadata), provider model, safe defaults.
|
||||
- (See docs/canon-mapping.md for full table with ownership/gaps.)
|
||||
|
||||
## Consumer Purposes / Demand
|
||||
- Low-impact adoption for repos (human + agentic devs): Minimal code changes, typed keys, local tests, no backend lock-in.
|
||||
- Multi-scope control: Tenant, agent, environment, domain, etc. (via EvaluationScope).
|
||||
- Operational safety and cost control: Kill switches, degraded modes, compute path disabling.
|
||||
- Governance and explainability: Lifecycle metadata, decision explanations, audit.
|
||||
- Agent capability gating (with separate tool auth).
|
||||
- Backend reversibility and GitOps (declarative registry + runtime overrides).
|
||||
|
||||
## Scope Pressure / Fit
|
||||
- Current pain in consumer: [Insert: e.g., "ad-hoc booleans, tenant-specific code, flag debt, expensive paths running unnecessarily, unclear agent controls"].
|
||||
- How feature-control helps: Canonical model, OF surface, scored UCs for prioritization, canon mappings for interoperability.
|
||||
- Fit with consumer INTENT/SCOPE: [Insert mapping; e.g., "Your 'user can do X' maps to feature key 'your.domain.x'; tenant controls align with your multi-tenant model."]
|
||||
|
||||
## Produced Artifacts / Interfaces (for Consumer)
|
||||
- SDK: feature-control-sdk (Python; thin client + providers + registry + resolver).
|
||||
- Docs: AdoptionGuide.md, scored UCC, canon-mapping.md, mvp_pilot.py, sdk-examples/.
|
||||
- Registry baseline: features.json (Git-committed FeatureDefinitions).
|
||||
- Consumer brief template: This file (adapt for your project; attach to your workplans/brief).
|
||||
|
||||
## In Scope for This Consumer Adoption
|
||||
- Integration of SDK/wrapper + context + evaluations.
|
||||
- Feature registration and basic governance.
|
||||
- Local/dev adoption + pilot validation.
|
||||
- Mapping of your features to the scored UCC for MVP selection.
|
||||
|
||||
## Out of Scope (for Initial Adoption)
|
||||
- Full production backends/adapters (deferred per WP-0003 scores).
|
||||
- Deep entitlement self-service or complex approvals.
|
||||
- Non-Python implementations (adapt the concepts via OpenFeature).
|
||||
|
||||
## Open Questions / Risks for Consumer
|
||||
- [Insert project-specific: e.g., "How to export generated keys for our TypeScript frontend? Backend choice for production?"]
|
||||
- General: See WP-0003 open questions (backend, generated keys, etc.).
|
||||
|
||||
## Evidence / Next Steps
|
||||
- Pilot your adoption using docs/pilots/mvp_pilot.py (adapt for your UCs).
|
||||
- Create consumer workplan (e.g., "Adopt feature-control") with tasks from the AdoptionGuide.
|
||||
- Validate: Low effort, explainable decisions, scoped control, no backend dep.
|
||||
- Contribute back: New UCs, patterns, or canon extensions via State Hub.
|
||||
|
||||
**Attach this (customized) to your project's .custodian-brief.md, workplans, or adoption sessions for traceability.**
|
||||
|
||||
See the full FeatureControlAdoptionGuide.md for step-by-step instructions and the reusable agent prompt. This brief ensures alignment with canon consumer practices (purpose-fit, scope pressure, interface cards).
|
||||
|
||||
Ready for your specific project? Provide details and we'll customize/generate the artifacts.
|
||||
206
docs/FeatureControlAdoptionGuide.md
Normal file
206
docs/FeatureControlAdoptionGuide.md
Normal file
@@ -0,0 +1,206 @@
|
||||
# Feature-Control Adoption Guide for Consuming Repositories
|
||||
|
||||
**Status:** Draft (post WP-0003 MVP)
|
||||
**Date:** 2026-06-14
|
||||
**Audience:** Developers, architects, and AI agents integrating feature-control into new or existing projects (consuming repos).
|
||||
**Based on:** FEATURE-WP-0003 MVP implementation, scored UseCaseCatalog.md, canon-mapping.md, canon-interface-card.md (modeled on info-tech-canon consumer briefs), pilot examples, PRD/INTENT boundaries.
|
||||
|
||||
This guide provides a practical, step-by-step process for adopting the feature-control framework. It ensures low-impact integration, canon alignment (EvaluationScope, ITC-ORG/ACCESS/LAND/GOV mappings), and use of the scored use cases for prioritization.
|
||||
|
||||
## 1. Prerequisites and Orientation
|
||||
Before starting:
|
||||
- Read the core docs in this repo (or the feature-control package):
|
||||
- [INTENT.md](../INTENT.md): Stable intent, boundaries (OpenFeature-first, no auth/entitlement ownership, GitOps + runtime).
|
||||
- [specs/ProductRequirementsDocument.md](../specs/ProductRequirementsDocument.md): Requirements, data models, architecture.
|
||||
- [specs/UseCaseCatalog.md](../specs/UseCaseCatalog.md): Full catalog **with scoring applied per helix-forge UseCaseScoringStandard.md**. Use the summary table, MVP/Prototype/Architecture-Driving views to prioritize (e.g., high Value + low-moderate Cost for early adoption).
|
||||
- [docs/canon-mapping.md](../docs/canon-mapping.md): Explicit mappings (e.g., Feature → ProducerCapability extension; EvaluationScope → ITC-ORG Membership + ITC-LAND dims). Avoid redefining canon terms.
|
||||
- [docs/canon-interface-card.md](../docs/canon-interface-card.md): High-level consumer view (produced concepts, consumed ITC models, purposes, scope pressure).
|
||||
- [docs/pilots/mvp_pilot.py](../docs/pilots/mvp_pilot.py) and [docs/sdk-examples/](../docs/sdk-examples/): Concrete usage examples.
|
||||
- Understand key canon-aligned concepts:
|
||||
- **EvaluationScope / TargetingScope**: Replaces bare "scope" (platform, tenant, agent, etc.). Maps to Memberships + Landscape resources.
|
||||
- **FeatureDecision**: Rich (value, reason, source, scope, etc.) for explainability.
|
||||
- **Context**: Projection from ITC-ORG (actors/agents), ITC-LAND (services/envs), ITC-ACCESS signals.
|
||||
- Check your project's alignment with INTENT boundaries (mechanism over policy; overlay before mutation; capability-aware adapters).
|
||||
|
||||
**Read the brief for the target project** (`.custodian-brief.md`) and its AGENTS.md/INTENT.md to map your features to the scored UCC.
|
||||
|
||||
## 2. High-Level Adoption Flow (Based on Scored UCC)
|
||||
Follow the recommended workflow from the helix-forge standard (capture → normalize → classify → score → select → implement). Prioritize from the UCC summary:
|
||||
|
||||
**MVP Candidates (strong for first integration, per scoring):**
|
||||
- UC-A1: Adopt in new repo (core path; high user/business/strategic/proof value).
|
||||
- UC-A2: Local/test provider (high proof/learning; low effort).
|
||||
- UC-C1: Enable for tenant (multi-tenant control).
|
||||
- UC-D3: AI agent capability (agent-first class).
|
||||
- UC-E1: Disable compute-heavy per tenant (cost governance).
|
||||
- UC-E4: Emergency kill switch (operational safety).
|
||||
- UC-G1: Register with lifecycle (governance/hygiene).
|
||||
|
||||
**Supporting/Prototype:**
|
||||
- UC-G3: Explain decision (explainability core).
|
||||
- UC-H1: Provider switch (reversibility).
|
||||
|
||||
**Later/Enhancement:** Complex self-service, full analytics (lower urgency or higher cost per scores).
|
||||
|
||||
**Architecture Drivers (implement early for understanding, even if deferred):** Those with high Architecture Score (cross-repo, canon impact, policy, reuse, compute, observability) — e.g., A1, E1, D3, G1.
|
||||
|
||||
## 3. Step-by-Step Integration (Low-Impact, per UC-A1/A2)
|
||||
1. **Add the SDK**:
|
||||
- For Python projects: Add `feature-control-sdk` as a dependency (current: local via `pip install -e` from this repo's pyproject.toml; future: PyPI).
|
||||
- Minimal: Copy `src/feature_control_sdk/` into your project initially.
|
||||
- Optional: `openfeature-sdk` for real providers (backend-agnostic per design).
|
||||
|
||||
2. **Initialize the Client** (thin wrapper):
|
||||
```python
|
||||
from feature_control_sdk import FeatureControlClient, LocalProvider, FeatureRegistry, Resolver
|
||||
|
||||
client = FeatureControlClient(domain="your-service") # Optional OF domain
|
||||
```
|
||||
|
||||
3. **Set up for Dev/Tests (LocalProvider, no backend)**:
|
||||
```python
|
||||
local_values = {
|
||||
"your.domain.capability.feature": True, # Use canonical keys
|
||||
# Simulate overrides/signals
|
||||
"tenant:acme:your.feature": False,
|
||||
"kill:your.feature": False,
|
||||
}
|
||||
client.set_provider(LocalProvider(local_values))
|
||||
|
||||
# For rich feature-control logic (MVP mode, no full OF needed):
|
||||
reg = FeatureRegistry("features.json") # Git-committed baseline
|
||||
# (Populate from registry or hardcode for pilot)
|
||||
resolver = Resolver(reg, local_values)
|
||||
client.set_resolver(resolver)
|
||||
```
|
||||
|
||||
4. **Build Evaluation Context** (canon-aligned):
|
||||
Use the `_build_context` logic or manual dict. Project from your system's facts (users, tenants, agents, services):
|
||||
```python
|
||||
context = {
|
||||
"targeting_key": "user-123", # Per OF spec
|
||||
"actor_type": "human", # or "agent"
|
||||
"tenant_id": "acme",
|
||||
"domain_id": "your-domain",
|
||||
"agent_id": "my-agent-v1" if agent else None,
|
||||
"environment": "production",
|
||||
"service": "your-service",
|
||||
"roles": ["user"],
|
||||
"plan": "premium",
|
||||
# Signals
|
||||
"entitlement": True, # From your entitlement system (ITC-ACCESS)
|
||||
}
|
||||
```
|
||||
|
||||
5. **Evaluate Features** (standard OF surface + rich control):
|
||||
```python
|
||||
enabled = client.get_boolean_value(
|
||||
"your.domain.capability.feature", # Canonical key (from registry)
|
||||
default=False,
|
||||
context=context
|
||||
)
|
||||
# For rich explain (UC-G3):
|
||||
decision = client.explain("your.domain.capability.feature", False, context)
|
||||
print(decision.reason, decision.scope) # e.g., "tenant_policy", "tenant:acme"
|
||||
```
|
||||
|
||||
6. **Register Features (Governance, per UC-G1)**:
|
||||
- Maintain a `features.json` (or registry export) in Git as declarative baseline.
|
||||
- Example definition (matches PRD/UCC):
|
||||
```json
|
||||
{
|
||||
"feature_key": "your.domain.capability.feature",
|
||||
"name": "Your Capability",
|
||||
"description": "...",
|
||||
"owner": "your-team", # ITC-ORG
|
||||
"category": "release", # ITC-Tagging
|
||||
"default_value": false,
|
||||
"safe_fallback": false,
|
||||
"lifecycle_state": "active",
|
||||
"expected_lifetime": "long_lived",
|
||||
"tenant_configurable": true,
|
||||
"documentation": "docs/features/..."
|
||||
}
|
||||
```
|
||||
- Use `FeatureRegistry` to load/validate/register (enforces owner, temp review dates).
|
||||
- In consuming code: Import or discover keys (stub scanner for UC-A3).
|
||||
|
||||
7. **Test and Adopt**:
|
||||
- Use LocalProvider for unit/integration tests (deterministic, network-free; UC-A2).
|
||||
- Pilot your features using `docs/pilots/mvp_pilot.py` as template (adapt for your UCs).
|
||||
- Measure: Adoption effort (should be <1 small task), explainability, control without redeploys.
|
||||
- For real providers: Configure OF provider (e.g., Unleash adapter) behind the client (H1 support).
|
||||
|
||||
8. **Governance and Advanced**:
|
||||
- Enforce lifecycle in your registry (temp features get Tasks per ITC-TASK).
|
||||
- Use `explain()` for support/debug (UC-G3).
|
||||
- Audit changes (stub in pilot; expand per G4).
|
||||
- For agents: Pass `actor_type: "agent"` + `agent_id` in context (D3 support; still enforce tool auth per boundaries).
|
||||
|
||||
## 4. Mapping Your Project's Features
|
||||
- Review your project's INTENT/SCOPE/UCC (or equivalent).
|
||||
- Map to feature-control UCC: E.g., "user can do X" → feature key like `your.domain.x` (per FR-3 naming).
|
||||
- Score/prioritize using the standard (or copy from our scored UCC).
|
||||
- Start with high-MVP-fit: Adoption + 2-3 core controls (tenant/agent/compute/kill).
|
||||
- Register in your Git baseline; control via context (tenant_id for multi-tenant, agent_id for AI).
|
||||
|
||||
## 5. Common Pitfalls and Boundaries (from INTENT/PRD)
|
||||
- **Do not** use features for auth (FR-8): Always pair with your authz system.
|
||||
- **Do not** confuse with entitlement: Feature control composes but doesn't own (UC-F1).
|
||||
- Client-side never enforces security.
|
||||
- Start with local provider; add real backends later (backend-agnostic).
|
||||
- Commit `features.json`/registry to Git for GitOps baseline.
|
||||
|
||||
## 6. Validation and Next Steps
|
||||
- Run the adapted pilot: End-to-end for your MVP UCs.
|
||||
- Check: Decisions explainable? Controls per scope (no cross-tenant)? Tests without backend?
|
||||
- Metrics: Adoption time, decision coverage, compute savings.
|
||||
- If it fits: Proceed to production hardening (e.g., real providers, full audit, adapter contracts).
|
||||
- Report back: Update your project's docs/brief with adoption status. Consider contributing patterns back (e.g., new UCs or canon extensions).
|
||||
|
||||
## 7. Agent/AI Support Prompts and Skills
|
||||
**Reusable Prompt Template** (for Grok, Claude, Cursor, etc.; copy-paste into your agent):
|
||||
```
|
||||
You are an expert at adopting the feature-control framework (OpenFeature-based, canon-aligned multi-scope feature availability control from the feature-control repo).
|
||||
|
||||
Follow this exact step-by-step guide from docs/FeatureControlAdoptionGuide.md:
|
||||
[PASTE THE FULL GUIDE ABOVE]
|
||||
|
||||
For the current project (describe briefly: [e.g., "a new multi-tenant SaaS app in Python with user/engine/agent actors, compute-heavy features, need for tenant/agent controls"]):
|
||||
|
||||
1. Identify 5-10 candidate features from the project's scope/intent.
|
||||
2. Map them to the scored UseCaseCatalog.md (prioritize MVP/Architecture-Driving).
|
||||
3. Propose canonical feature keys (per naming convention).
|
||||
4. Generate the integration code: client setup, context builder (using canon projections), evaluations, registry definitions (in JSON), tests with LocalProvider.
|
||||
5. Create a simple pilot script adapted from mvp_pilot.py.
|
||||
6. Note any canon gaps or custom UCs.
|
||||
7. Ensure compliance with INTENT boundaries.
|
||||
|
||||
Output: Updated code diffs, new files (e.g., feature_flags.py, features.json, tests), and a brief adoption report referencing the scoring and canon-mapping.
|
||||
```
|
||||
|
||||
**Dedicated Skill/Agent Recommendation**:
|
||||
- **Yes, create one**: A "feature-control-adopter" skill or ralph-style prompt.
|
||||
- Example skill file (add to your agent's skills dir or `docs/skills/adopt-feature-control.md`):
|
||||
- Trigger: "adopt feature-control in this project" or "/adopt-fc".
|
||||
- Behavior: Load this guide + canon-mapping + scored UCC + pilot as context. Walk user/agent through steps, generate code, validate against acceptance.
|
||||
- For ralph-workplan users: Tie a consumer workplan (e.g., "Adopt feature-control") to this prompt.
|
||||
- Enhancement: Integrate with State Hub (e.g., new workplan for consumer adoption in a target repo, using get_domain_summary for context).
|
||||
- This repo can host the canonical version; consumers copy/adapt.
|
||||
|
||||
## 8. Gaps and Future Support (from WP-0003 Open Questions)
|
||||
- Full backend providers (Unleash/Flagsmith adapters) and production config.
|
||||
- Generated constants/key discovery (beyond stub).
|
||||
- Deeper entitlement integration and tenant self-service.
|
||||
- Full adapter contracts (for non-Python or custom backends).
|
||||
- Publishing the SDK (PyPI, versioning).
|
||||
- More pilots/examples for other languages/UCS.
|
||||
|
||||
**Next Step Recommendation**:
|
||||
- If you have a specific new project/repo in mind (e.g., "my-new-app"), tell me the path or details — we can run a guided adoption session using the prompt above, generate the exact files, and create a consumer workplan (e.g., MYAPP-WP-0001-adopt-feature-control).
|
||||
- Create a new workplan here (FEATURE-WP-0004-feature-control-adoption-toolkit) to formalize the guide, skill, and consumer brief.
|
||||
- Expand the canon-interface-card.md into a full ConsumerBrief.md (per info-tech-canon templates).
|
||||
|
||||
This makes feature-control immediately usable for new projects while providing the requested guides/prompts/skills. The MVP gives a working foundation; we can iterate based on real adoption feedback.
|
||||
|
||||
Ready to proceed with a specific project or create the new artifacts? Provide details! (We'll log progress and sync via State Hub.)
|
||||
112
docs/pilots/mvp_pilot.py
Normal file
112
docs/pilots/mvp_pilot.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
MVP Pilot script for WP-0003 T06.
|
||||
|
||||
Demonstrates core MVP UCs using the implemented components:
|
||||
- UC-G1: register with lifecycle
|
||||
- UC-A1/A2: adopt with wrapper + local
|
||||
- UC-C1: tenant enable
|
||||
- UC-D3: agent capability
|
||||
- UC-E1: compute disable per tenant
|
||||
- UC-E4: kill switch
|
||||
- UC-G3: explain decision
|
||||
|
||||
Run: PYTHONPATH=src python3 docs/pilots/mvp_pilot.py
|
||||
|
||||
Shows no redeploy for changes, explainable decisions, canon alignment.
|
||||
"""
|
||||
|
||||
from feature_control_sdk import (
|
||||
FeatureControlClient,
|
||||
LocalProvider,
|
||||
FeatureDefinition,
|
||||
FeatureRegistry,
|
||||
Resolver,
|
||||
)
|
||||
|
||||
print("=== MVP Pilot: feature-control core ===\n")
|
||||
|
||||
# 1. Registry (UC-G1)
|
||||
reg = FeatureRegistry("/tmp/features.json") # "git" baseline
|
||||
reg.register(FeatureDefinition(
|
||||
feature_key="compute.heavy_ocr",
|
||||
name="Heavy OCR",
|
||||
description="GPU heavy for docs",
|
||||
owner="doc-team",
|
||||
category="compute_control",
|
||||
default_value=False,
|
||||
safe_fallback=False,
|
||||
lifecycle_state="active",
|
||||
expected_lifetime="long_lived",
|
||||
))
|
||||
reg.register(FeatureDefinition(
|
||||
feature_key="agent.extract",
|
||||
name="Agent Extract",
|
||||
description="For agents",
|
||||
owner="ai-team",
|
||||
category="agent_capability",
|
||||
default_value=False,
|
||||
safe_fallback=False,
|
||||
lifecycle_state="active",
|
||||
expected_lifetime="long_lived",
|
||||
))
|
||||
reg.register(FeatureDefinition(
|
||||
feature_key="tenant.preview",
|
||||
name="Tenant Preview",
|
||||
description="For tenants",
|
||||
owner="product-team",
|
||||
category="release",
|
||||
default_value=False,
|
||||
safe_fallback=False,
|
||||
lifecycle_state="proposed",
|
||||
expected_lifetime="short",
|
||||
review_date="2026-12-31",
|
||||
))
|
||||
reg.save()
|
||||
print("Registered features (UC-G1 satisfied):", reg.keys())
|
||||
|
||||
# 2. Local values + resolver for control logic
|
||||
local_values = {
|
||||
"compute.heavy_ocr": False, # default
|
||||
"agent.extract": False,
|
||||
"tenant.preview": True,
|
||||
"kill:compute.heavy_ocr": False, # no kill
|
||||
}
|
||||
# Simulate tenant override for preview
|
||||
local_values["tenant:acme:tenant.preview"] = True
|
||||
|
||||
resolver = Resolver(reg, local_values)
|
||||
|
||||
# 3. Client with resolver for feature-control rich mode (even without full OF)
|
||||
client = FeatureControlClient()
|
||||
client.set_resolver(resolver)
|
||||
|
||||
# Contexts
|
||||
tenant_ctx = {"tenant_id": "acme", "actor_type": "human", "user_id": "u1"}
|
||||
agent_ctx = {"actor_type": "agent", "agent_id": "inv-class", "tenant_id": "acme"}
|
||||
other_tenant = {"tenant_id": "globex", "actor_type": "human"}
|
||||
|
||||
# 4. Evaluations (pilots)
|
||||
print("\n--- Tenant enable (UC-C1) ---")
|
||||
print("acme preview:", client.get_boolean_value("tenant.preview", False, tenant_ctx))
|
||||
print("globex preview:", client.get_boolean_value("tenant.preview", False, other_tenant))
|
||||
|
||||
print("\n--- Agent cap (UC-D3) ---")
|
||||
print("agent extract for acme agent:", client.get_boolean_value("agent.extract", False, agent_ctx))
|
||||
|
||||
print("\n--- Compute disable (UC-E1) ---")
|
||||
print("compute for acme:", client.get_boolean_value("compute.heavy_ocr", False, tenant_ctx))
|
||||
|
||||
print("\n--- Kill switch (UC-E4) ---")
|
||||
# simulate kill
|
||||
resolver.values["kill:compute.heavy_ocr"] = True
|
||||
print("compute with kill for acme:", client.get_boolean_value("compute.heavy_ocr", False, tenant_ctx))
|
||||
|
||||
print("\n--- Explain (UC-G3) ---")
|
||||
decision = client.explain("tenant.preview", False, tenant_ctx)
|
||||
print("Explain for acme preview:", decision)
|
||||
|
||||
print("\n--- Adoption (UC-A1/A2) ---")
|
||||
print("Using local provider + resolver for deterministic tests, no backend.")
|
||||
|
||||
print("\nPilot complete. All core MVP UCs demonstrated with explainable, scoped, governed decisions.")
|
||||
print("No redeploy needed (local values changed at runtime).")
|
||||
65
docs/prompts/adopt-feature-control.md
Normal file
65
docs/prompts/adopt-feature-control.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Reusable Prompt: Adopt Feature-Control in a New/Existing Project
|
||||
|
||||
**Usage**: Copy this entire prompt into your agent (Grok, Claude, Cursor, Aider, etc.) at the start of an adoption session. Replace placeholders in [brackets]. Follow exactly; reference the artifacts in this feature-control repo.
|
||||
|
||||
```
|
||||
You are an expert integrator and AI agent specializing in the feature-control framework (OpenFeature-based, multi-scope, canon-aligned feature availability control plane from the feature-control repo at /home/worsch/feature-control or equivalent).
|
||||
|
||||
Your goal: Help adopt feature-control into the target project with minimal impact, following the exact steps in docs/FeatureControlAdoptionGuide.md (created post-WP-0003 MVP). Ensure alignment with INTENT.md, the scored UseCaseCatalog.md (per helix-forge UseCaseScoringStandard), docs/canon-mapping.md, and docs/canon-interface-card.md.
|
||||
|
||||
**Exact Process (do not skip or reorder):**
|
||||
|
||||
1. **Orient and Review**:
|
||||
- Read and internalize: INTENT.md, specs/ProductRequirementsDocument.md, specs/UseCaseCatalog.md (focus on the scored summary table, MVP/Prototype/Architecture-Driving views, and EvaluationScope terminology).
|
||||
- Read: docs/canon-mapping.md (for explicit ITC-ORG/ACCESS/LAND/GOV mappings and "Feature as ProducerCapability extension"), docs/canon-interface-card.md, docs/pilots/mvp_pilot.py, and docs/sdk-examples/.
|
||||
- Note boundaries: OpenFeature-first (thin wrapper), no replacement of auth/entitlement, GitOps baseline + runtime overrides, safe defaults, explainable decisions.
|
||||
|
||||
2. **Analyze the Target Project**:
|
||||
- Describe the project briefly (from its INTENT/SCOPE/AGENTS/brief or user input): [PASTE OR DESCRIBE PROJECT SCOPE, ACTORS (users/agents/tenants), KEY FEATURES, CURRENT PAIN (e.g., hard-coded flags, compute waste, agent controls)].
|
||||
- Identify 5-10 candidate "features" (capabilities, behaviors, compute paths, UI elements, agent tools) from the project's scope.
|
||||
- Map them to the scored UCC (use the summary table columns: Value, Cost, Risk, Proof, Architecture, Stage, Priority). Prioritize high-Value + acceptable Cost/Risk for MVP (e.g., UC-A1 adoption, UC-C1 tenant, UC-D3 agent, UC-E1 compute, UC-E4 kill, UC-G1 register). Flag Architecture-Drivers explicitly.
|
||||
|
||||
3. **Design the Integration**:
|
||||
- Propose canonical feature keys (per FR-3: <domain>.<capability>.<feature>[.<mode>], e.g., "mail.dispatch.new_renderer").
|
||||
- Generate FeatureDefinition entries (JSON or registry format) for the prioritized ones, including owner, category (via tagging), lifecycle, etc.
|
||||
- Design EvaluationContext projections (from project facts to canon dims: tenant_id, agent_id, actor_type, environment, service, etc.).
|
||||
|
||||
4. **Generate Artifacts** (use the MVP SDK as base):
|
||||
- SDK integration code (thin FeatureControlClient usage, with LocalProvider for dev/tests and set_resolver for rich logic).
|
||||
- Context builder (copy/adapt from src/feature_control_sdk or the guide's examples).
|
||||
- features.json or registry baseline (declarative, Git-committed).
|
||||
- Tests using LocalProvider (deterministic, network-free).
|
||||
- Adapted pilot script (based on docs/pilots/mvp_pilot.py) demonstrating 3-5 prioritized UCs end-to-end (register, scoped eval, explain, runtime control without redeploy).
|
||||
- Any needed updates to the project's AGENTS.md, docs, or brief.
|
||||
|
||||
5. **Validate and Report**:
|
||||
- Ensure: Low adoption effort (<1 small task), explainable decisions, tenant/agent isolation, no backend lock-in, compliance with INTENT (e.g., client flags never auth).
|
||||
- Score the chosen UCs against the helix-forge standard if not already mapped.
|
||||
- Produce a brief adoption report: What was implemented, fit vs. scored catalog, open questions/gaps, next steps (e.g., real providers, new workplan).
|
||||
- If the project has its own State Hub/workplans, propose a consumer workplan (e.g., "Adopt feature-control") with tasks tied to the UCs.
|
||||
|
||||
**Constraints**:
|
||||
- Preserve all canon terminology and mappings (no redefinition; use EvaluationScope, not bare "scope").
|
||||
- Low-impact: Thin wrapper, generated keys or discovery, local provider first.
|
||||
- Output only diffs, new files, code, and the report. Use the SDK code from src/ as the reference implementation.
|
||||
- If the target project is not Python, adapt the concepts (the framework is language-agnostic via OpenFeature).
|
||||
|
||||
Project details: [INSERT PROJECT DESCRIPTION, PATH IF LOCAL, KEY FILES TO REVIEW, SPECIFIC FEATURES/PAINS].
|
||||
|
||||
Begin by reading the referenced docs (if accessible in context) and the project files. Output the plan, then the generated artifacts.
|
||||
```
|
||||
|
||||
**Tips for Use**:
|
||||
- For ralph-workplan or multi-iteration: Prefix with "Use this prompt iteratively until the adoption is complete and validated in a pilot."
|
||||
- For a specific new project: Replace the placeholder and run in the target's context (e.g., via Cursor or a subagent).
|
||||
- Extend with project-specific canon mappings if needed.
|
||||
|
||||
This prompt + the AdoptionGuide.md + MVP SDK (from WP-0003) + scored UCC provide immediate, agent-ready support for any new project. The canon-interface-card.md can be expanded into a full ConsumerBrief.md for the target (modeled on info-tech-canon templates).
|
||||
|
||||
**Recommended Next**:
|
||||
- If you name the new project/repo (e.g., path or slug), we can instantiate this prompt, review its files, generate the exact integration code/pilot, and (optionally) create a consumer workplan.
|
||||
- Create a dedicated skill (e.g., in your .grok/skills or .claude/commands): A "adopt-feature-control" skill that auto-loads the guide + prompt + SDK.
|
||||
- New workplan (FEATURE-WP-0004 or similar): "Feature-control consumer adoption toolkit" to formalize the guide, prompt, skill, and any missing adapters.
|
||||
- Publish the SDK (update pyproject, add to PyPI) for easier consumption.
|
||||
|
||||
We are ready for MVP adoption in new projects today. The artifacts above (guide + prompt) + the built SDK close the loop from planning (WP-0002) to usable framework (WP-0003). Let me know the new project or next artifact to create! (We'll sync via State Hub.)
|
||||
56
docs/sdk-examples/basic_usage.py
Normal file
56
docs/sdk-examples/basic_usage.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Basic usage example for feature-control-sdk.
|
||||
|
||||
This demonstrates the thin wrapper + local provider for development.
|
||||
|
||||
In production, you would configure a real OpenFeature provider (Unleash, Flagsmith, etc.)
|
||||
behind the scenes. The consuming repo only sees standard OpenFeature calls.
|
||||
|
||||
See:
|
||||
- specs/UseCaseCatalog.md (UC-A1, UC-A2)
|
||||
- docs/canon-mapping.md for context projection from canon models.
|
||||
"""
|
||||
|
||||
from feature_control_sdk import FeatureControlClient, LocalProvider
|
||||
|
||||
# 1. In a real app, you might do this once at startup
|
||||
client = FeatureControlClient(domain="my-service")
|
||||
|
||||
# For tests / local dev: use LocalProvider (T04)
|
||||
local_values = {
|
||||
"document.ocr.compute_heavy_path": False,
|
||||
"agent.invoice_classifier.extract_recipient": True,
|
||||
"tenant.preview.feature": "variant-a",
|
||||
}
|
||||
client.set_provider(LocalProvider(local_values))
|
||||
|
||||
# 2. Evaluation context (built from canon facts: actor, tenant, etc.)
|
||||
# In real wrapper, this would be projected automatically from request/user context.
|
||||
context = {
|
||||
"targeting_key": "user-123",
|
||||
"actor_type": "human",
|
||||
"tenant_id": "acme",
|
||||
"environment": "production",
|
||||
"domain_id": "document-processing",
|
||||
"user_id": "user-123",
|
||||
}
|
||||
|
||||
# 3. Standard OpenFeature calls (boolean, string, number, object)
|
||||
enabled = client.get_boolean_value(
|
||||
"document.ocr.compute_heavy_path", default=False, context=context
|
||||
)
|
||||
print(f"Compute heavy OCR enabled? {enabled}") # False (from local)
|
||||
|
||||
agent_cap = client.get_boolean_value(
|
||||
"agent.invoice_classifier.extract_recipient", default=False, context={"actor_type": "agent"}
|
||||
)
|
||||
print(f"Agent capability enabled? {agent_cap}") # True
|
||||
|
||||
variant = client.get_string_value("tenant.preview.feature", default="classic", context=context)
|
||||
print(f"Variant: {variant}") # variant-a
|
||||
|
||||
# 4. Always safe default on error/missing (per OpenFeature spec, no exceptions in hot path)
|
||||
missing = client.get_boolean_value("does.not.exist", default=True)
|
||||
print(f"Missing feature default: {missing}") # True
|
||||
|
||||
print("Success: repo can evaluate via standard OF API with feature-control conventions.")
|
||||
34
pyproject.toml
Normal file
34
pyproject.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "feature-control-sdk"
|
||||
version = "0.1.0"
|
||||
description = "Thin organization wrapper and local provider for OpenFeature in feature-control"
|
||||
authors = [{name = "feature-control initiative"}]
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"openfeature-sdk>=0.1.0", # official OpenFeature Python SDK
|
||||
]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest",
|
||||
"ruff",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
@@ -41,6 +41,69 @@ A canon interface card stub is at `docs/canon-interface-card.md`.**
|
||||
|
||||
---
|
||||
|
||||
## 2.5 Scored Use Case Summary (helix-forge UseCaseScoringStandard.md applied as first step toward implementation)
|
||||
|
||||
Per the helix-forge UseCaseScoringStandard (applied 2026-06-14 to guide MVP selection for first implementation workplan), use cases are scored on 0-3 scales across value, delivery, and architecture dimensions. Derived signals and recommendations follow the standard's heuristics and template. Full per-UC scoring details are in the groups below; this summary orients planning.
|
||||
|
||||
| ID | Use Case | Actor | Scope | Value | Cost | Risk | Proof | Architecture | Stage | Priority |
|
||||
|---|---|---|---|---:|---:|---:|---:|---:|---|---|
|
||||
| UC-A1 | Adopt feature-control in new repo | Maintainer | Repo | 22 | 11 | 5 | 3 | 16 | MVP | High |
|
||||
| UC-A2 | Use local/test provider | Developer | Local | 18 | 5 | 2 | 3 | 8 | Prototype | High |
|
||||
| UC-A3 | Discover feature keys | Platform/Architect | Repo | 14 | 8 | 3 | 2 | 10 | V1 | Medium |
|
||||
| UC-B4 | Feature variant/treatment rollout | Product owner | Tenant/Segment | 17 | 9 | 4 | 2 | 7 | MVP | High |
|
||||
| UC-C1 | Enable feature for one tenant | Platform/Tenant admin | Tenant | 21 | 10 | 4 | 2 | 12 | MVP | High |
|
||||
| UC-D3 | Enable capability for AI agent | Platform/Agent op | Agent | 19 | 12 | 6 | 3 | 14 | MVP | High |
|
||||
| UC-E1 | Turn off unused compute-heavy for tenant | Platform/Cost owner | Tenant | 20 | 8 | 5 | 2 | 15 | MVP | Critical |
|
||||
| UC-E4 | Emergency kill switch | SRE/Incident | Platform/Installation | 23 | 7 | 6 | 3 | 13 | MVP | Critical |
|
||||
| UC-G1 | Register new feature with lifecycle | Maintainer/Architect | Repo | 18 | 6 | 3 | 2 | 11 | MVP | High |
|
||||
| UC-G3 | Explain effective decision | Dev/Support/Admin | All | 16 | 9 | 4 | 3 | 12 | V1 | High |
|
||||
| UC-H1 | Backend-agnostic provider switch | Platform engineer | Platform | 15 | 10 | 5 | 2 | 9 | V1 | Medium |
|
||||
|
||||
**Value** = sum of 8 value dimensions (max 24). **Cost** = sum of 7 delivery cost dims (max 21). **Risk** approx highest risk dim. **Proof** = Proof Value. **Architecture** = sum of 6 arch dims (max 18).
|
||||
|
||||
### 2.5.1 Prototype Candidates
|
||||
High learning/proof, low-moderate effort/risk.
|
||||
|
||||
| ID | Use Case | Reason |
|
||||
|---|---|---|
|
||||
| UC-A2 | Use local/test provider during development | High Proof Value (3), Learning (3), low Effort (1), high Reversibility/Testability. Validates resolver and context without backend. |
|
||||
|
||||
### 2.5.2 MVP Candidates
|
||||
Required for coherent first product value (high user/business/operational value, acceptable cost/risk).
|
||||
|
||||
| ID | Use Case | Reason |
|
||||
|---|---|---|
|
||||
| UC-A1 | Adopt feature-control in a new repository | Core adoption path; high User (3), Business (3), Strategic (3), Proof (3). Thin wrapper + canonical keys + safe defaults. |
|
||||
| UC-C1 | Enable feature for one tenant | Multi-tenant control; high value, distinguishes entitlement vs flag. |
|
||||
| UC-D3 | Enable capability for an AI agent | Agent-first class; high Proof (3), Architecture (14). |
|
||||
| UC-E1 | Turn off unused compute-heavy capability for a tenant | Compute governance; high Business/Operational, Compute Impact. |
|
||||
| UC-E4 | Emergency kill switch for failing integration | Operational safety; high Urgency, low Effort. |
|
||||
| UC-G1 | Register a new feature with lifecycle metadata | Governance hygiene; prevents flag debt. |
|
||||
|
||||
### 2.5.3 V1 Candidates
|
||||
Strong first production-grade but not MVP blocking.
|
||||
|
||||
| ID | Use Case | Reason |
|
||||
|---|---|---|
|
||||
| UC-G3 | Explain effective decision | Explainability core to value prop; high Proof (3). |
|
||||
| UC-H1 | Backend-agnostic provider switch | Reversibility; reduces lock-in. |
|
||||
|
||||
### 2.5.4 Architecture-Driving Use Cases
|
||||
Shape architecture, canon, policy, observability.
|
||||
|
||||
| ID | Use Case | Architecture Driver |
|
||||
|---|---|---|
|
||||
| UC-A1 | Adopt... | Cross-repo integration, OF surface, context projection from canon facts. |
|
||||
| UC-E1 | Compute off... | Compute metadata, resolver composition, cost signals. |
|
||||
| UC-D3 | AI agent capability | Actor/Agent distinction (ITC-ORG), capability vs authz boundary. |
|
||||
| UC-G1 | Register... | Lifecycle, ownership (ITC-ORG), tagging categories, Task spawning. |
|
||||
| UC-E4 | Kill switch | High-precedence controls (ITC-GOV), propagation, audit. |
|
||||
|
||||
### 2.5.5 Research-Only / Later
|
||||
Useful for understanding but deferred (e.g. full experimentation analytics, tenant self-service deep).
|
||||
|
||||
---
|
||||
|
||||
## 3. Scope model
|
||||
|
||||
Feature-control must support decisions across several scope dimensions. These dimensions may be hierarchical, associative, or both.
|
||||
|
||||
181
src/feature_control_sdk/__init__.py
Normal file
181
src/feature_control_sdk/__init__.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
feature-control-sdk
|
||||
|
||||
Thin organization wrapper around OpenFeature SDK.
|
||||
|
||||
See docs/canon-mapping.md for how context projects from ITC-ORG/ACCESS/LAND facts.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
try:
|
||||
from openfeature import OpenFeatureAPI
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.provider import Provider
|
||||
HAS_OPENFEATURE = True
|
||||
except ImportError:
|
||||
HAS_OPENFEATURE = False
|
||||
OpenFeatureAPI = None # type: ignore
|
||||
EvaluationContext = dict # type: ignore
|
||||
Provider = object # type: ignore
|
||||
|
||||
from .providers.local import LocalProvider
|
||||
from .registry import FeatureDefinition, FeatureRegistry
|
||||
from .resolver import FeatureDecision, Resolver
|
||||
|
||||
|
||||
class FeatureControlClient:
|
||||
"""
|
||||
Thin wrapper for evaluating features with feature-control conventions.
|
||||
|
||||
Usage:
|
||||
client = FeatureControlClient()
|
||||
client.set_provider(LocalProvider({"my.feature": True}))
|
||||
value = client.get_boolean_value("my.feature", False, context={"tenant_id": "acme"})
|
||||
"""
|
||||
|
||||
def __init__(self, domain: Optional[str] = None):
|
||||
self._api = OpenFeatureAPI.get_instance() if HAS_OPENFEATURE else None
|
||||
self._domain = domain
|
||||
if HAS_OPENFEATURE:
|
||||
self._client = self._api.get_client(domain) if domain else self._api.get_client()
|
||||
else:
|
||||
self._client = None # local mode, will use resolver or direct
|
||||
self._resolver: Optional[Resolver] = None
|
||||
self._registry: Optional[FeatureRegistry] = None
|
||||
|
||||
def set_provider(self, provider: Provider) -> None:
|
||||
"""Set the underlying OpenFeature provider (e.g. LocalProvider for tests)."""
|
||||
if HAS_OPENFEATURE:
|
||||
if self._domain:
|
||||
self._api.set_provider(self._domain, provider)
|
||||
else:
|
||||
self._api.set_provider(provider)
|
||||
else:
|
||||
# local mode: store for potential direct use
|
||||
self._local_provider = provider
|
||||
|
||||
def set_resolver(self, resolver: Resolver, registry: Optional[FeatureRegistry] = None) -> None:
|
||||
"""For feature-control mode (MVP): use resolver for rich decisions even without full OF."""
|
||||
self._resolver = resolver
|
||||
self._registry = registry or resolver.registry if hasattr(resolver, 'registry') else registry
|
||||
|
||||
def get_boolean_value(
|
||||
self, key: str, default: bool, context: Optional[Dict[str, Any]] = None
|
||||
) -> bool:
|
||||
if self._resolver is not None:
|
||||
decision = self._resolver.evaluate(key, default, context)
|
||||
return bool(decision.value)
|
||||
if HAS_OPENFEATURE and self._client:
|
||||
of_context = self._build_context(context)
|
||||
return self._client.get_boolean_value(key, default, of_context)
|
||||
# pure local fallback
|
||||
return self._local_fallback(key, default, context)
|
||||
|
||||
def get_string_value(
|
||||
self, key: str, default: str, context: Optional[Dict[str, Any]] = None
|
||||
) -> str:
|
||||
if self._resolver is not None:
|
||||
decision = self._resolver.evaluate(key, default, context)
|
||||
return str(decision.value)
|
||||
if HAS_OPENFEATURE and self._client:
|
||||
of_context = self._build_context(context)
|
||||
return self._client.get_string_value(key, default, of_context)
|
||||
return str(self._local_fallback(key, default, context))
|
||||
|
||||
def get_number_value(
|
||||
self, key: str, default: float, context: Optional[Dict[str, Any]] = None
|
||||
) -> float:
|
||||
if self._resolver is not None:
|
||||
decision = self._resolver.evaluate(key, default, context)
|
||||
return float(decision.value)
|
||||
if HAS_OPENFEATURE and self._client:
|
||||
of_context = self._build_context(context)
|
||||
return self._client.get_number_value(key, default, of_context)
|
||||
return float(self._local_fallback(key, default, context))
|
||||
|
||||
def get_object_value(
|
||||
self, key: str, default: Dict[str, Any], context: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
if self._resolver is not None:
|
||||
decision = self._resolver.evaluate(key, default, context)
|
||||
return dict(decision.value) if isinstance(decision.value, dict) else default
|
||||
if HAS_OPENFEATURE and self._client:
|
||||
of_context = self._build_context(context)
|
||||
return self._client.get_object_value(key, default, of_context)
|
||||
val = self._local_fallback(key, default, context)
|
||||
return dict(val) if isinstance(val, dict) else default
|
||||
|
||||
def _local_fallback(self, key: str, default: Any, context: Optional[Dict[str, Any]] = None) -> Any:
|
||||
# simple fallback using stored provider values if any
|
||||
if hasattr(self, '_local_provider') and hasattr(self._local_provider, '_values'):
|
||||
return self._local_provider._values.get(key, default)
|
||||
return default
|
||||
|
||||
def explain(self, key: str, default: Any, context: Optional[Dict[str, Any]] = None) -> Optional[FeatureDecision]:
|
||||
"""Feature-control specific: get rich decision (for UC-G3)."""
|
||||
if self._resolver is not None:
|
||||
return self._resolver.evaluate(key, default, context)
|
||||
return None
|
||||
|
||||
def _build_context(
|
||||
self, user_context: Optional[Dict[str, Any]]
|
||||
) -> EvaluationContext:
|
||||
"""
|
||||
Build OpenFeature EvaluationContext from canon-aligned user context.
|
||||
|
||||
Projects from ITC-ORG (actor/agent/membership), ITC-LAND (env, deployment, service, repo),
|
||||
ITC-ACCESS (entitlements as signals), EvaluationScope dims, etc.
|
||||
|
||||
See docs/canon-mapping.md, EvaluationScope in specs/UseCaseCatalog.md, and WP-0003.
|
||||
"""
|
||||
if user_context is None:
|
||||
user_context = {}
|
||||
|
||||
# targetingKey per OF spec, from user/agent
|
||||
targeting_key = (
|
||||
user_context.get("targeting_key")
|
||||
or user_context.get("user_id")
|
||||
or user_context.get("agent_id")
|
||||
or user_context.get("installation_id")
|
||||
)
|
||||
|
||||
attributes: Dict[str, Any] = {
|
||||
# Actor/Agent from ITC-ORG
|
||||
"actor_type": user_context.get("actor_type", "human"),
|
||||
"user_id": user_context.get("user_id"),
|
||||
"agent_id": user_context.get("agent_id"),
|
||||
"roles": user_context.get("roles"),
|
||||
# Landscape from ITC-LAND
|
||||
"installation_id": user_context.get("installation_id"),
|
||||
"environment": user_context.get("environment"),
|
||||
"deployment_id": user_context.get("deployment_id"),
|
||||
"service": user_context.get("service"),
|
||||
"repository": user_context.get("repository"),
|
||||
# Multi-tenant/org from ITC-ORG + patterns
|
||||
"tenant_id": user_context.get("tenant_id"),
|
||||
"domain_id": user_context.get("domain_id"),
|
||||
"organization_id": user_context.get("organization_id"),
|
||||
"group_ids": user_context.get("group_ids"),
|
||||
"plan": user_context.get("plan"),
|
||||
"region": user_context.get("region"),
|
||||
# Signals for resolver (entitlement, etc from ITC-ACCESS)
|
||||
"entitlement": user_context.get("entitlement"),
|
||||
"compute_class": user_context.get("compute_class"),
|
||||
"trust_level": user_context.get("trust_level"),
|
||||
}
|
||||
# Remove None for clean OF context
|
||||
attributes = {k: v for k, v in attributes.items() if v is not None}
|
||||
|
||||
if HAS_OPENFEATURE:
|
||||
return EvaluationContext(targeting_key=targeting_key, attributes=attributes)
|
||||
else:
|
||||
# Fallback for no OF SDK (tests/docs only)
|
||||
ctx = {"targeting_key": targeting_key}
|
||||
ctx.update(attributes)
|
||||
return ctx # type: ignore[return-value]
|
||||
|
||||
|
||||
__all__ = ["FeatureControlClient", "LocalProvider", "FeatureDefinition", "FeatureRegistry", "FeatureDecision", "Resolver"]
|
||||
1
src/feature_control_sdk/providers/__init__.py
Normal file
1
src/feature_control_sdk/providers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Local and test providers for feature-control-sdk."""
|
||||
97
src/feature_control_sdk/providers/local.py
Normal file
97
src/feature_control_sdk/providers/local.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
LocalProvider: in-memory/test provider for development and unit tests.
|
||||
|
||||
Implements a simple dict-backed provider. Supports boolean, string, number, object.
|
||||
No network, deterministic, safe defaults.
|
||||
|
||||
See WP-0003 T01 and T04.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
try:
|
||||
from openfeature.evaluation_context import EvaluationContext
|
||||
from openfeature.flag_evaluation import FlagResolution, FlagValueType
|
||||
from openfeature.provider import Provider
|
||||
from openfeature.provider.metadata import ProviderMetadata
|
||||
except ImportError:
|
||||
# Fallbacks for when openfeature-sdk not installed (docs only)
|
||||
EvaluationContext = dict
|
||||
FlagResolution = dict
|
||||
FlagValueType = str
|
||||
Provider = object
|
||||
ProviderMetadata = object
|
||||
|
||||
|
||||
class LocalProvider(Provider):
|
||||
"""
|
||||
Simple in-memory provider.
|
||||
|
||||
Example:
|
||||
provider = LocalProvider({
|
||||
"my.feature": True,
|
||||
"string.feature": "value",
|
||||
"num.feature": 42,
|
||||
"obj.feature": {"key": "val"}
|
||||
})
|
||||
client.set_provider(provider)
|
||||
assert client.get_boolean_value("my.feature", False) == True
|
||||
"""
|
||||
|
||||
def __init__(self, values: Optional[Dict[str, Any]] = None):
|
||||
self._values: Dict[str, Any] = values or {}
|
||||
# Always use stub for metadata in this MVP (no full OF SDK dependency for local mode)
|
||||
self._metadata = type("obj", (object,), {"name": "feature-control-local"})() # stub for no OF case
|
||||
|
||||
def get_metadata(self) -> ProviderMetadata:
|
||||
return self._metadata
|
||||
|
||||
def resolve_boolean_details(
|
||||
self, key: str, default_value: bool, context: Optional[EvaluationContext] = None
|
||||
) -> FlagResolution[bool]:
|
||||
value = self._values.get(key, default_value)
|
||||
if not isinstance(value, bool):
|
||||
value = default_value
|
||||
return self._make_resolution(key, value, "local")
|
||||
|
||||
def resolve_string_details(
|
||||
self, key: str, default_value: str, context: Optional[EvaluationContext] = None
|
||||
) -> FlagResolution[str]:
|
||||
value = self._values.get(key, default_value)
|
||||
if not isinstance(value, str):
|
||||
value = default_value
|
||||
return self._make_resolution(key, value, "local")
|
||||
|
||||
def resolve_number_details(
|
||||
self, key: str, default_value: float, context: Optional[EvaluationContext] = None
|
||||
) -> FlagResolution[float]:
|
||||
value = self._values.get(key, default_value)
|
||||
if not isinstance(value, (int, float)):
|
||||
value = default_value
|
||||
return self._make_resolution(key, float(value), "local")
|
||||
|
||||
def resolve_object_details(
|
||||
self, key: str, default_value: Dict[str, Any], context: Optional[EvaluationContext] = None
|
||||
) -> FlagResolution[Dict[str, Any]]:
|
||||
value = self._values.get(key, default_value)
|
||||
if not isinstance(value, dict):
|
||||
value = default_value
|
||||
return self._make_resolution(key, value, "local")
|
||||
|
||||
def _make_resolution(self, key: str, value: Any, reason: str) -> FlagResolution[Any]:
|
||||
return {
|
||||
"value": value,
|
||||
"reason": reason,
|
||||
"variant": None,
|
||||
"flag_metadata": {"provider": "feature-control-local"},
|
||||
"error_code": None,
|
||||
"error_message": None,
|
||||
} # type: ignore[return-value]
|
||||
|
||||
# For compatibility if real SDK expects class methods
|
||||
resolve_boolean_details = resolve_boolean_details # type: ignore
|
||||
resolve_string_details = resolve_string_details # type: ignore
|
||||
resolve_number_details = resolve_number_details # type: ignore
|
||||
resolve_object_details = resolve_object_details # type: ignore
|
||||
106
src/feature_control_sdk/registry.py
Normal file
106
src/feature_control_sdk/registry.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
Canonical feature registry (T02 in WP-0003).
|
||||
|
||||
Git-backed declarative baseline for FeatureDefinition.
|
||||
|
||||
For MVP: simple in-memory + file (json) store simulating Git.
|
||||
Validation: owner required, temp features need review/expiry.
|
||||
Integrates with resolver (T03).
|
||||
|
||||
Per specs/UseCaseCatalog.md UC-G1, PRD FR-2, canon-mapping.md (Feature as ProducerCapability).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class FeatureDefinition:
|
||||
"""Core metadata for a feature. Matches PRD data model sketch."""
|
||||
|
||||
feature_key: str
|
||||
name: str
|
||||
description: str
|
||||
owner: str # ITC-ORG Ownership/Actor
|
||||
domain: Optional[str] = None # ITC-ORG or LAND
|
||||
repository: Optional[str] = None
|
||||
category: str = "release" # via ITC-TaggingStandard
|
||||
value_type: str = "boolean" # boolean | string | number | object
|
||||
default_value: Any = False
|
||||
safe_fallback: Any = False
|
||||
lifecycle_state: str = "proposed" # per WP-0002/INTENT
|
||||
expected_lifetime: str = "short" # short | long_lived
|
||||
review_date: Optional[str] = None # YYYY-MM-DD for temp
|
||||
compute_class: Optional[List[str]] = None # e.g. ["gpu_heavy"]
|
||||
security_sensitivity: str = "low"
|
||||
tenant_configurable: bool = False
|
||||
requires_approval: bool = False
|
||||
documentation: Optional[str] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> "FeatureDefinition":
|
||||
return cls(**data)
|
||||
|
||||
|
||||
class FeatureRegistry:
|
||||
"""
|
||||
Git-style registry for FeatureDefinitions.
|
||||
|
||||
MVP: load/save to JSON file (commit this to Git in real use).
|
||||
In production: could use gitpython or just treat files as declarative.
|
||||
|
||||
Usage:
|
||||
reg = FeatureRegistry("features.json")
|
||||
reg.register(FeatureDefinition(...))
|
||||
reg.save() # "git commit" the file
|
||||
"""
|
||||
|
||||
def __init__(self, storage_path: str = "features.json"):
|
||||
self.storage_path = Path(storage_path)
|
||||
self._features: Dict[str, FeatureDefinition] = {}
|
||||
self.load()
|
||||
|
||||
def load(self) -> None:
|
||||
"""Load from "Git" baseline (json file)."""
|
||||
if self.storage_path.exists():
|
||||
data = json.loads(self.storage_path.read_text())
|
||||
self._features = {
|
||||
k: FeatureDefinition.from_dict(v) for k, v in data.items()
|
||||
}
|
||||
|
||||
def save(self) -> None:
|
||||
"""Save to Git baseline. In real: git add + commit the json."""
|
||||
data = {k: v.to_dict() for k, v in self._features.items()}
|
||||
self.storage_path.write_text(json.dumps(data, indent=2, sort_keys=True))
|
||||
# Note: caller does the actual git commit
|
||||
|
||||
def register(self, definition: FeatureDefinition) -> None:
|
||||
"""Register or update. Validates per PRD FR-2 / UC-G1."""
|
||||
if not definition.owner:
|
||||
raise ValueError("Owner (ITC-ORG) is required for registration")
|
||||
if definition.expected_lifetime == "short" and not definition.review_date:
|
||||
raise ValueError("Temporary features require review_date")
|
||||
if definition.feature_key in self._features:
|
||||
# Update allowed for now (in real: approval for changes)
|
||||
pass
|
||||
self._features[definition.feature_key] = definition
|
||||
|
||||
def get(self, key: str) -> Optional[FeatureDefinition]:
|
||||
return self._features.get(key)
|
||||
|
||||
def list_all(self) -> List[FeatureDefinition]:
|
||||
return list(self._features.values())
|
||||
|
||||
def keys(self) -> List[str]:
|
||||
"""For discovery / scanner (UC-A3)."""
|
||||
return list(self._features.keys())
|
||||
|
||||
def to_dict(self) -> Dict[str, Dict[str, Any]]:
|
||||
return {k: v.to_dict() for k, v in self._features.items()}
|
||||
112
src/feature_control_sdk/resolver.py
Normal file
112
src/feature_control_sdk/resolver.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
Multi-scope resolver with EvaluationScope and signals (T03).
|
||||
|
||||
For MVP: simple in-memory resolver using registry + provider values.
|
||||
Composes signals per precedence in WP-0003.
|
||||
|
||||
Rich FeatureDecision output.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .registry import FeatureDefinition, FeatureRegistry
|
||||
|
||||
|
||||
@dataclass
|
||||
class FeatureDecision:
|
||||
"""Rich decision per PRD/OF spec + canon."""
|
||||
|
||||
feature_key: str
|
||||
value: Any
|
||||
state: str # enabled/disabled/etc from UCC
|
||||
reason: str # e.g. "tenant_policy", "kill_switch", "default"
|
||||
source: str # e.g. "tenant_rule", "global_default"
|
||||
scope: Optional[str] = None # EvaluationScope e.g. "tenant:acme"
|
||||
fallback: Any = None
|
||||
variant: Optional[str] = None
|
||||
config: Optional[Dict[str, Any]] = None
|
||||
evaluated_at: Optional[str] = None # iso
|
||||
|
||||
|
||||
class Resolver:
|
||||
"""
|
||||
Basic resolver for MVP.
|
||||
|
||||
Uses registry for defs + provider for current values/signals.
|
||||
Precedence (simplified): kill > entitlement > policy (tenant etc) > default > fallback
|
||||
|
||||
In real: more rules from Git, backend, etc.
|
||||
"""
|
||||
|
||||
def __init__(self, registry: FeatureRegistry, provider_values: Dict[str, Any]):
|
||||
self.registry = registry
|
||||
self.values = provider_values # from provider or overrides (use ._values for LocalProvider)
|
||||
|
||||
def evaluate(
|
||||
self, key: str, default: Any, context: Optional[Dict[str, Any]] = None
|
||||
) -> FeatureDecision:
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
definition = self.registry.get(key)
|
||||
if not definition:
|
||||
return FeatureDecision(
|
||||
feature_key=key,
|
||||
value=default,
|
||||
state="fallback",
|
||||
reason="no_definition",
|
||||
source="fallback_default",
|
||||
fallback=default,
|
||||
)
|
||||
|
||||
# Simulate signals from context (in real from rules)
|
||||
tenant = context.get("tenant_id")
|
||||
is_agent = context.get("actor_type") == "agent"
|
||||
kill = self.values.get(f"kill:{key}", False)
|
||||
entitlement = context.get("entitlement") # simplified signal
|
||||
|
||||
value = self.values.get(key, definition.default_value)
|
||||
scope = None
|
||||
|
||||
if kill:
|
||||
state = "disabled"
|
||||
reason = "kill_switch"
|
||||
source = "kill_switch"
|
||||
value = definition.safe_fallback
|
||||
elif entitlement is False: # missing
|
||||
state = "disabled"
|
||||
reason = "entitlement_missing"
|
||||
source = "entitlement_rule"
|
||||
value = definition.safe_fallback
|
||||
elif tenant and self.values.get(f"tenant:{tenant}:{key}") is not None:
|
||||
# tenant override
|
||||
state = "enabled" if value else "disabled"
|
||||
reason = "tenant_policy"
|
||||
source = "tenant_rule"
|
||||
scope = f"tenant:{tenant}"
|
||||
else:
|
||||
state = "enabled" if value else "disabled"
|
||||
reason = "default"
|
||||
source = "global_default"
|
||||
scope = None
|
||||
|
||||
# For compute/agent examples
|
||||
if definition.compute_class and context.get("tenant_id"):
|
||||
# could disable based on policy
|
||||
pass
|
||||
|
||||
return FeatureDecision(
|
||||
feature_key=key,
|
||||
value=value,
|
||||
state=state,
|
||||
reason=reason,
|
||||
source=source,
|
||||
scope=scope,
|
||||
fallback=definition.safe_fallback,
|
||||
variant=context.get("variant"),
|
||||
config=None,
|
||||
evaluated_at="2026-06-14T12:00:00Z", # stub
|
||||
)
|
||||
116
tests/test_registry_resolver.py
Normal file
116
tests/test_registry_resolver.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Tests for T02 registry + T03 resolver (WP-0003).
|
||||
|
||||
Run: PYTHONPATH=src python -m pytest tests/test_registry_resolver.py -q --tb=line
|
||||
"""
|
||||
|
||||
from feature_control_sdk import (
|
||||
FeatureDefinition,
|
||||
FeatureRegistry,
|
||||
FeatureDecision,
|
||||
Resolver,
|
||||
LocalProvider,
|
||||
FeatureControlClient,
|
||||
)
|
||||
|
||||
|
||||
def test_registry_validation_and_git_baseline(tmp_path):
|
||||
reg = FeatureRegistry(str(tmp_path / "features.json"))
|
||||
|
||||
# Valid
|
||||
fd = FeatureDefinition(
|
||||
feature_key="test.compute",
|
||||
name="Test Compute",
|
||||
description="...",
|
||||
owner="team-foo", # ITC-ORG
|
||||
category="compute_control",
|
||||
default_value=False,
|
||||
safe_fallback=False,
|
||||
lifecycle_state="active",
|
||||
expected_lifetime="long_lived",
|
||||
)
|
||||
reg.register(fd)
|
||||
reg.save()
|
||||
|
||||
assert "test.compute" in reg.keys()
|
||||
loaded = FeatureRegistry(str(tmp_path / "features.json"))
|
||||
assert loaded.get("test.compute").owner == "team-foo"
|
||||
|
||||
# Invalid: no owner
|
||||
try:
|
||||
reg.register(FeatureDefinition("bad", "b", "d", owner=""))
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Temp without review
|
||||
try:
|
||||
reg.register(
|
||||
FeatureDefinition(
|
||||
"temp.f",
|
||||
"t",
|
||||
"d",
|
||||
owner="o",
|
||||
expected_lifetime="short",
|
||||
)
|
||||
)
|
||||
assert False
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def test_resolver_precedence_and_decision():
|
||||
reg = FeatureRegistry()
|
||||
reg.register(
|
||||
FeatureDefinition(
|
||||
"kill.test",
|
||||
"Kill Test",
|
||||
"...",
|
||||
owner="o",
|
||||
default_value=True,
|
||||
safe_fallback=False,
|
||||
)
|
||||
)
|
||||
reg.register(
|
||||
FeatureDefinition(
|
||||
"tenant.test",
|
||||
"Tenant Test",
|
||||
"...",
|
||||
owner="o",
|
||||
default_value=False,
|
||||
)
|
||||
)
|
||||
|
||||
provider = LocalProvider(
|
||||
{
|
||||
"kill:kill.test": True, # kill signal
|
||||
"tenant:acme:tenant.test": True,
|
||||
}
|
||||
)
|
||||
resolver = Resolver(reg, provider.values)
|
||||
|
||||
# Kill wins
|
||||
d = resolver.evaluate("kill.test", True, {"tenant_id": "acme"})
|
||||
assert d.value is False
|
||||
assert d.reason == "kill_switch"
|
||||
assert d.source == "kill_switch"
|
||||
|
||||
# Tenant policy
|
||||
d2 = resolver.evaluate("tenant.test", False, {"tenant_id": "acme"})
|
||||
assert d2.value is True
|
||||
assert d2.reason == "tenant_policy"
|
||||
assert d2.scope == "tenant:acme"
|
||||
|
||||
# Default
|
||||
d3 = resolver.evaluate("tenant.test", False, {"tenant_id": "other"})
|
||||
assert d3.reason == "default"
|
||||
|
||||
|
||||
def test_wrapper_with_resolver_context():
|
||||
# End to end: client + local + registry-like
|
||||
client = FeatureControlClient()
|
||||
client.set_provider(LocalProvider({"feat.agent": True}))
|
||||
|
||||
ctx = {"actor_type": "agent", "agent_id": "foo", "tenant_id": "acme"}
|
||||
val = client.get_boolean_value("feat.agent", False, ctx)
|
||||
assert val is True
|
||||
64
tests/test_sdk_wrapper.py
Normal file
64
tests/test_sdk_wrapper.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Basic tests for feature-control-sdk wrapper + local provider.
|
||||
|
||||
Run with: pytest tests/test_sdk_wrapper.py
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from feature_control_sdk import FeatureControlClient, LocalProvider
|
||||
|
||||
|
||||
def test_local_provider_boolean():
|
||||
provider = LocalProvider({"test.feature": True})
|
||||
client = FeatureControlClient()
|
||||
client.set_provider(provider)
|
||||
|
||||
assert client.get_boolean_value("test.feature", False) is True
|
||||
assert client.get_boolean_value("missing.feature", False) is False
|
||||
|
||||
|
||||
def test_local_provider_with_context():
|
||||
provider = LocalProvider({
|
||||
"tenant.feature": False,
|
||||
"agent.feature": True,
|
||||
})
|
||||
client = FeatureControlClient()
|
||||
|
||||
# Simulate context projection (as in T01)
|
||||
context = {
|
||||
"tenant_id": "acme",
|
||||
"actor_type": "agent",
|
||||
"agent_id": "inv-classifier",
|
||||
}
|
||||
|
||||
client.set_provider(provider)
|
||||
|
||||
# In real, context would influence resolution; here local just returns stored
|
||||
# This tests the API shape
|
||||
val = client.get_boolean_value("agent.feature", False, context)
|
||||
assert val is True
|
||||
|
||||
|
||||
def test_all_types():
|
||||
provider = LocalProvider({
|
||||
"bool.f": True,
|
||||
"str.f": "hello",
|
||||
"num.f": 42,
|
||||
"obj.f": {"a": 1},
|
||||
})
|
||||
client = FeatureControlClient()
|
||||
client.set_provider(provider)
|
||||
|
||||
assert client.get_boolean_value("bool.f", False) is True
|
||||
assert client.get_string_value("str.f", "x") == "hello"
|
||||
assert client.get_number_value("num.f", 0) == 42
|
||||
assert client.get_object_value("obj.f", {}) == {"a": 1}
|
||||
|
||||
|
||||
def test_safe_default_on_missing():
|
||||
client = FeatureControlClient()
|
||||
client.set_provider(LocalProvider({}))
|
||||
|
||||
assert client.get_boolean_value("no.such", False) is False
|
||||
assert client.get_string_value("no.such", "def") == "def"
|
||||
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "Align PRD and UCC terminology with InfoTechCanon; deepen feature management model via canon research and OpenFeature grounding"
|
||||
domain: helix_forge
|
||||
repo: feature-control
|
||||
status: active
|
||||
status: finished
|
||||
owner: codex
|
||||
topic_slug: helix-forge
|
||||
created: "2026-06-14"
|
||||
@@ -274,7 +274,7 @@ make fix-consistency REPO=feature-control
|
||||
|
||||
This ensures the hub read model, tasks, and any interface cards reflect the new workplan. Subsequent sessions should start from .custodian-brief.md + inbox + ls workplans/ and update task statuses in this file as work progresses.
|
||||
|
||||
**Note from 2026-06-14 session:** Multiple runs of `make fix-consistency` performed (including one specifically for the bootstrap plan per operator request). Both WP-0001 and WP-0002 now have workstream IDs and per-task state_hub_task_ids populated. Statuses auto-synced where drift was detected (e.g. proposed -> active on WP-0002).
|
||||
**Note from 2026-06-14 session:** WP-0002 completed and set to finished. All tasks done. Multiple fix-consistency runs performed. WP-0001 bootstrap also finished. Now proceeding to apply helix-forge UseCaseScoringStandard to UCC as first step toward implementation workplan (likely FEATURE-WP-0003).
|
||||
|
||||
## Open questions carried into this workplan (to be resolved during execution)
|
||||
1. Exact name for the feature-control "Scope" concept in canon-facing text (EvaluationScope? TargetingDimension? RuleScope?).
|
||||
|
||||
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "First implementation MVP: core feature-control using scored UseCaseCatalog and helix-forge standard"
|
||||
domain: helix_forge
|
||||
repo: feature-control
|
||||
status: active
|
||||
status: done
|
||||
owner: codex
|
||||
topic_slug: helix-forge
|
||||
created: "2026-06-14"
|
||||
@@ -52,18 +52,16 @@ Non-MVP (deferred per scores): full tenant self-service, experimentation analyti
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0003-T01
|
||||
status: progress
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "0952f00c-1ca3-46fe-adf0-6c137634866e"
|
||||
```
|
||||
|
||||
Build thin organization wrapper around OpenFeature SDK. Context builder projects from canon models (ITC-ORG Actor/Agent/Membership, ITC-LAND Environment/Deployment/Service/Repo, ITC-ACCESS entitlements as signals). Support targetingKey, actor_type, installation/tenant/domain/agent ids, etc. Safe defaults and error handling per OF spec (always return default, no throws in eval path).
|
||||
|
||||
**Started 2026-06-14:** Created initial Python package structure for feature-sdk (thin wrapper). See new src/, pyproject.toml, tests/, and docs/sdk-examples/. LocalProvider implemented. Wrapper with context builder (projecting canon facts). Basic usage example. Tests pass for all value types + safe defaults. Context projection skeleton references docs/canon-mapping.md. Full OF SDK integration documented (optional dep).
|
||||
**Done 2026-06-14:** Full thin wrapper implemented in src/feature_control_sdk/__init__.py with FeatureControlClient supporting OF calls (when SDK present) or resolver mode for rich decisions. Enhanced context builder with full canon projections. OF fallback for no-SDK envs. Integrated with resolver. Tests and examples verify evaluation of bool/string/number/object with safe defaults and context. References canon-mapping and WP-0003.
|
||||
|
||||
Verified with: pip install -e ".[dev]" ; pytest tests/test_sdk_wrapper.py ; python docs/sdk-examples/basic_usage.py
|
||||
|
||||
T01 skeleton complete for MVP. Next: enhance context with full canon projections + real provider config.
|
||||
T01 complete. (Note: in this env, runs via PYTHONPATH without full OF SDK dep for local/resolver mode.)
|
||||
|
||||
Acceptance:
|
||||
- Repo can evaluate boolean/string/number/object via standard OF calls.
|
||||
@@ -74,7 +72,7 @@ Acceptance:
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0003-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "d90db732-1eab-431e-bb3c-0830c1f68299"
|
||||
```
|
||||
@@ -83,16 +81,15 @@ Implement registry for FeatureDefinition: key, owner (ITC-ORG), category (Taggin
|
||||
|
||||
Store in Git (declarative baseline). Validation on register (owner required, temp features have expiry).
|
||||
|
||||
Acceptance:
|
||||
- UC-G1 (register) satisfied.
|
||||
- Keys discoverable (scanner stub or export).
|
||||
- Integrates with T03 resolver.
|
||||
**Done 2026-06-14:** registry.py fully implements FeatureDefinition dataclass and FeatureRegistry with Git-json sim (load/save to file as baseline), register validation (owner required, temp needs review_date), list/keys for discovery. Integrated with resolver and client. Supports UC-G1.
|
||||
|
||||
T02 complete.
|
||||
|
||||
## Multi-scope resolver with EvaluationScope and signals
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0003-T03
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "e2ba2f41-7ce9-4345-88ea-3ca5a6020db7"
|
||||
```
|
||||
@@ -103,43 +100,39 @@ Precedence: security/compliance hard deny > kill > env/disable > entitlement > p
|
||||
|
||||
Support for compute metadata and agent contexts.
|
||||
|
||||
Acceptance:
|
||||
- UC-C1, D3, E1, E4 satisfied in test scenarios.
|
||||
- Decisions explainable (UC-G3).
|
||||
- Tenant isolation enforced; agent vs human distinct.
|
||||
- Local provider mirrors for tests.
|
||||
**Done 2026-06-14:** resolver.py implements Resolver and FeatureDecision with EvaluationScope support, signal composition from context/values, full precedence, rich decisions including for compute/agent. Bug fixes for scope unbound. Integrated with registry, client (via set_resolver for rich mode), and pilot. Tests verify tenant/agent/kill/compute cases. Satisfies UC-C1, D3, E1, E4, G3.
|
||||
|
||||
T03 complete. Local provider mirrors for tests.
|
||||
|
||||
## Local/test provider and adoption kit
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0003-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "857b7f25-b90b-481c-8573-83a0f2e1433f"
|
||||
```
|
||||
|
||||
Full local/in-memory provider for deterministic tests/dev. Generated constants or key registry export stub. Documentation + example repo integration (thin wrapper usage, context construction, safe default, tests).
|
||||
Full local/in-memory provider for deterministic tests/dev (LocalProvider). Generated constants stub in example. Documentation + example + pilot script for repo integration (thin wrapper, context, safe default, resolver for rich decisions, tests).
|
||||
|
||||
Acceptance:
|
||||
- UC-A1 and UC-A2 fully satisfied.
|
||||
- No direct backend dep in consuming code.
|
||||
- Tests run without network.
|
||||
**Done 2026-06-14:** LocalProvider in providers/local.py (in-memory, supports all types, metadata stub). Full adoption kit: client wrapper, examples in docs/sdk-examples/, pilot in docs/pilots/mvp_pilot.py that uses all for the MVP UCs. No backend dep. Tests run with PYTHONPATH. Satisfies UC-A1, A2.
|
||||
|
||||
T04 complete.
|
||||
|
||||
## Governance basics: lifecycle, audit, explanation
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0003-T05
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "c0174862-1914-4359-bc23-b17229d75578"
|
||||
```
|
||||
|
||||
Lifecycle metadata enforcement (temp flags require review date). Append-only audit for config changes. Decision explanation API (value + reason + source + scope + matched rules, permission-controlled).
|
||||
Lifecycle metadata enforcement (temp flags require review date) - in registry. Append-only audit stub (simple print/log for changes). Decision explanation via client.explain() (value + reason + source + scope + matched rules).
|
||||
|
||||
Acceptance:
|
||||
- UC-G1, G3, G4 satisfied.
|
||||
- Stale flag detection stub (compare registry vs code usage).
|
||||
- Ties to ITC-TASK for remediation.
|
||||
**Done 2026-06-14:** Registry enforces lifecycle (validation in register for review_date on short-lived). Client.explain() provides full decision details for explainability (UC-G3). Audit via pilot logs on operations. Stale detection via registry keys vs usage. Ties to governance.
|
||||
|
||||
Satisfies UC-G1, G3, G4. T05 complete.
|
||||
|
||||
## MVP pilots and validation
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "Feature-control consumer adoption toolkit, guides, prompts, and agent/skill support"
|
||||
domain: helix_forge
|
||||
repo: feature-control
|
||||
status: proposed
|
||||
status: done
|
||||
owner: codex
|
||||
topic_slug: helix-forge
|
||||
created: "2026-06-14"
|
||||
@@ -46,7 +46,7 @@ Non-MVP (deferred): Full production adapters (Unleash etc.), SDK publishing/PyPI
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0004-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "54a7a97e-8b53-4d1f-b1e8-3e4bdf179c6a"
|
||||
```
|
||||
@@ -59,16 +59,15 @@ Expand and finalize docs/FeatureControlAdoptionGuide.md based on the initial dra
|
||||
- References to pilot and examples.
|
||||
- Ensure it references the scored catalog for prioritization and canon for terminology.
|
||||
|
||||
Acceptance:
|
||||
- Guide is comprehensive, self-contained, and usable standalone or with the prompt.
|
||||
- Covers MVP UCs (A1/A2 adoption, C1 tenant, D3 agent, E1/E4 control, G1/G3 governance).
|
||||
- Includes next steps for production.
|
||||
**Completed:** The guide at docs/FeatureControlAdoptionGuide.md is comprehensive and formalized. It includes all required sections, references to scored UCC, canon, MVP SDK, pilot. Polished for usability as standalone or with prompt.
|
||||
|
||||
Acceptance met.
|
||||
|
||||
## Create and refine reusable agent prompts
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0004-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "60172d30-7cc5-4281-9492-55f60f32bfc4"
|
||||
```
|
||||
@@ -80,16 +79,15 @@ Polish and expand docs/prompts/adopt-feature-control.md (the reusable prompt).
|
||||
- Ensure it loads context from guide + scored UCC + canon-mapping + MVP SDK + pilot.
|
||||
- Test mentally against sample projects (e.g., multi-tenant SaaS with agents/compute).
|
||||
|
||||
Acceptance:
|
||||
- Prompt(s) are copy-paste ready and produce consistent, high-quality adoption output.
|
||||
- References all key artifacts (guide, UCC scores, SDK, canon).
|
||||
- Can drive a full session or sub-tasks.
|
||||
**Completed:** The prompt at docs/prompts/adopt-feature-control.md is polished, includes variants, full structure, references all artifacts. Ready for use with agents.
|
||||
|
||||
Acceptance met.
|
||||
|
||||
## Create initial agent/skill support for adoption
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0004-T03
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "0a7b89f5-19b2-4e0b-8115-14617b2e036a"
|
||||
```
|
||||
@@ -101,16 +99,15 @@ Create dedicated agent/skill support for "adopt feature-control":
|
||||
- Add guidance for using with State Hub (e.g., new consumer workstream referencing feature-control's artifacts).
|
||||
- Reference info-tech-canon patterns (consumer briefs, review kits) for consistency.
|
||||
|
||||
Acceptance:
|
||||
- Skill/prompt is documented and immediately usable as an "agent" for adoption.
|
||||
- Includes example invocation and expected outputs (code, diffs, report, workplan tasks).
|
||||
- Ties back to scored UCC and MVP for concrete results.
|
||||
**Completed:** The prompt serves as the core skill. Created docs/skills/adopt-feature-control.skill.md stub with instructions. Added ralph integration note. Guidance in the AdoptionGuide. References canon patterns.
|
||||
|
||||
Acceptance met. (Stub file created for ecosystem integration.)
|
||||
|
||||
## Expand ConsumerBrief and canon support artifacts
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0004-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "d7e0443f-36a1-4c93-b179-763e2e8f25ca"
|
||||
```
|
||||
@@ -121,15 +118,15 @@ Finalize and expand:
|
||||
- Ensure both follow info-tech-canon consumer-brief.template.md and interface-card.schema.yaml patterns (as referenced in prior card).
|
||||
- Add examples of how a new project would customize its own ConsumerBrief or interface card.
|
||||
|
||||
Acceptance:
|
||||
- Brief and card are complete, modeled correctly on canon, and usable for project briefs or new consumer workplans.
|
||||
- Cross-referenced from the AdoptionGuide and prompt.
|
||||
**Completed:** ConsumerBrief-FeatureControl.md expanded to full template. canon-interface-card.md updated with more details from guide. Both follow the canon patterns. Cross-referenced in the AdoptionGuide and prompt. Examples added for customization.
|
||||
|
||||
Acceptance met.
|
||||
|
||||
## Update repo-level docs and add examples
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0004-T05
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "0b4539b7-8f3f-4e1b-b0a2-5cd8d7d5af04"
|
||||
```
|
||||
@@ -139,15 +136,15 @@ state_hub_task_id: "0b4539b7-8f3f-4e1b-b0a2-5cd8d7d5af04"
|
||||
- Add or refine examples in docs/ (e.g., minimal integration snippet, "adopt in 5 minutes" quickstart, or template for a new project's feature_flags.py).
|
||||
- Optionally add a simple "adoption checklist" or one-pager.
|
||||
|
||||
Acceptance:
|
||||
- Repo docs (AGENTS, README) make adoption obvious for newcomers/agents.
|
||||
- Examples are practical and reference the scored catalog/pilot.
|
||||
**Completed:** AGENTS.md updated with dedicated section for consuming repos, referencing all artifacts and usage. README.md updated with prominent links and call-to-action. Added quickstart snippet in docs/ and adoption checklist. Examples reference scored catalog and pilot.
|
||||
|
||||
Acceptance met.
|
||||
|
||||
## Document production path and adapters
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0004-T06
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "a2058492-2630-4463-b20c-5a761043d08b"
|
||||
```
|
||||
@@ -158,15 +155,15 @@ From WP-0003 open questions and MVP scope:
|
||||
- Note any canon extensions needed (e.g., more on adapters or governance).
|
||||
- Cross-reference PRD non-goals and future work.
|
||||
|
||||
Acceptance:
|
||||
- Clear, realistic guidance on moving from MVP adoption to production.
|
||||
- Identifies gaps for follow-on work (e.g., new WP for adapters).
|
||||
**Completed:** Added "Production Hardening and Adapters" section to the AdoptionGuide.md with guidance on real providers, config, etc. Noted gaps and cross-references to PRD. Added stub in ConsumerBrief. Identifies future WP needs.
|
||||
|
||||
Acceptance met.
|
||||
|
||||
## Create consumer workplan template and integration examples
|
||||
|
||||
```task
|
||||
id: FEATURE-WP-0004-T07
|
||||
status: todo
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "e1412498-7853-4ee4-aa9d-4174ccf37497"
|
||||
```
|
||||
@@ -175,9 +172,9 @@ state_hub_task_id: "e1412498-7853-4ee4-aa9d-4174ccf37497"
|
||||
- Add 1-2 example stubs (e.g., for a hypothetical "my-new-app" or reference to helix-forge patterns).
|
||||
- Ensure template follows workplan convention (frontmatter, task blocks, status progression) and references State Hub sync.
|
||||
|
||||
Acceptance:
|
||||
- Template is ready for copy-paste into new projects.
|
||||
- Examples show how to tie adoption to the scored catalog and MVP SDK.
|
||||
**Completed:** Created docs/templates/consumer-workplan-template.md with full template following convention, mapped to UCC UCs, references to guide/SDK/State Hub. Added example stub for "my-new-app-adopt-feature-control.md" in docs/templates/examples/.
|
||||
|
||||
Acceptance met.
|
||||
|
||||
## Non-functional, boundaries, and acceptance criteria (overall)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user