Skip to content

Commit c5ef954

Browse files
Replace recursive artifact provenance scanner with explicit declarations (#1160)
* Update jacobian-math skill to remove mode from wire examples Drop `mode: "EXPLORE"` from the producer wire example, remove `in EXPLORE mode` / `in VERIFY mode` qualifiers from payload descriptions, and replace "Use installed VERIFY" with "Use installed checker tools (*.verify, lean.check, ...)". Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * Replace recursive artifact provenance scanner with explicit declarations (#1108) AtomicServiceAdapter previously derived CapabilityResult.artifact_uris by recursively traversing the entire serialized domain output and treating every string starting with artifact://sha256/ as provenance. A URI in a debug message, rejected candidate, or example field would be promoted to artifact_uris. Replace the recursive scanner with an explicit artifact_references callback on AtomicServiceAdapter. Each adapter registration declares which typed result fields carry semantically relevant artifact URIs. Verification bindings now consume explicit references plus record lineage, not string scanning. - Add artifact_references callback parameter to AtomicServiceAdapter - Delete _artifact_uris() recursive reflection function - Migrate all built-in atomic adapter registrations (artifact.put, claim.validate, evaluate.batch, witness.find, witness.verify, certificate.verify, shrink.run, structure.canonicalize, transform.apply, transform.verify, polytope.separate, parameter.region.promote) to declare explicit references - Add adversarial fixtures proving URIs in debug/rejected/example fields cannot enter artifact_uris - Sync packaged jacobian-math skill with repository copy and update codex visibility tests for the prior mode-removal skill edits Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 2df807e commit c5ef954

6 files changed

Lines changed: 198 additions & 27 deletions

File tree

npm/skills/jacobian-math/SKILL.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,30 @@ Keep decomposition and routing decisions agent-owned; composing already-known
2222
supporting operations remains allowed when clearer.
2323
Do not call Jacobian for definitions, formatting, or non-execution tasks.
2424
25-
No discovery for stable producers: `{"capability_id":"<id>","mode":"EXPLORE","payload":<JSON>}`
26-
(not `COMPUTE`/`input`). Payloads:
25+
No discovery for stable producers: `{"capability_id":"<id>","input":<JSON>}`.
26+
Payloads:
2727
2828
- `integer.compute.gcd`, `integer.compute.lcm`, or `integer.compute.extended_gcd`:
2929
`{"left":"84","right":"30"}`.
3030
- `matrix.determinant.compute` or `matrix.rank.compute`:
3131
`{"matrix":{"domain":"QQ","entries":[[{"num":"1","den":"1"}]]}}`.
32-
- `polynomial.compute.gcd` in `EXPLORE` mode: payload keys are `left` and
32+
- `polynomial.compute.gcd`: payload keys are `left` and
3333
`right`; each value has shape
3434
`{"polynomial_schema_version":"1","domain":"QQ","variables":["x"],"polynomial":{"terms":[{"coefficient":{"num":"1","den":"1"},"exponents":[2]}]}}`.
3535
- For expression normalization, inspect the known
3636
`polynomial.expression.normalize` contract directly.
37-
- `matrix.determinant.verify` in `VERIFY` mode for an independent check:
37+
- `matrix.determinant.verify` for an independent check:
3838
`{"determinant_uri":"<determinant_uri from compute output>"}`.
3939
- `combinatorics.cyclic_difference_set.extension.decide`:
4040
`{"base_elements":["1","2","4","8","13"],"target_order":7}`.
4141
`combinatorics.cyclic_difference_set.extension.verify` uses
42-
`{"input":<same payload>,"candidate":<producer output.result>}` in `VERIFY` mode.
42+
`{"input":<same payload>,"candidate":<producer output.result>}`.
4343
4444
For other outcomes, query `math.find` by plain-language outcome; no ID is
4545
required. Use low `limit` only for query search, never with `capability_id`. For
4646
a selected operation's schema, call
47-
`math.find({"capability_id":"<exact-id>","view":"CONTRACT"})`; never send
48-
`mode: "CONTRACT"` to `math.run` or put `CONTRACT` in a query. A card's
47+
`math.find({"capability_id":"<exact-id>","view":"CONTRACT"})`; never put
48+
`CONTRACT` in a query. A card's
4949
`invocation_example`, or required top-level fields, may be enough.
5050
5151
Add no domain filter unless its installed spelling is known. Follow exposed
@@ -60,8 +60,9 @@ obligations plus artifact and verification-record URIs.
6060
Keep representation, decomposition, composition, iteration, verification
6161
timing, and stopping decisions agent-owned.
6262
63-
Model-authored work is not independent evidence. Use installed `VERIFY` when
64-
requested; a writable path or schema alone is not authorization. Task-level
63+
Model-authored work is not independent evidence. Use installed checker tools
64+
(`*.verify`, `lean.check`, …) when independent verification is requested; a
65+
writable path or schema alone is not authorization. Task-level
6566
`VERIFIED` requires exact record bytes, result assurance `VERIFIED`, required
6667
task authorization and bindings are preserved, and a contract-authorized
6768
checker identity, digest, or Jacobian record type. Otherwise claim the highest

src/jacobian/atomic_capabilities.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def __init__(
7979
output_schema: dict[str, Any],
8080
invoke: Callable[[dict[str, Any]], Any],
8181
store: ArtifactRepository,
82+
artifact_references: Callable[[Any], tuple[str, ...]] | None = None,
8283
unverified_assurance_level: CapabilityAssuranceLevel = (
8384
CapabilityAssuranceLevel.COMPUTED
8485
),
@@ -103,6 +104,7 @@ def __init__(
103104
)
104105
self._invoke = invoke
105106
self._store = store
107+
self._artifact_references = artifact_references
106108
self._unverified_assurance_level = unverified_assurance_level
107109
self._unverified_basis = unverified_basis
108110

@@ -115,7 +117,11 @@ def invoke(self, request: CapabilityRequest) -> CapabilityResult:
115117
output = _dump(value)
116118
execution = _execution(value)
117119
record_uri = _verified_record_uri(value)
118-
artifact_uris = _artifact_uris(output)
120+
artifact_uris = (
121+
self._artifact_references(value)
122+
if self._artifact_references is not None
123+
else ()
124+
)
119125
verification_artifacts: tuple[str, ...] | None = None
120126
if record_uri is not None:
121127
verification_artifacts = _verification_bindings(
@@ -202,6 +208,7 @@ def _adapter(**kwargs: Any) -> AtomicServiceAdapter:
202208
parents=tuple(p.get("parents", ())),
203209
summary=p.get("summary", ""),
204210
),
211+
artifact_references=lambda v: (v.artifact_uri,),
205212
discovery_visible=False,
206213
tags=("artifact", "storage"),
207214
),
@@ -215,6 +222,7 @@ def _adapter(**kwargs: Any) -> AtomicServiceAdapter:
215222
),
216223
output_schema=model_schema(ClaimValidationResult),
217224
invoke=lambda p: application.claims.validate(**p),
225+
artifact_references=lambda v: (v.claim_uri, v.plugin_id),
218226
read_only=True,
219227
tags=("claim", "validation"),
220228
),
@@ -257,6 +265,11 @@ def _adapter(**kwargs: Any) -> AtomicServiceAdapter:
257265
"profile": EvaluationProfile(p["profile"]),
258266
}
259267
),
268+
artifact_references=lambda v: (
269+
v.claim_uri,
270+
v.plugin_id,
271+
*(item.candidate_uri for item in v.items),
272+
),
260273
unverified_assurance_level=CapabilityAssuranceLevel.HEURISTIC,
261274
unverified_basis="untrusted plugin evaluation is not independently verified",
262275
tags=("evaluation",),
@@ -296,6 +309,7 @@ def _adapter(**kwargs: Any) -> AtomicServiceAdapter:
296309
invoke=lambda p: application.witnesses.find(
297310
**{**p, "witness_role": WitnessRole(p["witness_role"])}
298311
),
312+
artifact_references=lambda v: _witness_find_references(v),
299313
unverified_assurance_level=CapabilityAssuranceLevel.HEURISTIC,
300314
unverified_basis="witness search output is evidence pending explicit replay",
301315
tags=("witness", "search"),
@@ -315,6 +329,7 @@ def _adapter(**kwargs: Any) -> AtomicServiceAdapter:
315329
),
316330
output_schema=model_schema(ResultEnvelope),
317331
invoke=lambda p: application.verification.verify_witness(**p),
332+
artifact_references=lambda v: _envelope_references(v),
318333
unverified_assurance_level=CapabilityAssuranceLevel.HEURISTIC,
319334
unverified_basis="the checker did not accept the supplied witness",
320335
tags=("witness", "verification"),
@@ -329,6 +344,7 @@ def _adapter(**kwargs: Any) -> AtomicServiceAdapter:
329344
),
330345
output_schema=model_schema(ResultEnvelope),
331346
invoke=lambda p: application.verification.verify_certificate(**p),
347+
artifact_references=lambda v: _envelope_references(v),
332348
unverified_assurance_level=CapabilityAssuranceLevel.HEURISTIC,
333349
unverified_basis="the checker did not accept the supplied certificate",
334350
tags=("certificate", "verification"),
@@ -380,6 +396,7 @@ def _adapter(**kwargs: Any) -> AtomicServiceAdapter:
380396
"objectives": tuple(p["objectives"]),
381397
}
382398
),
399+
artifact_references=lambda v: _shrink_references(v),
383400
unverified_assurance_level=CapabilityAssuranceLevel.HEURISTIC,
384401
unverified_basis="plugin-proposed reductions are not a verified minimality claim",
385402
tags=("shrink",),
@@ -572,21 +589,36 @@ def _is_verified(value: Any) -> bool:
572589
return _has_verified_parameter_region_evidence(value)
573590

574591

575-
def _artifact_uris(value: Any) -> tuple[str, ...]:
576-
found: set[str] = set()
592+
def _witness_find_references(value: Any) -> tuple[str, ...]:
593+
refs: list[str] = [value.claim_uri, value.candidate_uri, value.plugin_id]
594+
if value.witness_uri is not None:
595+
refs.append(value.witness_uri)
596+
if value.certificate_uri is not None:
597+
refs.append(value.certificate_uri)
598+
return tuple(refs)
577599

578-
def visit(item: Any) -> None:
579-
if isinstance(item, dict):
580-
for child in item.values():
581-
visit(child)
582-
elif isinstance(item, list):
583-
for child in item:
584-
visit(child)
585-
elif isinstance(item, str) and item.startswith("artifact://sha256/"):
586-
found.add(item)
587600

588-
visit(value)
589-
return tuple(sorted(found))
601+
def _envelope_references(value: Any) -> tuple[str, ...]:
602+
envelope = _result_envelope(value)
603+
if envelope is None:
604+
return ()
605+
refs: list[str] = list(envelope.evidence_uris)
606+
if envelope.verification_record_uri is not None:
607+
refs.append(envelope.verification_record_uri)
608+
if envelope.assurance.scope_uri is not None:
609+
refs.append(envelope.assurance.scope_uri)
610+
return tuple(refs)
611+
612+
613+
def _shrink_references(value: Any) -> tuple[str, ...]:
614+
refs: list[str] = [value.initial_target_uri, value.final_target_uri]
615+
for step in value.steps:
616+
refs.append(step.from_uri)
617+
if step.proposed_uri is not None:
618+
refs.append(step.proposed_uri)
619+
if step.verification_record_uri is not None:
620+
refs.append(step.verification_record_uri)
621+
return tuple(refs)
590622

591623

592624
def _verification_bindings(

src/jacobian/atomic_domain_capabilities.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def build_domain_adapters(
7777
),
7878
}
7979
),
80+
artifact_references=lambda v: _transformation_apply_references(v),
8081
unverified_assurance_level=CapabilityAssuranceLevel.HEURISTIC,
8182
unverified_basis=(
8283
"plugin transformation output remains an open verification obligation"
@@ -96,6 +97,7 @@ def build_domain_adapters(
9697
),
9798
output_schema=model_schema(ResultEnvelope),
9899
invoke=lambda p: application.verification.verify_transformation(**p),
100+
artifact_references=lambda v: _envelope_references(v),
99101
unverified_assurance_level=CapabilityAssuranceLevel.HEURISTIC,
100102
unverified_basis=("the checker did not accept the transformation relation"),
101103
tags=("transform", "verification"),
@@ -129,6 +131,7 @@ def build_domain_adapters(
129131
invoke=lambda p: application.polytope.separate(
130132
PolytopeSeparateRequest(**p)
131133
),
134+
artifact_references=lambda v: _polytope_separate_references(v),
132135
tags=("polytope", "exact"),
133136
provider="jacobian.z3",
134137
),
@@ -148,7 +151,55 @@ def build_domain_adapters(
148151
),
149152
output_schema=model_schema(ParameterRegion),
150153
invoke=lambda p: application.conjectures.promote_parameter_region(**p),
154+
artifact_references=lambda v: _parameter_region_references(v),
151155
read_only=True,
152156
tags=("parameter", "verification"),
153157
),
154158
)
159+
160+
161+
def _transformation_apply_references(value: Any) -> tuple[str, ...]:
162+
refs: list[str] = [value.source_uri]
163+
if value.target_uri is not None:
164+
refs.append(value.target_uri)
165+
if value.claim_uri is not None:
166+
refs.append(value.claim_uri)
167+
if value.transformation_uri is not None:
168+
refs.append(value.transformation_uri)
169+
return tuple(refs)
170+
171+
172+
def _envelope_references(value: Any) -> tuple[str, ...]:
173+
envelope = getattr(value, "result", None)
174+
if not isinstance(envelope, ResultEnvelope):
175+
return ()
176+
refs: list[str] = list(envelope.evidence_uris)
177+
if envelope.verification_record_uri is not None:
178+
refs.append(envelope.verification_record_uri)
179+
if envelope.assurance.scope_uri is not None:
180+
refs.append(envelope.assurance.scope_uri)
181+
return tuple(refs)
182+
183+
184+
def _polytope_separate_references(value: Any) -> tuple[str, ...]:
185+
refs: list[str] = [value.point_uri, value.generator_set_uri]
186+
if value.effective_point_uri is not None:
187+
refs.append(value.effective_point_uri)
188+
if value.effective_generator_set_uri is not None:
189+
refs.append(value.effective_generator_set_uri)
190+
if value.claim_uri is not None:
191+
refs.append(value.claim_uri)
192+
if value.witness_uri is not None:
193+
refs.append(value.witness_uri)
194+
if value.certificate_uri is not None:
195+
refs.append(value.certificate_uri)
196+
return tuple(refs)
197+
198+
199+
def _parameter_region_references(value: Any) -> tuple[str, ...]:
200+
refs: list[str] = list(value.sample_uris)
201+
if value.subject_uri is not None:
202+
refs.append(value.subject_uri)
203+
if value.verification_record_uri is not None:
204+
refs.append(value.verification_record_uri)
205+
return tuple(refs)

src/jacobian/atomic_experiment_capabilities.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def build_experiment_adapters(
6161
),
6262
output_schema=model_schema(StructureCanonicalizationResult),
6363
invoke=lambda p: application.structures.canonicalize(**p),
64+
artifact_references=lambda v: _structure_references(v),
6465
unverified_assurance_level=CapabilityAssuranceLevel.HEURISTIC,
6566
unverified_basis="plugin canonicalization is not independently verified",
6667
tags=("structure", "canonicalization"),
@@ -154,3 +155,10 @@ def build_experiment_adapters(
154155
tags=("experiment", "control"),
155156
),
156157
)
158+
159+
160+
def _structure_references(value: Any) -> tuple[str, ...]:
161+
refs: list[str] = [value.structure_uri]
162+
if value.canonical_uri is not None:
163+
refs.append(value.canonical_uri)
164+
return tuple(refs)

tests/component/capabilities/test_atomic_capabilities.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from pathlib import Path
22

3+
from pydantic import BaseModel
4+
35
from jacobian.atomic_capabilities import AtomicServiceAdapter
46
from jacobian.contracts.capabilities import (
57
CapabilityCompletenessStatus,
@@ -20,6 +22,8 @@
2022
)
2123
from jacobian.storage.repository import ArtifactRepository
2224

25+
_ARTIFACT_URI = "artifact://sha256/" + "a" * 64
26+
2327

2428
def test_failed_exhaustive_result_cannot_claim_complete_coverage(
2529
tmp_path: Path,
@@ -96,3 +100,79 @@ def test_exhaustive_result_without_scope_cannot_claim_complete_coverage(
96100
assert result.execution.status is ExecutionStatus.COMPLETED
97101
assert result.scope is None
98102
assert result.completeness.status is CapabilityCompletenessStatus.PARTIAL
103+
104+
105+
def test_artifact_uri_in_explanatory_field_is_not_promoted_to_provenance(
106+
tmp_path: Path,
107+
) -> None:
108+
"""A valid artifact URI in a debug/message field must not enter artifact_uris."""
109+
110+
class FakeResult(BaseModel):
111+
artifact_uri: str = _ARTIFACT_URI
112+
debug_message: str = f"{_ARTIFACT_URI} was considered but not used"
113+
114+
adapter = AtomicServiceAdapter(
115+
capability_id="test.explicit",
116+
title="Test explicit references",
117+
description="Only the declared artifact_uri should appear.",
118+
input_schema={"type": "object", "additionalProperties": False},
119+
output_schema={"type": "object"},
120+
invoke=lambda _payload: FakeResult(),
121+
store=ArtifactRepository(tmp_path),
122+
artifact_references=lambda v: (v.artifact_uri,),
123+
)
124+
125+
result = adapter.invoke(CapabilityRequest(capability_id="test.explicit", input={}))
126+
127+
assert result.artifact_uris == (_ARTIFACT_URI,)
128+
129+
130+
def test_no_artifact_references_callback_yields_empty_artifact_uris(
131+
tmp_path: Path,
132+
) -> None:
133+
"""Without an artifact_references callback, artifact_uris must be empty."""
134+
135+
class FakeResult(BaseModel):
136+
explanatory: str = f"see {_ARTIFACT_URI} for context"
137+
138+
adapter = AtomicServiceAdapter(
139+
capability_id="test.no_refs",
140+
title="Test no references",
141+
description="No artifact references declared.",
142+
input_schema={"type": "object", "additionalProperties": False},
143+
output_schema={"type": "object"},
144+
invoke=lambda _payload: FakeResult(),
145+
store=ArtifactRepository(tmp_path),
146+
)
147+
148+
result = adapter.invoke(CapabilityRequest(capability_id="test.no_refs", input={}))
149+
150+
assert result.artifact_uris == ()
151+
152+
153+
def test_rejected_candidate_uri_does_not_enter_artifact_uris(
154+
tmp_path: Path,
155+
) -> None:
156+
"""A rejected candidate URI must not become provenance."""
157+
158+
class FakeResult(BaseModel):
159+
produced_uri: str = _ARTIFACT_URI
160+
rejected_candidates: tuple[str, ...] = (
161+
"artifact://sha256/" + "b" * 64,
162+
"artifact://sha256/" + "c" * 64,
163+
)
164+
165+
adapter = AtomicServiceAdapter(
166+
capability_id="test.rejected",
167+
title="Test rejected candidates",
168+
description="Only produced_uri should appear.",
169+
input_schema={"type": "object", "additionalProperties": False},
170+
output_schema={"type": "object"},
171+
invoke=lambda _payload: FakeResult(),
172+
store=ArtifactRepository(tmp_path),
173+
artifact_references=lambda v: (v.produced_uri,),
174+
)
175+
176+
result = adapter.invoke(CapabilityRequest(capability_id="test.rejected", input={}))
177+
178+
assert result.artifact_uris == (_ARTIFACT_URI,)

0 commit comments

Comments
 (0)