Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

## Unreleased

- **Insufficient-evidence remediation now stays framework-aware from the
decision engine through every primary short-form surface.** Semantic
`incomplete_surface` gaps for frameworks with explicit inventory support now
lead with the generated `suggested-inventory.json` artifact and the exact
`<framework>.tool_inventories` manifest key instead of an unreachable generic
MCP/OpenAPI route. Console scan output, the GitHub step summary, and text-mode
`verify` all project that same rank-1 action; unsupported source shapes retain
the compatibility fallback. Human work now precedes the exact rerun command
in text output, and verifier fix tasks collapse the duplicate semantic and
extraction inventory remedies into one instruction. The regression runs a
real Google ADK workspace through static extraction and semantic assessment,
so it cannot manufacture a pass-ineligible medium-confidence tool state.
([#318](https://github.com/ThreeMoonsLab/agents-shipgate/issues/318))

- **Human review now blocks merge and completion, not publication of the
evidence a human needs in order to review.** A human route was one universal
stop: `control.state: "human_review_required"` with `must_stop: true` and
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ Then read `report.json.release_decision.decision`, the source-of-truth gate:
| Decision | Meaning | Next step |
|---|---|---|
| `blocked` | Active, unaccepted blockers exist. | Fix the blockers or remove the risky tool surface. |
| `insufficient_evidence` | The scan cannot confidently gate release from the available static evidence. This does not prove the agent is unsafe. | Provide clearer sources such as an MCP export, OpenAPI spec, explicit local tool inventory, or broader OpenAI SDK source path, then rerun. |
| `insufficient_evidence` | The scan cannot confidently gate release from the available static evidence. This does not prove the agent is unsafe. | Follow the first structured evidence-gap action. Supported frameworks name the generated local inventory and exact manifest route; unidentified source shapes receive the generic source guidance. Then rerun. |
| `review_required` | Human review is needed, often for accepted debt or evidence gaps below the blocked threshold. | Review the listed items before promotion. |
| `passed` | No active blocker or review signal was found. | Keep the report artifact with the PR/release record. |

Expand Down
13 changes: 8 additions & 5 deletions STABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -1101,11 +1101,14 @@ takes precedence over both. The precedence is therefore:
→ `review_required` (other) → `passed`.

The intended recovery for a degraded-evidence case — whichever of the two
verdicts it lands on — is to provide clearer local evidence — for example an MCP
export, OpenAPI spec, explicit local tool inventory, broader OpenAI Agents SDK
source path, or validation trace — and rerun the scan. When the decision is
`review_required` because of an active high/critical finding, also resolve that
finding. `agents-shipgate verify` keeps both cases human-routed
verdicts it lands on — is the first structured action in
`release_decision.evidence_coverage.evidence_gaps[]`. For supported frameworks,
that action names the generated local inventory artifact and the exact
`<framework>.tool_inventories` manifest route. Only unidentified or unsupported
source shapes receive generic MCP/OpenAPI/inventory guidance. Apply the reviewed
evidence route and rerun the scan. When the decision is `review_required`
because of an active high/critical finding, also resolve that finding.
`agents-shipgate verify` keeps both cases human-routed
(`fix_task.actor = "human"`): a degraded-evidence case never opens an automated
coding-agent fix path, regardless of which verdict it carries.

Expand Down
2 changes: 1 addition & 1 deletion docs/agent-contract-current.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ In `agents-shipgate-reports/report.json`:
- `release_decision.evidence_coverage.semantic_coverage` (v0.29+) — `{total_actions, pass_eligible_actions, gap_count, review_concern_count, reason_counts}`. A non-zero semantic `gap_count` prevents `passed`; a non-zero `review_concern_count` prevents an automatic pass and routes known unscoped/ambient authority to review. Semantic gaps are not Findings and cannot be suppressed, baselined, severity-overridden, waived by `--no-heuristics`, or satisfied by `human_ack`.
- `release_decision.evidence_coverage.policy_gap_count` and top-level `policy_evidence_gaps[]` (v0.33+) — policy applicability that is heuristic-only, mixed, unknown, or conflicting. These rows are outside Findings and cannot be suppressed, baselined, severity-overridden, acknowledged, or removed by `--no-heuristics`; any row prevents `passed`.
- `release_decision.evidence_coverage.identity_coverage` (v0.30+) — `{total_observations, canonical_tools, bound_tools, pass_eligible_tools, ambiguous_name_count, gap_count, reason_counts}`. Provider-scoped observations remain separate unless an exact reviewed `tool_identity.bindings[]` entry joins them. Any ambiguous selector, invalid binding, or conflicting identity prevents `passed`.
- `release_decision.evidence_coverage.evidence_gaps[]` (v0.26+; semantic kinds added v0.29) — one structured row per measurable gap: `{kind, subject, source_type, source_ref, why, next_action}`. In addition to `low_confidence_tool` and `source_warning`, v0.29 adds `incomplete_surface`, `missing_effect_evidence`, `inferred_effect_only`, `conflicting_effect_evidence`, `missing_authority_evidence`, `partial_authority_evidence`, `conflicting_authority_evidence`, and `invalid_semantic_annotation`. Semantic next actions use `declare_action_effect`, `declare_action_authority`, `provide_complete_inventory`, or `resolve_semantic_conflict`, include accepted values and exact source/manifest pointers, and are always human-routed. Their declaration placeholders carry `suggested_patch_kind="manual"`, `auto_apply=false`, and `requires_human_review=true`; they are not Patch objects. Work the rows in order instead of guessing; Agents Shipgate never auto-asserts effect or authority.
- `release_decision.evidence_coverage.evidence_gaps[]` (v0.26+; semantic kinds added v0.29) — one structured row per measurable gap: `{kind, subject, source_type, source_ref, why, next_action}`. In addition to `low_confidence_tool` and `source_warning`, v0.29 adds `incomplete_surface`, `missing_effect_evidence`, `inferred_effect_only`, `conflicting_effect_evidence`, `missing_authority_evidence`, `partial_authority_evidence`, `conflicting_authority_evidence`, and `invalid_semantic_annotation`. Semantic next actions use `declare_action_effect`, `declare_action_authority`, `declare_tool_inventory`, `provide_complete_inventory`, or `resolve_semantic_conflict`, include accepted values and exact source/manifest pointers, and are always human-routed. Their declaration placeholders carry `suggested_patch_kind="manual"`, `auto_apply=false`, and `requires_human_review=true`; they are not Patch objects. Work the rows in order instead of guessing; Agents Shipgate never auto-asserts effect or authority.
- `loaded_policy_packs[].{source,sha256,sha256_status,owner}` (v0.27+) — policy-pack distribution and ownership metadata for organization audit. `sha256_status` is `"verified"` only when the manifest pin matched; otherwise it is `"unpinned"`. This is report metadata; normal pack matching and release gating still come from deterministic rules and `release_decision.decision`.
- `findings[].support` (v0.33+) — typed predicate support with status, effective confidence, policy/block eligibility, claim IDs, evidence bases, predicate rows, and `support_hash`. Rule confidence and `block: true` are ceilings/requests; they cannot upgrade the support. Baseline matching for supported findings requires the same support hash.
- `findings[].policy_routing` (v0.28+) — optional policy-pack owner, reviewers, and approval-routing metadata. This is non-enforcing reviewer/audit metadata, not `Finding.evidence`; it does not affect fingerprints, suppressions, baselines, `blocks_release`, or `release_decision`.
Expand Down
2 changes: 1 addition & 1 deletion docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ Then read `report.json.release_decision.decision`, the source-of-truth gate:
| Decision | Meaning | Next action |
| --- | --- | --- |
| `blocked` | Active, unaccepted blockers exist. | Fix blockers or remove the risky tool surface. |
| `insufficient_evidence` | The scan cannot confidently gate release from the available static evidence; this does not prove the agent is unsafe. | Provide an MCP export, OpenAPI spec, explicit local tool inventory, or broader OpenAI SDK source path, then rerun. |
| `insufficient_evidence` | The scan cannot confidently gate release from the available static evidence; this does not prove the agent is unsafe. | Follow the first structured evidence-gap action. Supported frameworks name the generated local inventory and exact manifest route; unidentified source shapes receive the generic source guidance. Then rerun. |
| `review_required` | Human review is needed for accepted debt or evidence gaps below the blocked threshold. | Review the listed items before promotion. |
| `passed` | No active blocker or review signal was found. | Keep the report artifact with the PR/release record. |

Expand Down
2 changes: 1 addition & 1 deletion llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1350,7 +1350,7 @@ In `agents-shipgate-reports/report.json`:
- `release_decision.evidence_coverage.semantic_coverage` (v0.29+) — `{total_actions, pass_eligible_actions, gap_count, review_concern_count, reason_counts}`. A non-zero semantic `gap_count` prevents `passed`; a non-zero `review_concern_count` prevents an automatic pass and routes known unscoped/ambient authority to review. Semantic gaps are not Findings and cannot be suppressed, baselined, severity-overridden, waived by `--no-heuristics`, or satisfied by `human_ack`.
- `release_decision.evidence_coverage.policy_gap_count` and top-level `policy_evidence_gaps[]` (v0.33+) — policy applicability that is heuristic-only, mixed, unknown, or conflicting. These rows are outside Findings and cannot be suppressed, baselined, severity-overridden, acknowledged, or removed by `--no-heuristics`; any row prevents `passed`.
- `release_decision.evidence_coverage.identity_coverage` (v0.30+) — `{total_observations, canonical_tools, bound_tools, pass_eligible_tools, ambiguous_name_count, gap_count, reason_counts}`. Provider-scoped observations remain separate unless an exact reviewed `tool_identity.bindings[]` entry joins them. Any ambiguous selector, invalid binding, or conflicting identity prevents `passed`.
- `release_decision.evidence_coverage.evidence_gaps[]` (v0.26+; semantic kinds added v0.29) — one structured row per measurable gap: `{kind, subject, source_type, source_ref, why, next_action}`. In addition to `low_confidence_tool` and `source_warning`, v0.29 adds `incomplete_surface`, `missing_effect_evidence`, `inferred_effect_only`, `conflicting_effect_evidence`, `missing_authority_evidence`, `partial_authority_evidence`, `conflicting_authority_evidence`, and `invalid_semantic_annotation`. Semantic next actions use `declare_action_effect`, `declare_action_authority`, `provide_complete_inventory`, or `resolve_semantic_conflict`, include accepted values and exact source/manifest pointers, and are always human-routed. Their declaration placeholders carry `suggested_patch_kind="manual"`, `auto_apply=false`, and `requires_human_review=true`; they are not Patch objects. Work the rows in order instead of guessing; Agents Shipgate never auto-asserts effect or authority.
- `release_decision.evidence_coverage.evidence_gaps[]` (v0.26+; semantic kinds added v0.29) — one structured row per measurable gap: `{kind, subject, source_type, source_ref, why, next_action}`. In addition to `low_confidence_tool` and `source_warning`, v0.29 adds `incomplete_surface`, `missing_effect_evidence`, `inferred_effect_only`, `conflicting_effect_evidence`, `missing_authority_evidence`, `partial_authority_evidence`, `conflicting_authority_evidence`, and `invalid_semantic_annotation`. Semantic next actions use `declare_action_effect`, `declare_action_authority`, `declare_tool_inventory`, `provide_complete_inventory`, or `resolve_semantic_conflict`, include accepted values and exact source/manifest pointers, and are always human-routed. Their declaration placeholders carry `suggested_patch_kind="manual"`, `auto_apply=false`, and `requires_human_review=true`; they are not Patch objects. Work the rows in order instead of guessing; Agents Shipgate never auto-asserts effect or authority.
- `loaded_policy_packs[].{source,sha256,sha256_status,owner}` (v0.27+) — policy-pack distribution and ownership metadata for organization audit. `sha256_status` is `"verified"` only when the manifest pin matched; otherwise it is `"unpinned"`. This is report metadata; normal pack matching and release gating still come from deterministic rules and `release_decision.decision`.
- `findings[].support` (v0.33+) — typed predicate support with status, effective confidence, policy/block eligibility, claim IDs, evidence bases, predicate rows, and `support_hash`. Rule confidence and `block: true` are ceilings/requests; they cannot upgrade the support. Baseline matching for supported findings requires the same support hash.
- `findings[].policy_routing` (v0.28+) — optional policy-pack owner, reviewers, and approval-routing metadata. This is non-enforcing reviewer/audit metadata, not `Finding.evidence`; it does not affect fingerprints, suppressions, baselines, `blocks_release`, or `release_decision`.
Expand Down
9 changes: 6 additions & 3 deletions src/agents_shipgate/ci/github_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
from agents_shipgate.core.disclaimers import STATIC_VERDICT_DISCLAIMER
from agents_shipgate.core.privacy import sanitize_report
from agents_shipgate.report.markdown import _safe_markdown_text
from agents_shipgate.report.summary_text import evidence_coverage_text
from agents_shipgate.report.summary_text import (
evidence_coverage_text,
primary_evidence_remediation_text,
)
from agents_shipgate.schemas.report import ReadinessReport


Expand Down Expand Up @@ -41,8 +44,8 @@ def write_github_step_summary(report: ReadinessReport) -> None:
)
if decision.decision == "insufficient_evidence":
lines.append(
"Improve evidence: provide MCP export, OpenAPI spec, explicit local "
"tool inventory, or a broader OpenAI SDK source path; then rerun scan."
"Improve evidence: "
f"{_safe_markdown_text(primary_evidence_remediation_text(decision.evidence_coverage))}"
)
if agent_summary and agent_summary.first_recommended_action:
lines.append(_agent_next_action_line(agent_summary.first_recommended_action))
Expand Down
39 changes: 28 additions & 11 deletions src/agents_shipgate/ci/release_decision.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,17 +723,32 @@ def _semantic_gap(
action_why = "Conflicting binding evidence cannot be auto-resolved."
expects = "Reconcile positive structural evidence and reviewed declarations, then rerun verification."
elif kind == "incomplete_surface":
action_kind = "provide_complete_inventory"
accepted_values = [
"complete_mcp_export",
"openapi_spec",
"reviewed_explicit_inventory",
]
action_why = "The complete statically-bound tool surface must be enumerable."
expects = (
"Provide a complete MCP export, OpenAPI spec, or reviewed explicit "
"tool inventory, then rerun verification."
)
manifest_key = _inventory_manifest_key(tool.source_type)
if manifest_key is not None:
action_kind = "declare_tool_inventory"
accepted_values = ["reviewed_explicit_inventory"]
action_why = (
f"{tool.source_type} extraction is static-only; an explicit "
"local tool inventory is the supported way to make the full "
"surface enumerable."
)
expects = (
"Review the skeleton written next to report.json, save it in "
f"your repo, reference it from `{manifest_key}` in "
"shipgate.yaml, then rerun verification."
)
else:
action_kind = "provide_complete_inventory"
accepted_values = [
"complete_mcp_export",
"openapi_spec",
"reviewed_explicit_inventory",
]
action_why = "The complete statically-bound tool surface must be enumerable."
expects = (
"Provide a complete MCP export, OpenAPI spec, or reviewed explicit "
"tool inventory, then rerun verification."
)
elif kind in {
"missing_effect_evidence",
"inferred_effect_only",
Expand Down Expand Up @@ -854,6 +869,8 @@ def _semantic_gap_path(kind: str, tool: Tool) -> str:

action_row = f"shipgate.yaml#action_surface.actions[tool={tool.name!r}]"
if kind == "incomplete_surface":
if _inventory_manifest_key(tool.source_type) is not None:
return SUGGESTED_INVENTORY_FILENAME
return "shipgate.yaml#tool_sources"
if kind in _SELECTOR_KINDS:
return action_row
Expand Down
11 changes: 5 additions & 6 deletions src/agents_shipgate/cli/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
from agents_shipgate.core.disclaimers import STATIC_VERDICT_DISCLAIMER
from agents_shipgate.core.errors import AgentsShipgateError, ConfigError, InputParseError
from agents_shipgate.core.findings.constants import SEVERITY_ORDER
from agents_shipgate.report.summary_text import evidence_coverage_text
from agents_shipgate.report.summary_text import (
evidence_coverage_text,
primary_evidence_remediation_text,
)

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -445,11 +448,7 @@ def _print_cli_summary(report, ci_mode: str, exit_code: int, *, verbose: bool =
ev = decision.evidence_coverage
typer.echo(f"Evidence coverage: {evidence_coverage_text(ev)}")
if decision.decision == "insufficient_evidence":
typer.echo(
"Improve evidence: provide MCP export, OpenAPI spec, explicit "
"local tool inventory, or a broader OpenAI SDK source path; "
"then rerun scan."
)
typer.echo(f"Improve evidence: {primary_evidence_remediation_text(ev)}")
if report.agent_summary and report.agent_summary.first_recommended_action:
action = report.agent_summary.first_recommended_action
if action.command:
Expand Down
9 changes: 9 additions & 0 deletions src/agents_shipgate/cli/verify/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from agents_shipgate.core.disclaimers import STATIC_VERDICT_DISCLAIMER
from agents_shipgate.core.errors import AgentsShipgateError, ConfigError, InputParseError
from agents_shipgate.core.logging import configure_logging
from agents_shipgate.report.summary_text import primary_evidence_remediation_text
from agents_shipgate.schemas.diagnostics import NextAction

from .git import ensure_git_workspace, staged_paths_under
Expand Down Expand Up @@ -427,6 +428,14 @@ def verify(
typer.echo(f"Trigger: {verifier.trigger.get('rationale')}")
typer.echo(f"Base status: {verifier.base_status}")
typer.echo(f"Exit code: {exit_code}")
if (
verifier.release_decision is not None
and verifier.release_decision.decision == "insufficient_evidence"
):
typer.echo(
"Improve evidence: "
f"{primary_evidence_remediation_text(verifier.release_decision.evidence_coverage)}"
)
typer.echo(f"Static-verdict boundary: {STATIC_VERDICT_DISCLAIMER}")
raise typer.Exit(exit_code)

Expand Down
5 changes: 4 additions & 1 deletion src/agents_shipgate/cli/verify/fix_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,10 @@ def _insufficient_evidence_remedies(report: ReadinessReport) -> list[str]:
decision = report.release_decision
assert decision is not None
for gap in decision.evidence_coverage.evidence_gaps:
if gap.kind in {"low_confidence_tool", "source_warning"}:
if gap.kind in {"low_confidence_tool", "source_warning"} or (
gap.kind == "incomplete_surface"
and gap.next_action.kind == "declare_tool_inventory"
):
continue
action = gap.next_action
accepted = (
Expand Down
Loading