Repository navigation
Description
Problem
The declaration workflow is the last wall between a first-time user and a gateable verdict, and it is currently shaped wrong in four measured ways (all from the 2026-08-20/23 adoption walks, CLI 0.16.0b7, post-#403):
- It asks humans to re-state what the scanner already knows. smart_closer: 12 tools × 4 fields ≈ 48 hand-typed values, while the same report already carries derived
risk_tagsfor the interesting tools. The user's question "what should I write?" has no answer in the product; the vocabulary's semantics (ambientvsunscoped) appear in no glossary. - It accepts contradictions silently — A declaration weaker than inferred evidence is accepted silently:
effect: readon a tool taggedexternal_writebecomes pass-eligible with zero findings #409. The declarant is the person the gate is blocking; the path of least resistance isread/none, and shipgate converts it into pass-eligibility with zero findings. - It flattens three epistemically different statements into one form.
effectis a fact about code (derivable here).authority/scopesare facts about deployment (they live in Terraform/OAuth config, often unanswerable from the repo — both walk targets are samples whose honest authority isnone, which trivially passes).approvalis an organizational rule. Different owners, different sources of truth, different rot rates — one undifferentiated blank list. - Nothing ever re-opens a declaration. Matching is by name (
tool_idoptional,schemas/manifest/action_surface.py:150); a year-oldeffect: writekeeps passing after the function's semantics change. Declarations rot, and a green gate at month twelve rests on descriptions of functions that no longer do what they did.
And for the coding-agent workflow specifically, the current model creates a pincer (hit live on github-mcp-server#3076): the agent must not edit shipgate.yaml, but its new tool is invisible until someone does.
Principles
- Never ask a human what the scanner can prove. Strong evidence establishes an effect with no declaration; questions exist only where evidence is ambiguous, absent, or being overridden.
- An agent may propose what evidence supports; only a human may assert against it. A content rule, enforced by comparison against derived evidence — no authorship forensics (git can't provide them; agents commit as their user).
- Every answer is pinned to the evidence that justified it. Evidence moves → the question re-opens. One comparator serves contradiction checking (A declaration weaker than inferred evidence is accepted silently:
effect: readon a tool taggedexternal_writebecomes pass-eligible with zero findings #409) and drift detection. - Attestation is the PR review of a protected file, not a separate ceremony. The manifest stays the trust root; CODEOWNERS + branch protection supply the human-authority property in practice, and shipgate should say so.
- Authority follows credentials, not functions. Declared once per source, overridden per tool.
- Facts in the manifest; rules in policy packs. Chosen once at init, not answered 12 times.
Design
A. Monotone declarations (#409 — Increment 1)
Declarations may freely add or escalate relative to evidence. De-escalation requires an explicit override: {evidence, reason}; without it, config_error. With it, accepted — but always emits a review finding; an override is never silent. Checks consume the union of declared effect and derived risk evidence, so under-declaring a secondary effect cannot dodge a check (this also resolves multi-effect tools without breaking the effect enum: risk_tags already carries the rest).
B. Evidence-first effects + questionnaire scaffold
Adapters emit per-tool effect evidence with basis and a derivation_id. Strong basis (call-graph to a known SDK write; first-party MCP readOnlyHint/destructiveHint on a trust: internal source) → effect established, no blank. suggested-declarations.yaml becomes a questionnaire holding only the ambiguous residue, each entry a question with candidates, the evidence for each, and a proposed conservative answer:
# ── Question 2 of 5 ──────────────────────────────────────────
# get_sap_credit_status: evidence is ambiguous between two readings.
# read — returns data, no state change observed
# privileged_data_access — the data is a customer credit limit
- tool: get_sap_credit_status
effect: privileged_data_access # proposed; keep to confirm, or replace
basis: confirmed:d41f8a2c # evidence pin, stamped on applyVerify reports progress as "3 of 5 questions answered; 2 remain (both authority)" instead of "36 semantic evidence gaps" — same facts, visible finish line.
Measured cost: smart_closer 48 blanks → ~5 questions + 1–2 authority blocks. github-mcp-server (117 tools, first-party MCP annotations) → 1 authority block + confirmations for the destructive handful.
C. Per-source authority
tool_sources:
- id: adk_agent
type: google_adk
path: agent.py
authority: {mode: scoped, auth_type: oauth2, credential_mode: service_account}All six Salesforce tools share one client; asking per-tool asks the same infra question six times and breeds the copy-paste that breeds wrong answers. Per-tool override remains; existing per-tool declarations keep working (additive).
D. The coding-agent loop
One new next_action.kind: confirm_declarations carrying the question list, each tagged authorable_by: coding_agent | human:
- Agent applies what it may: evidence-consistent proposals via the existing
apply-patchessurface (new patch kind,--confidence high), committed to the PR branch —review_publishablealready permits commit/push/update_pr. - Human review happens at the PR, where it already happens.
trust_root_touchedstill requires a human merge; the PR comment renders the declaration diff as the review surface — ✓ rows machine-verified as evidence-consistent, ⚠ rows for overrides. The reviewer reads exceptions, not 117 rows; that is what makes attestation real rather than rubber-stamped. - Agent stops at
authorable_by: humanwith the exact questions, not a generic "human review required."
This dissolves the #3076 pincer as a consequence: an agent declaring its own new tool is an evidence-consistent edit (fine, human merges); an agent weakening a declaration is blocked by content rule A regardless of what file it edits. Extends the existing quickstart doctrine ("proposal authorship, not approval") from tool_sources rows to declarations.
E. Drift pinning
basis: confirmed:<derivation_id> stamped at apply time; every scan re-derives and compares. Hash stable (renames, refactors) → silence, no treadmill. Hash moved → declaration_drift gap re-opens naming the delta ("send_email: external_communication evidence added since confirmation"). Same comparator as A. This is what makes the system safe to run for years — today nothing re-opens a declaration.
F. Policy packs as the rules layer
init asks one question: pick a pack (default, financial-strict, read-only-agent, template). Packs map effect → required controls. Today's seven separate SHIP-POLICY-APPROVAL-MISSING blockers become one statement: "pack default requires approval for financial writes; 3 tools lack it."
G. Adoption ladder + trust hardening
| rung | you run | you get | exit criterion |
|---|---|---|---|
| 0 Audit | scan — no manifest needed |
capability catalog + risk table | decided it's worth gating |
| 1 Gate the delta | init (advisory); backlog auto-baselined |
per-PR verdict on what the PR changes | first verdict, same day |
| 2 Answer on touch | questions arrive per-PR (usually 0–2) | shrinking backlog, progress counter | question backlog = 0 |
| 3 Strict | ci.mode: strict + protected manifest |
enforced gate | — |
- Rung 0 is the missing consumer mode: both walk targets are samples someone is evaluating, for which producer ceremony is unanswerable in principle. A manifest-less catalog is also the 60-second demo.
environment.target: template: sample repos adopt honestly — authority questions suppressed, verdict capped atreview_required("adopters must declare authority before production"). Prerequisite for the upstream-PR strategy (benchmark/upstream-prs.md) to be honest.SHIP-TRUST-MANIFEST-UNPROTECTED(static, guidance-level): no CODEOWNERS rule covers the manifest. Branch protection is not statically verifiable; the finding says so instead of pretending.doctorprints the current rung and its exit criterion.
Increments
- A declaration weaker than inferred evidence is accepted silently:
effect: readon a tool taggedexternal_writebecomes pass-eligible with zero findings #409 — monotone rule + contradiction finding. Independently shippable; closes the live fail-open in the trust anchor. - B — evidence-first effects, questionnaire, progress counter. The activation move.
- C + F — per-source authority, pack selection at init.
- E + G — drift pinning, rungs, template mode, CODEOWNERS check.
Headline metric & measurement
Time-to-first-gateable-verdict on an unconfigured real repo (the strategic bottleneck: activation). Secondary: insufficient_evidence rate on the labeled corpus (W27 baseline), and count-of-human-inputs-to-verdict (smart_closer: 48 → target ≤ 7). Re-run both adoption walks (adk-samples#1745, github-mcp-server#3076) as the acceptance harness — they are the measured baseline.
Non-goals & compatibility
- No new CLI commands (surface discipline): everything extends
scan,init,apply-patches,verify,doctor, policy packs. - No weakening of P0: prove actual agent-to-tool bindings #268: untrusted third-party content still cannot self-declare; evidence strength is conditioned on
tool_sources[].trust, and heuristics still never drive policy (ux(reporting): distinguish heuristic action projections from evidence-backed assertions #357) — they gain only the power to challenge (A). - Additive manifest fields (
tool_sources[].authority,override,basis); existing manifests unchanged. One report-schema bump (effect evidence + questions) and one contract bump (confirm_declarations) — both follow the standard bump checklists. - Importing scopes from deployment config (Terraform/OAuth manifests) is deliberately deferred; noted as the natural extension of C.
Grounded in: the 2026-08-18/20 adoption walks (#384–#398), the #403 epic and its verification, the #3076 pincer, and the #409 adversarial-declaration experiment. Related open: #396 (reason-text honesty), #398 (name-candidate poisoning), #357 (heuristic vs evidence distinction — A is its complement, not its reversal), #327 (adoption walk composition).
Activity
fix(evidence): a declaration weaker than its evidence is never silent (…
pengfei-threemoonslab commentedon Aug 24, 2026
First empirical baseline for the headline metric
Fourth adoption walk of google/adk-samples#1745, run 2026-08-23 on
0a744364(post-#411 and #413, Increment 1 only — the questionnaire and pre-filled effects from this RFC have not shipped). Walked as a first-time user from a pristine checkout, following only CLI output.A verdict was reached for the first time.
python/agents/smart_closerpathinsufficient_evidenceblocked,critical=2critical=0, high=0create_sap_sales_orderSequence:
verify --preview→init→ writedeclared_purpose→scan→ declare 2 tools →scan→verify.The finding that matters for this RFC's scope
Declaring 2 of 12 tools was sufficient. Baseline scan:
insufficient_evidence, 24 gaps,0/12pass-eligible,critical=0, high=0. After six lines of YAML declaringsend_email: external_communicationandcreate_sap_sales_order: financial_write(bothauthority.mode: none):This sharpens the design target. The RFC assumes the cost problem is the full 48-value surface; in practice the gate-relevant subset was 2 tools. A questionnaire that ranks questions by how much they can move the verdict — money and outward communication first — would have reached the same block after two questions. Suggests questions-to-first-blocker as a companion metric to time-to-first-gateable-verdict.
Increment 1 verified end to end
Three ways of answering, all behaving as designed:
blocked, risk named,2/12pass-eligible.send_email→effect: read, the realistic cheat to clear the audit blocker) → the blocker does clear, butdeclaration_below_inferred_evidenceappears — "declared effect 'read' is weaker than inferred 'external_communication' evidence (risk_hint:keyword)" — pass-eligible drops2/12 → 1/12and gaps rise20 → 21. Defeatable, but never quietly.evidence: external_communication, reason: the body is a mockprint) → accepted,2/12restored,review_concern_count: 1, and a complete audit row inacknowledged_overridescarryingdeclared_effect,inferred_effect,inferred_sources,corroborating_sources: [], the reason, andmanifest_path. Both gaps cleared, including themixed_policy_evidenceoverlap.No quiet path to green exists across the three.
Reviewer projection
verify --no-base→blocked, and the PR comment leads with the subject:Safety finding first, trust-root notice second — the ordering earlier walks had backwards. Note this also narrows the projection gap observed on github-mcp-server#3076: the subject is carried when a blocker exists; the gap is specific to the excluded/unbound case, where a ledger entry exists but no blocker does, so
Most severe:has nothing to name.Still open, and still this RFC's scope
authority.mode: noneis the honest answer for a mock, and it makes the authority dimension vacuous. adk-samples#1745's README states there are no live connections. This is theenvironment.target: templatecase in section G, and both walk targets have it.mixed_policy_evidencerenders as "builtin-effect-control-applicability: Policy applicability mixes authoritative and heuristic evidence." (relates to Audit adopter-facing output for internal identity-model vocabulary #329).next_actionfields in the preview payload — filed separately as Twonext_actionfields in one preview payload, different commands, no precedence marker — and the non-authoritative one comes first #414.fix(evidence): one routing table, one permutation, one derivation (#410…
fix(scaffold): a questionnaire whose numbers run in order (#410 review)
feat(evidence): ask only what the scanner cannot prove (#410 incremen…
fix(evidence): one permission list, published and judged the same way (…
fix(evidence): join a declaration to the source that produced the act…
feat(evidence): authority follows credentials, not functions (#410 in…
fix(action-surface): one permission list per action
fix(action-surface): one permission list per action (#418)
fix(evidence): a statement about deployment cannot subtract published…
test(evidence): guard the two mechanisms the sweep found unguarded (#410
9 remaining items
feat(control): an agent may draft what the evidence supports (#410 §D)
feat(control): an agent may draft what the evidence supports (#410 §D)
feat(control): an agent may draft what the evidence supports (#410 §D)
feat(control): an agent may draft what the evidence supports (#410 §D)
feat(evidence): pin an answer to its evidence, and name the rung you …
fix(policy): address the expert review — two P1s and five correctness…
feat(policy): the rules layer is a pack, chosen once at init (#410 §F) (
feat(control): an agent may draft what the evidence supports (#410 §D)
feat(control): an agent may draft what the evidence supports (#410 §D) (
fix(inputs): name every id shape codex_config mints, and pin all three
fix(inputs): a codex_config source is one loaded source per MCP server (
fix(agent-mode): the direction owns the pack change, not the caller (#…
feat(agent-mode): finish the control-envelope rollout and prove the a…
pengfei-threemoonslab commentedon Sep 2, 2026
Closing the RFC epic — every design section and every follow-up has shipped:
effect: readon a tool taggedexternal_writebecomes pass-eligible with zero findings #409), fix(evidence): a declaration cannot discharge a category it does not cover (#409 follow-ups) #413effect_readingsobserved-vs-declared.The headline-metric baseline (adoption walk 1 → walk 4: seven commands with no verdict → five commands to a verdict) is recorded in the thread; the next measurement is the participant-validation gate on the #456 corpus rather than another walk. The committed capability state (#474) is the successor primitive.