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
1 change: 1 addition & 0 deletions crates/traverse-cli/src/http_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7434,6 +7434,7 @@ mod tests {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
594 changes: 541 additions & 53 deletions crates/traverse-cli/src/main.rs

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions crates/traverse-contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ pub struct CapabilityContract {
/// Typed JSON schema for capability state values written through the runtime `DataStore`.
#[serde(default)]
pub state_schema: Option<Value>,
/// Executable surface examples (spec 102). Preserved through publish; not cleared by validate.
#[serde(default)]
pub use_cases: Vec<UseCase>,
}

/// One authored use case that demonstrates a concrete input/output path for a capability.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UseCase {
pub scenario: String,
pub input_example: Value,
pub output_example: Value,
pub happy: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub persona_ref: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Expand Down
1 change: 1 addition & 0 deletions crates/traverse-contracts/tests/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,7 @@ fn valid_contract() -> CapabilityContract {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/traverse-mcp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,7 @@ mod tests {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/traverse-mcp/tests/mcp_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ fn capability_contract() -> traverse_contracts::CapabilityContract {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/traverse-runtime/src/data_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2384,6 +2384,7 @@ mod tests {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema,
use_cases: Vec::new(),
}
}
}
1 change: 1 addition & 0 deletions crates/traverse-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5503,6 +5503,7 @@ mod tests {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/traverse-runtime/src/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ mod tests {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
};
let record = CapabilityRegistryRecord {
scope: RegistryScope::Private,
Expand Down
1 change: 1 addition & 0 deletions crates/traverse-runtime/src/workflows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3550,6 +3550,7 @@ mod tests {
},
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/traverse-runtime/tests/expedition_wasm_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ fn expedition_contract() -> CapabilityContract {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ fn base_contract(
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/traverse-runtime/tests/placement_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ fn base_contract() -> CapabilityContract {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/traverse-runtime/tests/router_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ fn base_contract(service_type: ServiceType) -> CapabilityContract {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/traverse-runtime/tests/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,7 @@ fn capability_contract(
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down Expand Up @@ -1437,6 +1438,7 @@ fn simple_registration(scope: RegistryScope, id: &str, version: &str) -> Capabil
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
};
CapabilityRegistration {
scope,
Expand Down
1 change: 1 addition & 0 deletions crates/traverse-runtime/tests/thread_pool_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ fn test_contract() -> CapabilityContract {
event_trigger: None,
connector_requirements: Vec::new(),
state_schema: None,
use_cases: Vec::new(),
}
}

Expand Down
36 changes: 22 additions & 14 deletions docs/adr/0038-contract-surface-coverage.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,47 @@
# ADR-0038: Contract Surface Must Be Covered by Use Cases

- Status: Accepted
- Governing spec: `102-contract-surface-coverage`
- Related issues: traverse#1014, #1015, #1016; registry#192, #193
- Status: Accepted (amended 2026-08-10 for Decision 58)
- Governing spec: `102-contract-surface-coverage` (v1.1.0 Approved)
- Related issues: traverse#1014, #1015, #1016, #1040; registry#192, #193, #215

## Context

Capability contracts combine:

1. Free-text `summary` / `description`
2. JSON Schema input surface (including discriminator enums such as `action`)
2. JSON Schema input/output surface (enums and required properties)
3. `use_cases[]` with concrete input/output examples
4. An executable WASM artifact verified by package smoke

Only (3) and (4) are mechanically exercised today. `core.process-comment@1.0.0` demonstrated the failure mode: the schema enum and description advertised `resolve` / `pin` / markup sanitisation / allow-list “strict” mentions, while the artifact and eight use cases implemented a narrower matrix. Registry and traverse publish gates accepted the overclaim.
Only (3) and (4) are mechanically exercised. Two failure modes appeared:

- `core.process-comment@1.0.0` overclaimed `action` enum values beyond its use-case/smoke matrix.
- Loop batch publish validated use cases from raw JSON, then wrote a normalized `CapabilityContract` **without** a `use_cases` field, so registry copies lost them while CI still allowed missing use cases.

## Decision

Adopt **schema ⊆ use_cases ⊆ smoke** as a governed rule for discriminator enums (starting with `action`):
Adopt **schema ⊆ use_cases ⊆ smoke** for the full checkable schema surface (Decision 58):

- Every enum value retained in the contract MUST have at least one use case.
- Publish dry-run MUST fail on gaps once Spec 102 is Approved.
- Every input schema string enum value MUST appear in at least one use case input example.
- Every `inputs.schema.required` property MUST appear in at least one use case input example (no cartesian product).
- Every `reason_code` / `status` output enum value MUST appear in at least one use case output example; those fields MUST be enums when coverage is required.
- `use_cases` MUST be non-empty and MUST survive `capability publish` into the registry record.
- Each use case MUST have a matching smoke fixture asserting its `reason_code` / key outputs.
- Description claims beyond use cases MUST be called out under **Known limitations** or removed.
- An enum value MUST NOT be “implemented” solely as an undocumented generic unsupported stub.

For already-published overclaims: do not edit immutable versions; publish an honesty bump (e.g. `core.process-comment@1.0.1`) and deprecate the overclaiming version with an explicit reason.
For already-published gaps: do not edit immutable versions; publish an honesty bump and deprecate the dishonest version with an explicit reason.

## Alternatives Considered

- **Minimum use-case counts** — rejected: owner requires coverage of the declared capability surface, not N examples.
- **Cartesian required-field matrices** — rejected as an impractical publish gate.
- **Description-only linting (NLP)** — rejected for v1: high false-positive risk; use cases are the executable contract.
- **Require implementing every marketing claim immediately** — rejected as the default honesty path: narrowing the declared surface is a valid fix; full feature completion is a separate product ticket.
- **Gate only in registry** — rejected as sole control: authors need fail-fast in `capability publish --dry-run` before opening a registry PR. Registry SHOULD mirror the check for newly ADDED contracts.
- **CLI-only or registry-only enforcement** — rejected: authors need fail-fast publish dry-run; registry must still reject bypasses.
- **Parallel new coverage spec** — rejected: amend Spec 102 / registry FR-011 instead.

## Consequences

- Traverse gains Spec 102 + publish coverage checker (#1016) after approval.
- Registry gains a diff-based mirror check (registry#192) after the traverse spec is Approved (or a thin registry FR that references it).
- `core.process-comment` honesty bump (#1015 / registry#193) can land under existing `516` without waiting for Spec 102 approval, because it reduces claimed surface to already-tested behavior.
- Traverse Spec 102 v1.1.0 + expanded publish coverage checker (#1040).
- Registry FR-011 becomes MUST for new/changed contracts; CI mirror (#215).
- Honesty patch-bumps for stripped Loop capabilities follow under FR-010.
31 changes: 22 additions & 9 deletions docs/capability-contract-authoring-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,30 @@ cargo run -p traverse-cli-rs -- bundle register <path-to-manifest.json>

## Contract surface coverage (honesty)

Treat `use_cases` as the executable promise. If `inputs.schema` declares a
discriminator enum (especially `action`), every enum value MUST appear in at
least one `use_cases[].input_example`, and package smoke SHOULD exercise that
set. Do not list actions the artifact only rejects with a generic
`unsupported_action` unless that failure is itself a documented use case.

Description prose that mentions behavior beyond the use-case matrix MUST either
be removed or called out under an explicit **Known limitations** section.
Treat `use_cases` as the executable promise for the **entire declared schema
surface** (Spec `102` v1.1.0 / Decision 58) — not a minimum example count:

- Every string `enum` under `inputs.schema` MUST appear in ≥1
`use_cases[].input_example` at the same path.
- Every top-level `inputs.schema.required` property MUST appear in ≥1
`use_cases[].input_example`.
- `outputs.schema.properties.reason_code` and `status`, when used for
checkable outcomes, MUST be enums; every enum value MUST appear in ≥1
`use_cases[].output_example`.
- `use_cases` MUST be non-empty; `capability publish` preserves them into the
registry record.
- Each use case MUST have a matching package smoke fixture
(`runtime-requests/ucNN-*.json`) that asserts its `reason_code` / key outputs.

Do not list enum values the artifact only rejects with a generic
`unsupported_*` stub unless that failure is itself a documented use case.
Description prose beyond the use-case matrix MUST be removed or called out
under **Known limitations**. Narrowing an overclaimed surface via an honesty
patch-bump is a valid fix.

Governed by Spec `102-contract-surface-coverage` / ADR-0038.
`capability publish` / `--dry-run` enforce enum ⊆ use_cases (issue #1016).
`capability publish` / `--dry-run` and registry CI enforce the gate
(issues #1040 / registry#215).

## Persona references

Expand Down
36 changes: 36 additions & 0 deletions docs/decision-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -2130,3 +2130,39 @@ Publish and registry validation treated `description` and broad `action` enums a

Tickets filed on Project 1 (`#1014`–`#1016`) and Project 3 (`#192`–`#193`). Spec/ADR drafted. Honesty bump proceeds under existing `516` while Spec 102 awaits approval.


## Decision 58: Full Capability Surface Coverage via Use Cases (Not Minimum Counts)

- **Date**: 2026-08-10
- **Status**: Accepted; Spec 102 v1.1.0 Approved 2026-08-10
- **Governing spec**: `102-contract-surface-coverage` (v1.1.0), ADR-0038 (amended), registry `001` FR-011
- **Related issues**: traverse `#1040`; registry `#215`
- **Origin**: Post-Loop-batch audit — most registry `core.*` contracts had `use_cases` stripped by publish; owner directed that use cases must cover the entire capability, as a non-negotiable gate.

### Context

Decision 57 / Spec 102 v1.0.0 gated only `inputs.schema.properties.action.enum`. Loop capability publish validated use cases from raw JSON, then serialized `CapabilityContract` (which has no `use_cases` field), so registry copies lost them. Registry CI explicitly allowed missing use cases (`test_contract_without_use_cases_is_not_flagged`). Local examples often had use cases and smokes, but the catalog of record did not.

### Decision

1. **Coverage target**: the declared schema surface — every input schema string enum value; every `inputs.schema.required` property at least once; every `outputs.schema.properties.reason_code` / `status` enum value. Not a minimum use-case count. No cartesian product of required fields.
2. **Enums for checkable outcomes**: `reason_code` / `status` MUST be schema enums when authors need those outcomes covered; free-string fields are not coverage-checkable.
3. **Smoke linkage**: every `use_cases[]` entry MUST have a matching executable smoke fixture that asserts its `reason_code` / key outputs.
4. **Enforcement**: fail closed in both `capability publish` / `--dry-run` and registry CI for newly ADDED or CHANGED contracts. Publish MUST preserve `use_cases` (and author evidence) into the registry-bound JSON.
5. **History**: do not edit immutable stripped versions in place; honesty patch-bump them under FR-010.
6. **Governance vehicle**: amend Spec 102 (and registry `001` FR-011 from MAY→MUST for new/changed contracts); do not create a parallel coverage law.

### Alternatives Considered

- Minimum happy+unhappy counts only — rejected; owner clarified coverage of the whole capability matters, not N.
- Cartesian required-field matrices — rejected as an impractical publish gate.
- NLP description coverage as a blocking gate — deferred; Known limitations remain the honesty path for prose.
- Forward-only gate without republishing stripped caps — rejected; catalog would stay dishonest.
- CLI-only or registry-only enforcement — rejected; both are required.

### Outcome

- Spec 102 drafted at v1.1.0 (Draft) and ADR-0038 amended.
- Implementation tracked by traverse `#1040` and registry `#215`.
- Honesty patch-bumps for stripped Loop caps follow once the gate lands.

36 changes: 31 additions & 5 deletions examples/core-aggregate-team-action-health/contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
"id": "core.aggregate-team-action-health",
"namespace": "core",
"name": "aggregate-team-action-health",
"version": "1.0.0",
"version": "1.0.1",
"lifecycle": "active",
"owner": {
"team": "loop",
"contact": "founders@loop.dev"
},
"summary": "Aggregates open action items of a team into a health summary for managers (on-track %, overdue, overloaded owners, top pressure items).",
"description": "Pure aggregation capability used by the Manager Visibility workflow. Returns a compact health snapshot that can be rendered in a dashboard or weekly digest.",
"description": "Pure aggregation capability used by the Manager Visibility workflow. Returns a compact health snapshot that can be rendered in a dashboard or weekly digest.\n\nImplemented and smoke-tested matrix (use_cases):\n- team pulse aggregation with overloaded owners and top pressure items\n- invalid_input when reference_date is empty\n\nKnown limitations (intentional in 1.0.1):\n- Aggregation is memory-only; no persistence or notification side effects",
"use_cases": [
{
"scenario": "As a manager, I want a quick pulse of my team's action items.",
Expand Down Expand Up @@ -66,6 +66,28 @@
},
"happy": true,
"persona_ref": "collaboration-product-owner"
},
{
"scenario": "As a workflow author, I want invalid_input when reference_date is empty so bad schedules fail closed.",
"input_example": {
"items": [],
"reference_date": "",
"aggregation_config": {
"version": "1.0",
"overdue_threshold_days": 0
}
},
"output_example": {
"total_open": 0,
"on_track_pct": 0,
"overdue_count": 0,
"overloaded_owners": [],
"top_pressure_items": [],
"reason_code": "invalid_input",
"evaluation_trace": []
},
"happy": false,
"persona_ref": "runtime-engineer"
}
],
"inputs": {
Expand Down Expand Up @@ -122,7 +144,11 @@
}
},
"reason_code": {
"type": "string"
"type": "string",
"enum": [
"ok",
"invalid_input"
]
},
"evaluation_trace": {
"type": "array",
Expand Down Expand Up @@ -176,15 +202,15 @@
"source": "ai-assisted",
"author": "loop-founders + traverse-capability-author",
"created_at": "2026-08-08T06:00:00Z",
"spec_ref": "core.aggregate-team-action-health@1.0.0",
"spec_ref": "core.aggregate-team-action-health@1.0.1",
"adr_refs": [
"persona-council-review-2026-08-08"
],
"exception_refs": []
},
"evidence": [
{
"evidence_id": "core-aggregate-team-action-health-1.0.0-contract-validation",
"evidence_id": "core-aggregate-team-action-health-1.0.1-contract-validation",
"type": "contract_validation",
"status": "passed"
}
Expand Down
8 changes: 4 additions & 4 deletions examples/core-aggregate-team-action-health/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
"kind": "capability_package",
"schema_version": "1.0.0",
"package_id": "core.aggregate-team-action-health-agent",
"version": "1.0.0",
"summary": "Capability package for core.aggregate-team-action-health@1.0.0.",
"version": "1.0.1",
"summary": "Capability package for core.aggregate-team-action-health@1.0.1.",
"capability_ref": {
"id": "core.aggregate-team-action-health",
"version": "1.0.0",
"version": "1.0.1",
"contract_path": "./contract.json"
},
"workflow_refs": [
{
"workflow_id": "core.aggregate-team-action-health",
"workflow_version": "1.0.0"
"workflow_version": "1.0.1"
}
],
"source": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"request_id": "core-aggregate-team-action-health-01",
"intent": {
"capability_id": "core.aggregate-team-action-health",
"capability_version": "1.0.0"
"capability_version": "1.0.1"
},
"input": {
"items": [
Expand Down
Loading
Loading