generated from coulomb/repo-seed
Evidence with supportive metadata
This commit is contained in:
@@ -102,8 +102,12 @@ CREATE TABLE IF NOT EXISTS candidate_evidence (
|
||||
repository_id INTEGER NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,
|
||||
analysis_run_id INTEGER NOT NULL REFERENCES analysis_runs(id) ON DELETE CASCADE,
|
||||
capability_id INTEGER NOT NULL REFERENCES candidate_capabilities(id) ON DELETE CASCADE,
|
||||
target_kind TEXT NOT NULL DEFAULT 'capability',
|
||||
target_id INTEGER,
|
||||
type TEXT NOT NULL,
|
||||
reference TEXT NOT NULL,
|
||||
reference_kind TEXT NOT NULL DEFAULT 'source',
|
||||
reference_id INTEGER,
|
||||
strength TEXT NOT NULL DEFAULT 'medium',
|
||||
status TEXT NOT NULL DEFAULT 'candidate',
|
||||
source_refs TEXT NOT NULL DEFAULT '[]',
|
||||
@@ -156,8 +160,12 @@ CREATE TABLE IF NOT EXISTS approved_evidence (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
repository_id INTEGER NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,
|
||||
capability_id INTEGER NOT NULL REFERENCES approved_capabilities(id) ON DELETE CASCADE,
|
||||
target_kind TEXT NOT NULL DEFAULT 'capability',
|
||||
target_id INTEGER,
|
||||
type TEXT NOT NULL,
|
||||
reference TEXT NOT NULL,
|
||||
reference_kind TEXT NOT NULL DEFAULT 'source',
|
||||
reference_id INTEGER,
|
||||
strength TEXT NOT NULL DEFAULT 'medium',
|
||||
source_refs TEXT NOT NULL DEFAULT '[]',
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
|
||||
@@ -145,6 +145,10 @@ class CandidateEvidence:
|
||||
strength: str
|
||||
status: str
|
||||
source_refs: list[SourceReference]
|
||||
target_kind: str = "capability"
|
||||
target_id: int | None = None
|
||||
reference_kind: str = "source"
|
||||
reference_id: int | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -200,6 +204,10 @@ class Evidence:
|
||||
reference: str
|
||||
strength: str
|
||||
source_refs: list[SourceReference] = field(default_factory=list)
|
||||
target_kind: str = "capability"
|
||||
target_id: int | None = None
|
||||
reference_kind: str = "source"
|
||||
reference_id: int | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -1162,6 +1162,10 @@ class RegistryService:
|
||||
type: str,
|
||||
reference: str,
|
||||
strength: str = "medium",
|
||||
target_kind: str = "capability",
|
||||
target_id: int | None = None,
|
||||
reference_kind: str = "source",
|
||||
reference_id: int | None = None,
|
||||
) -> int:
|
||||
self.store.ensure_capability(repository_id, capability_id)
|
||||
return self.store.create_evidence(
|
||||
@@ -1170,6 +1174,10 @@ class RegistryService:
|
||||
type=type,
|
||||
reference=reference,
|
||||
strength=strength,
|
||||
target_kind=target_kind,
|
||||
target_id=target_id,
|
||||
reference_kind=reference_kind,
|
||||
reference_id=reference_id,
|
||||
)
|
||||
|
||||
def update_evidence(
|
||||
@@ -1180,6 +1188,10 @@ class RegistryService:
|
||||
type: str | None = None,
|
||||
reference: str | None = None,
|
||||
strength: str | None = None,
|
||||
target_kind: str | None = None,
|
||||
target_id: int | None = None,
|
||||
reference_kind: str | None = None,
|
||||
reference_id: int | None = None,
|
||||
) -> RepositoryAbilityMap:
|
||||
self.store.update_evidence(
|
||||
repository_id,
|
||||
@@ -1187,6 +1199,10 @@ class RegistryService:
|
||||
type=type,
|
||||
reference=reference,
|
||||
strength=strength,
|
||||
target_kind=target_kind,
|
||||
target_id=target_id,
|
||||
reference_kind=reference_kind,
|
||||
reference_id=reference_id,
|
||||
)
|
||||
return self.store.get_ability_map(repository_id)
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ class RegistryStore:
|
||||
connection.executescript(migration_path.read_text(encoding="utf-8"))
|
||||
self._ensure_content_chunks_table(connection)
|
||||
self._ensure_approved_source_ref_columns(connection)
|
||||
self._ensure_evidence_relationship_columns(connection)
|
||||
self._ensure_expectation_gaps_table(connection)
|
||||
|
||||
def connect(self) -> sqlite3.Connection:
|
||||
@@ -70,6 +71,41 @@ class RegistryStore:
|
||||
f"ALTER TABLE {table} ADD COLUMN source_refs TEXT NOT NULL DEFAULT '[]'"
|
||||
)
|
||||
|
||||
def _ensure_evidence_relationship_columns(
|
||||
self,
|
||||
connection: sqlite3.Connection,
|
||||
) -> None:
|
||||
for table in ("candidate_evidence", "approved_evidence"):
|
||||
columns = {
|
||||
row["name"]
|
||||
for row in connection.execute(f"PRAGMA table_info({table})").fetchall()
|
||||
}
|
||||
if "target_kind" not in columns:
|
||||
connection.execute(
|
||||
f"ALTER TABLE {table} ADD COLUMN target_kind TEXT NOT NULL DEFAULT 'capability'"
|
||||
)
|
||||
if "target_id" not in columns:
|
||||
connection.execute(f"ALTER TABLE {table} ADD COLUMN target_id INTEGER")
|
||||
if "reference_kind" not in columns:
|
||||
connection.execute(
|
||||
f"ALTER TABLE {table} ADD COLUMN reference_kind TEXT NOT NULL DEFAULT 'source'"
|
||||
)
|
||||
if "reference_id" not in columns:
|
||||
connection.execute(
|
||||
f"ALTER TABLE {table} ADD COLUMN reference_id INTEGER"
|
||||
)
|
||||
connection.execute(
|
||||
f"""
|
||||
UPDATE {table}
|
||||
SET target_kind = COALESCE(NULLIF(target_kind, ''), 'capability'),
|
||||
target_id = COALESCE(target_id, capability_id),
|
||||
reference_kind = COALESCE(NULLIF(reference_kind, ''), 'source')
|
||||
WHERE target_id IS NULL
|
||||
OR target_kind = ''
|
||||
OR reference_kind = ''
|
||||
"""
|
||||
)
|
||||
|
||||
def _ensure_content_chunks_table(self, connection: sqlite3.Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
@@ -355,16 +391,21 @@ class RegistryStore:
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO candidate_evidence
|
||||
(repository_id, analysis_run_id, capability_id, type,
|
||||
reference, strength, source_refs)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
(repository_id, analysis_run_id, capability_id,
|
||||
target_kind, target_id, type, reference,
|
||||
reference_kind, reference_id, strength, source_refs)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
repository_id,
|
||||
analysis_run_id,
|
||||
capability_id,
|
||||
"capability",
|
||||
capability_id,
|
||||
evidence.type,
|
||||
evidence.reference,
|
||||
"source",
|
||||
None,
|
||||
evidence.strength,
|
||||
self._source_refs_to_json(evidence.source_refs),
|
||||
),
|
||||
@@ -409,7 +450,8 @@ class RegistryStore:
|
||||
).fetchall()
|
||||
evidence_rows = connection.execute(
|
||||
"""
|
||||
SELECT id, capability_id, type, reference, strength, status, source_refs
|
||||
SELECT id, capability_id, target_kind, target_id, type, reference,
|
||||
reference_kind, reference_id, strength, status, source_refs
|
||||
FROM candidate_evidence
|
||||
WHERE repository_id = ? AND analysis_run_id = ?
|
||||
ORDER BY id
|
||||
@@ -442,6 +484,10 @@ class RegistryStore:
|
||||
strength=row["strength"],
|
||||
status=row["status"],
|
||||
source_refs=self._source_refs_from_json(row["source_refs"]),
|
||||
target_kind=row["target_kind"],
|
||||
target_id=row["target_id"],
|
||||
reference_kind=row["reference_kind"],
|
||||
reference_id=row["reference_id"],
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1709,20 +1755,30 @@ class RegistryStore:
|
||||
type: str,
|
||||
reference: str,
|
||||
strength: str,
|
||||
target_kind: str = "capability",
|
||||
target_id: int | None = None,
|
||||
reference_kind: str = "source",
|
||||
reference_id: int | None = None,
|
||||
source_refs: list[SourceReference] | None = None,
|
||||
) -> int:
|
||||
target_id = capability_id if target_id is None else target_id
|
||||
with self.connect() as connection:
|
||||
cursor = connection.execute(
|
||||
"""
|
||||
INSERT INTO approved_evidence
|
||||
(repository_id, capability_id, type, reference, strength, source_refs)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
(repository_id, capability_id, target_kind, target_id, type,
|
||||
reference, reference_kind, reference_id, strength, source_refs)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
repository_id,
|
||||
capability_id,
|
||||
target_kind,
|
||||
target_id,
|
||||
type,
|
||||
reference,
|
||||
reference_kind,
|
||||
reference_id,
|
||||
strength,
|
||||
self._source_refs_to_json(source_refs or []),
|
||||
),
|
||||
@@ -1737,6 +1793,10 @@ class RegistryStore:
|
||||
type: str | None = None,
|
||||
reference: str | None = None,
|
||||
strength: str | None = None,
|
||||
target_kind: str | None = None,
|
||||
target_id: int | None = None,
|
||||
reference_kind: str | None = None,
|
||||
reference_id: int | None = None,
|
||||
) -> None:
|
||||
self._update_approved_row(
|
||||
table="approved_evidence",
|
||||
@@ -1747,6 +1807,10 @@ class RegistryStore:
|
||||
"type": type,
|
||||
"reference": reference,
|
||||
"strength": strength,
|
||||
"target_kind": target_kind,
|
||||
"target_id": target_id,
|
||||
"reference_kind": reference_kind,
|
||||
"reference_id": reference_id,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1837,15 +1901,20 @@ class RegistryStore:
|
||||
connection.execute(
|
||||
"""
|
||||
INSERT INTO approved_evidence
|
||||
(repository_id, capability_id, type, reference, strength,
|
||||
(repository_id, capability_id, target_kind, target_id,
|
||||
type, reference, reference_kind, reference_id, strength,
|
||||
source_refs)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
repository_id,
|
||||
approved_capability_id,
|
||||
evidence.target_kind,
|
||||
evidence.target_id or approved_capability_id,
|
||||
evidence.type,
|
||||
evidence.reference,
|
||||
evidence.reference_kind,
|
||||
evidence.reference_id,
|
||||
evidence.strength,
|
||||
self._source_refs_to_json(evidence.source_refs),
|
||||
),
|
||||
@@ -1883,7 +1952,8 @@ class RegistryStore:
|
||||
).fetchall()
|
||||
evidence_rows = connection.execute(
|
||||
"""
|
||||
SELECT id, capability_id, type, reference, strength, source_refs
|
||||
SELECT id, capability_id, target_kind, target_id, type, reference,
|
||||
reference_kind, reference_id, strength, source_refs
|
||||
FROM approved_evidence
|
||||
WHERE repository_id = ?
|
||||
ORDER BY id
|
||||
@@ -1914,6 +1984,10 @@ class RegistryStore:
|
||||
reference=row["reference"],
|
||||
strength=row["strength"],
|
||||
source_refs=self._source_refs_from_json(row["source_refs"]),
|
||||
target_kind=row["target_kind"],
|
||||
target_id=row["target_id"],
|
||||
reference_kind=row["reference_kind"],
|
||||
reference_id=row["reference_id"],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -179,6 +179,10 @@ class EvidenceCreate(BaseModel):
|
||||
type: str
|
||||
reference: str
|
||||
strength: str = "medium"
|
||||
target_kind: str = "capability"
|
||||
target_id: int | None = None
|
||||
reference_kind: str = "source"
|
||||
reference_id: int | None = None
|
||||
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
@@ -188,6 +192,8 @@ class EvidenceCreate(BaseModel):
|
||||
"type": "unit_test",
|
||||
"reference": "tests/test_email_classification.py",
|
||||
"strength": "strong",
|
||||
"target_kind": "capability",
|
||||
"reference_kind": "source",
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -198,6 +204,10 @@ class EvidenceUpdate(BaseModel):
|
||||
type: str | None = None
|
||||
reference: str | None = None
|
||||
strength: str | None = None
|
||||
target_kind: str | None = None
|
||||
target_id: int | None = None
|
||||
reference_kind: str | None = None
|
||||
reference_id: int | None = None
|
||||
|
||||
|
||||
class AnalysisRunCreate(BaseModel):
|
||||
@@ -475,6 +485,10 @@ class CandidateEvidenceResponse(BaseModel):
|
||||
strength: str
|
||||
status: str
|
||||
source_refs: list[SourceReferenceResponse]
|
||||
target_kind: str = "capability"
|
||||
target_id: int | None = None
|
||||
reference_kind: str = "source"
|
||||
reference_id: int | None = None
|
||||
|
||||
|
||||
class CandidateFeatureResponse(BaseModel):
|
||||
@@ -665,6 +679,10 @@ class EvidenceResponse(BaseModel):
|
||||
reference: str
|
||||
strength: str
|
||||
source_refs: list[SourceReferenceResponse]
|
||||
target_kind: str = "capability"
|
||||
target_id: int | None = None
|
||||
reference_kind: str = "source"
|
||||
reference_id: int | None = None
|
||||
|
||||
|
||||
class FeatureResponse(BaseModel):
|
||||
|
||||
@@ -592,8 +592,12 @@ def repository_detail(
|
||||
<form class="stack" method="post" action="/ui/repos/{repository_id}/evidence">
|
||||
<h3>Add Capability Support</h3>
|
||||
<label>Supported capability ID <input name="capability_id" type="number" min="1" required></label>
|
||||
<label>Supported characteristic kind <input name="target_kind" value="capability" required></label>
|
||||
<label>Supported characteristic ID <input name="target_id" type="number" min="1" placeholder="Defaults to supported capability ID"></label>
|
||||
<label>Support type <input name="type" placeholder="fact, documentation, test, example, feature" required></label>
|
||||
<label>Reference <input name="reference" placeholder="Observed fact, file, or lower-level characteristic" required></label>
|
||||
<label>Reference kind <input name="reference_kind" value="source" placeholder="source, fact, feature, capability"></label>
|
||||
<label>Reference ID <input name="reference_id" type="number" min="1" placeholder="Optional fact or characteristic ID"></label>
|
||||
<label>Strength <input name="strength" value="medium" required></label>
|
||||
<button type="submit">Add Support</button>
|
||||
</form>
|
||||
@@ -724,6 +728,10 @@ def create_evidence_from_form(
|
||||
type: str = Form(...),
|
||||
reference: str = Form(...),
|
||||
strength: str = Form("medium"),
|
||||
target_kind: str = Form("capability"),
|
||||
target_id: int | None = Form(default=None),
|
||||
reference_kind: str = Form("source"),
|
||||
reference_id: int | None = Form(default=None),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.add_evidence(
|
||||
@@ -732,6 +740,10 @@ def create_evidence_from_form(
|
||||
type=type,
|
||||
reference=reference,
|
||||
strength=strength,
|
||||
target_kind=target_kind,
|
||||
target_id=target_id,
|
||||
reference_kind=reference_kind,
|
||||
reference_id=reference_id,
|
||||
)
|
||||
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
|
||||
|
||||
@@ -836,6 +848,10 @@ def edit_evidence_from_form(
|
||||
type: str = Form(...),
|
||||
reference: str = Form(...),
|
||||
strength: str = Form("medium"),
|
||||
target_kind: str = Form("capability"),
|
||||
target_id: int | None = Form(default=None),
|
||||
reference_kind: str = Form("source"),
|
||||
reference_id: int | None = Form(default=None),
|
||||
service: RegistryService = Depends(get_service),
|
||||
) -> RedirectResponse:
|
||||
service.update_evidence(
|
||||
@@ -844,6 +860,10 @@ def edit_evidence_from_form(
|
||||
type=type,
|
||||
reference=reference,
|
||||
strength=strength,
|
||||
target_kind=target_kind,
|
||||
target_id=target_id,
|
||||
reference_kind=reference_kind,
|
||||
reference_id=reference_id,
|
||||
)
|
||||
return RedirectResponse(f"/ui/repos/{repository_id}", status_code=303)
|
||||
|
||||
@@ -2795,15 +2815,25 @@ def render_approved_feature(feature: dict, repository_id: int) -> str:
|
||||
|
||||
|
||||
def render_approved_evidence(evidence: dict, repository_id: int) -> str:
|
||||
target_kind = escape(str(evidence.get("target_kind") or "capability"))
|
||||
target_id = evidence.get("target_id")
|
||||
reference_kind = escape(str(evidence.get("reference_kind") or "source"))
|
||||
reference_id = evidence.get("reference_id")
|
||||
return f"""
|
||||
<li>
|
||||
<strong>{escape(evidence["type"])}</strong>
|
||||
<span class="pill">{escape(evidence["strength"])}</span>
|
||||
<span class="pill">supports {target_kind}{f' #{target_id}' if target_id else ''}</span>
|
||||
<span class="pill">references {reference_kind}{f' #{reference_id}' if reference_id else ''}</span>
|
||||
<span class="source">{escape(evidence["reference"])}</span>
|
||||
{render_sources(evidence.get("source_refs", []))}
|
||||
<form class="stack" method="post" action="/ui/repos/{repository_id}/evidence/{evidence['id']}/edit">
|
||||
<label>Supported characteristic kind <input name="target_kind" value="{target_kind}" required></label>
|
||||
<label>Supported characteristic ID <input name="target_id" type="number" min="1" value="{target_id or ''}"></label>
|
||||
<label>Support type <input name="type" value="{escape(evidence['type'])}" required></label>
|
||||
<label>Reference <input name="reference" value="{escape(evidence['reference'])}" required></label>
|
||||
<label>Reference kind <input name="reference_kind" value="{reference_kind}" required></label>
|
||||
<label>Reference ID <input name="reference_id" type="number" min="1" value="{reference_id or ''}"></label>
|
||||
<label>Strength <input name="strength" value="{escape(evidence['strength'])}" required></label>
|
||||
<button class="secondary" type="submit">Save Support</button>
|
||||
</form>
|
||||
|
||||
@@ -75,6 +75,8 @@ def test_manual_registry_builds_ability_map(tmp_path):
|
||||
type="unit_test",
|
||||
reference="tests/test_email_classification.py",
|
||||
strength="strong",
|
||||
reference_kind="fact",
|
||||
reference_id=42,
|
||||
)
|
||||
|
||||
ability_map = service.ability_map(repository.id)
|
||||
@@ -86,6 +88,10 @@ def test_manual_registry_builds_ability_map(tmp_path):
|
||||
assert capability.inputs == ["subject", "body"]
|
||||
assert capability.features[0].location == "src/routes/classify_email.py"
|
||||
assert capability.evidence[0].strength == "strong"
|
||||
assert capability.evidence[0].target_kind == "capability"
|
||||
assert capability.evidence[0].target_id == capability_id
|
||||
assert capability.evidence[0].reference_kind == "fact"
|
||||
assert capability.evidence[0].reference_id == 42
|
||||
|
||||
|
||||
def test_manual_registry_updates_and_deletes_approved_entries(tmp_path):
|
||||
@@ -127,6 +133,8 @@ def test_manual_registry_updates_and_deletes_approved_entries(tmp_path):
|
||||
repository.id,
|
||||
evidence_id,
|
||||
strength="strong",
|
||||
reference_kind="feature",
|
||||
reference_id=feature_id,
|
||||
)
|
||||
|
||||
ability = ability_map.abilities[0]
|
||||
@@ -137,6 +145,8 @@ def test_manual_registry_updates_and_deletes_approved_entries(tmp_path):
|
||||
assert capability.outputs == ["response"]
|
||||
assert capability.features[0].location == "src/api.py"
|
||||
assert capability.evidence[0].strength == "strong"
|
||||
assert capability.evidence[0].reference_kind == "feature"
|
||||
assert capability.evidence[0].reference_id == feature_id
|
||||
|
||||
service.delete_feature(repository.id, feature_id)
|
||||
service.delete_evidence(repository.id, evidence_id)
|
||||
|
||||
@@ -34,6 +34,12 @@ def test_initialize_is_idempotent_and_applies_expected_columns(tmp_path):
|
||||
|
||||
assert "source_refs" in feature_columns
|
||||
assert "source_refs" in evidence_columns
|
||||
assert {
|
||||
"target_kind",
|
||||
"target_id",
|
||||
"reference_kind",
|
||||
"reference_id",
|
||||
} <= evidence_columns
|
||||
assert "content_chunks" in tables
|
||||
assert "expectation_gaps" in tables
|
||||
|
||||
|
||||
@@ -1609,6 +1609,8 @@ def test_ui_manual_registry_entry_loop(tmp_path):
|
||||
assert detail_response.status_code == 200
|
||||
assert "Manual Characteristic Tuning" in detail_response.text
|
||||
assert "Add Capability Support" in detail_response.text
|
||||
assert "Supported characteristic kind" in detail_response.text
|
||||
assert "Reference kind" in detail_response.text
|
||||
|
||||
ability_response = client.post(
|
||||
f"{repository_path}/abilities",
|
||||
@@ -1660,6 +1662,7 @@ def test_ui_manual_registry_entry_loop(tmp_path):
|
||||
"capability_id": str(capability_id),
|
||||
"type": "documentation",
|
||||
"reference": "README.md",
|
||||
"reference_kind": "source",
|
||||
"strength": "medium",
|
||||
},
|
||||
follow_redirects=False,
|
||||
@@ -1671,6 +1674,8 @@ def test_ui_manual_registry_entry_loop(tmp_path):
|
||||
assert "Manual Capability" in detail_response.text
|
||||
assert "Manual API" in detail_response.text
|
||||
assert "README.md" in detail_response.text
|
||||
assert "supports capability" in detail_response.text
|
||||
assert "references source" in detail_response.text
|
||||
assert "ID " in detail_response.text
|
||||
assert "Save Ability" in detail_response.text
|
||||
|
||||
@@ -1719,6 +1724,10 @@ def test_ui_manual_registry_entry_loop(tmp_path):
|
||||
data={
|
||||
"type": "test",
|
||||
"reference": "tests/test_manual.py",
|
||||
"target_kind": "capability",
|
||||
"target_id": str(capability_id),
|
||||
"reference_kind": "feature",
|
||||
"reference_id": str(feature_id),
|
||||
"strength": "strong",
|
||||
},
|
||||
follow_redirects=False,
|
||||
@@ -1730,6 +1739,7 @@ def test_ui_manual_registry_entry_loop(tmp_path):
|
||||
assert "Edited Manual Capability" in detail_response.text
|
||||
assert "Edited Manual API" in detail_response.text
|
||||
assert "tests/test_manual.py" in detail_response.text
|
||||
assert f"references feature #{feature_id}" in detail_response.text
|
||||
|
||||
delete_feature_response = client.post(
|
||||
f"{repository_path}/features/{feature_id}/delete",
|
||||
|
||||
@@ -226,3 +226,8 @@ capability, and allows manual tuning of evidence in a way that makes the support
|
||||
relationship clear. The data model has an additive path toward characteristic
|
||||
references so existing approved/candidate workflows continue to work while
|
||||
future iterations can link evidence to facts or deeper characteristics.
|
||||
|
||||
Implementation note 2026-04-29: approved and candidate evidence now carry
|
||||
additive support metadata: `target_kind`, `target_id`, `reference_kind`, and
|
||||
`reference_id`. Existing capability-bound evidence remains compatible, while the
|
||||
UI exposes these fields as supported-characteristic and reference metadata.
|
||||
|
||||
Reference in New Issue
Block a user