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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@
remains reproducibility provenance, but cannot extend reviewer-owned trust.
Baseline, acknowledgement, and severity-override expiry use the later of that
date and the verifier wall clock, so a forged backdated commit fails closed.
- **Agent-authored coverage proposals (contract v18 clarification).** Preflight
and local control now distinguish proposal authorship from approval for one
narrow manifest shape: an exact append-only addition of valid built-in
`tool_sources` rows may be authored by a coding agent and routed to verify.
Existing rows, all other manifest values, authority-bearing fields, custom
adapters, and unsafe paths remain human-routed; the concrete trust-root diff
still requires reviewer approval. Conventional test/golden fixtures are no
longer inferred as undeclared deployed surfaces unless the manifest
explicitly declares them. No schema or runtime-contract version changes.

- **Evidence-basis policy gate (P0, `0.16.0b5`).** Semantic claims and risk
hints now carry a typed evidence basis, stable claim IDs, and derived policy
Expand Down
18 changes: 15 additions & 3 deletions STABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -628,9 +628,12 @@ The stable top-level fields in the v0.3 preflight result are:
- `required_evidence[]` — deterministic evidence requirements for a
`--capability-request` high-risk action proposal.
- `changed_files[]` and `protected_surface_touches[]` — optional path review
projection from `--changed-files` and/or `--diff`.
- `requires_human_review` — true when the requested preflight input touches a
protected surface or lacks high/critical required evidence.
projection from `--changed-files` and/or `--diff`. A touch's existing
`requires_human_review` flag may be false only for a resolvable, append-only
proposal that adds valid built-in `tool_sources` coverage without changing
any other manifest value or existing source row.
- `requires_human_review` — true when a requested protected touch requires
pre-edit human routing or the plan lacks high/critical required evidence.
- `policy_snapshot_hash`, `trust_root_graph_hash`, and `trust_root_graph` —
deterministic hashes/projection for policy and trust-root drift review.
- `policy_drift` and `trust_root_graph_diff` — populated when
Expand All @@ -646,6 +649,15 @@ The stable top-level fields in the v0.3 preflight result are:
- `host_grant_drift` — optional host-grant drift payload when a host baseline
is present or explicitly supplied.

Preflight distinguishes proposal authorship from approval. A coding agent may
author the exact coverage-increasing manifest proposal described above and is
then routed to `verify`; path-only plans, custom adapters, `trust`/`optional`
fields, source edits/removals/reordering, non-contained or symlinked paths, and
mixed manifest changes remain human-routed. The exception never asserts
action effect, authority, binding, policy, or approval evidence. The resulting
`shipgate.yaml` diff is still a protected-surface change, and committed
verification continues to require human review before merge or execution.

### JSON report fields (stable)

In `agents-shipgate-reports/report.json`, the following are guaranteed:
Expand Down
10 changes: 8 additions & 2 deletions docs/agent-contract-current.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,14 @@ proactive routing surface for coding agents before edits. It accepts a single
surfaces, forbidden shortcut actions, required evidence for proposed high-risk
capabilities, host-grant drift when a host baseline is present, deterministic
`signals[]`, `control`, `requires_verify`, `verification_command`,
`allowed_next_commands[]`, and `plan_summary`. It is not a second gate; it must never be read as passed or
mergeable. The release gate remains `release_decision.decision`.
`allowed_next_commands[]`, and `plan_summary`. A concrete, resolvable diff that
only appends valid built-in `tool_sources` rows may mark that manifest touch
`requires_human_review=false` and route the coding agent to verification. This
authorizes proposal authorship only: existing rows and all other manifest
values must be unchanged, authority-bearing fields and custom adapters are
excluded, and the resulting trust-root diff still requires human review. It is
not a second gate; it must never be read as passed or mergeable. The release
gate remains `release_decision.decision`.

## Read these first for release gating

Expand Down
9 changes: 9 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ as diagnostic context only, and do not infer control from prose. Only
authorizes only the stated coding-agent route, and `human_review_required`
requires the agent to stop.

When `check` discovers an undeclared deployed tool surface, a coding agent may
prepare the narrow coverage proposal itself: append a built-in `tool_sources`
row with a contained existing path, leaving all existing rows and other
manifest values unchanged. Run preflight with the concrete diff, then follow
its exact verify command. This is proposal authorship, not approval; custom
adapters, trust/optional declarations, semantic or authority claims, and any
broader manifest change remain human-routed, and the concrete protected diff
still needs human review before merge.

### PR And Local Verification

For local pre-commit work, omit `--base` and `--head` so uncommitted edits are
Expand Down
10 changes: 8 additions & 2 deletions llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1166,8 +1166,14 @@ proactive routing surface for coding agents before edits. It accepts a single
surfaces, forbidden shortcut actions, required evidence for proposed high-risk
capabilities, host-grant drift when a host baseline is present, deterministic
`signals[]`, `control`, `requires_verify`, `verification_command`,
`allowed_next_commands[]`, and `plan_summary`. It is not a second gate; it must never be read as passed or
mergeable. The release gate remains `release_decision.decision`.
`allowed_next_commands[]`, and `plan_summary`. A concrete, resolvable diff that
only appends valid built-in `tool_sources` rows may mark that manifest touch
`requires_human_review=false` and route the coding agent to verification. This
authorizes proposal authorship only: existing rows and all other manifest
values must be unchanged, authority-bearing fields and custom adapters are
excluded, and the resulting trust-root diff still requires human review. It is
not a second gate; it must never be read as passed or mergeable. The release
gate remains `release_decision.decision`.

## Read these first for release gating

Expand Down
36 changes: 34 additions & 2 deletions src/agents_shipgate/cli/agent_result.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import json
from pathlib import Path
from pathlib import Path, PurePosixPath
from typing import Any

from agents_shipgate.config.loader import load_manifest
Expand Down Expand Up @@ -144,7 +144,11 @@ def _undeclared_tool_surfaces_changed(
catalog = load_triggers()
undeclared: list[str] = []
for path in changed_files:
if path in declared_set or is_agent_boundary_path(path):
if (
path in declared_set
or is_agent_boundary_path(path)
or _is_non_deployed_fixture_path(path)
):
continue
# Per-file classification: a glob rule matches on the path, a
# diff_contains rule (n8n, @function_tool) on this file's added lines.
Expand All @@ -167,6 +171,34 @@ def _undeclared_tool_surfaces_changed(
return sorted(dict.fromkeys(undeclared))


def _is_non_deployed_fixture_path(path: str) -> bool:
"""Exclude conventional test artifacts from undeclared deployment inference.

This filter is intentionally applied only after declared surfaces have
been computed. A fixture explicitly named by ``shipgate.yaml`` therefore
remains a real capability surface and is still routed through verify. Test
directories may appear below a package root, but fixture/golden names are
excluded only at the repository root or beneath an explicit test root so a
production path such as ``services/fixtures`` remains in scope.
"""

parts = PurePosixPath(path.replace("\\", "/")).parts
lowered = tuple(part.lower() for part in parts)
test_roots = {"test", "tests"}
fixture_roots = {
"__snapshots__",
"fixture",
"fixtures",
"golden",
"goldens",
}
if any(part in test_roots for part in lowered[:-1]):
return True
if lowered and lowered[0] in fixture_roots:
return True
return len(lowered) >= 4 and lowered[0] == "samples" and lowered[2] == "expected"


def _declared_tool_surfaces_changed(
*,
workspace: Path,
Expand Down
13 changes: 9 additions & 4 deletions src/agents_shipgate/cli/preflight.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,20 @@ def preflight(
)
else:
changed = _read_changed_files(changed_files)
diff_text = None
if diff is not None:
changed = sorted(set(changed) | set(_changed_files_from_diff(diff)))
diff_text = _read_diff(diff)
changed = sorted(
set(changed)
| {item.path for item in parse_unified_diff(diff_text) if item.path}
)
request = _read_capability_request(capability_request)
base = _read_base_preflight(base_preflight)
result = build_preflight_result(
workspace=workspace,
config=config,
changed_files=changed,
diff_text=diff_text,
capability_request=request,
base_preflight=base,
host_baseline=host_baseline,
Expand Down Expand Up @@ -134,9 +140,8 @@ def _read_changed_files(path: Path | None) -> list[str]:
return [line.strip() for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]


def _changed_files_from_diff(path: Path) -> list[str]:
text = sys.stdin.read() if str(path) == "-" else path.read_text(encoding="utf-8")
return sorted({item.path for item in parse_unified_diff(text) if item.path})
def _read_diff(path: Path) -> str:
return sys.stdin.read() if str(path) == "-" else path.read_text(encoding="utf-8")


def _read_capability_request(path: Path | None) -> CapabilityRequestV1 | None:
Expand Down
4 changes: 2 additions & 2 deletions src/agents_shipgate/core/agent_boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,8 @@ def evaluate_agent_boundary(
item.path
for item in diagnostics
if (
item.code == "content_source"
and item.path == ".codex/config.toml"
(item.code == "content_source" and item.path == ".codex/config.toml")
or item.code == "proposal_safe_manifest_addition"
)
},
)
Expand Down
34 changes: 34 additions & 0 deletions src/agents_shipgate/core/codex_boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
boundary_adapters_for_path,
is_agent_boundary_path,
)
from agents_shipgate.core.manifest_proposals import (
assess_coverage_increasing_tool_source_proposal,
)
from agents_shipgate.schemas.agent_control import (
CodingAgentCommandAction,
HumanControlAction,
Expand Down Expand Up @@ -473,11 +476,42 @@ def add(rule_id: str, *, path: str | None, evidence: dict[str, Any]) -> None:
},
)

manifest_diff_files = [
item
for item in diff_files
if item.path and item.path.replace("\\", "/") == "shipgate.yaml"
]
proposal_candidate = (
manifest_diff_files[0] if len(manifest_diff_files) == 1 else None
)
for diff_file in diff_files:
path = diff_file.path
if not path:
continue
normalized = path.replace("\\", "/")
# A block-level safe signal may clear the path-wide unclassified guard
# only when this is the sole record targeting the protected manifest.
if diff_file is proposal_candidate:
resolved = resolve(diff_file)
assessment = assess_coverage_increasing_tool_source_proposal(
workspace=workspace,
diff_file=diff_file,
resolved=resolved,
)
if assessment.proposal_safe:
evaluated_files.append(_evaluated_file_record(path, resolved))
diagnostics.append(
AgentResultDiagnostic(
level="info",
code="proposal_safe_manifest_addition",
message=(
"Manifest change only appends valid built-in tool-source "
"coverage. The coding agent may author this proposal, but "
"verify and human review still govern the concrete diff."
),
path=normalized,
)
)
if _is_codex_config_path(normalized):
resolved = resolve(diff_file)
evaluated_files.append(_evaluated_file_record(path, resolved))
Expand Down
Loading