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
35 changes: 35 additions & 0 deletions code-mower-package-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@
"source": "docs/context-setup.md",
"target": "docs/context-setup.md"
},
{
"kind": "doc",
"source": "docs/control-surface-telemetry.md",
"target": "docs/control-surface-telemetry.md"
},
{
"kind": "doc",
"source": "docs/devin-peer-support-qualification.md",
Expand Down Expand Up @@ -892,6 +897,36 @@
"source": "src/code_mower/context_store.py",
"target": "src/code_mower/context_store.py"
},
{
"kind": "fixture",
"source": "src/code_mower/control_surface_session_summary.accepted.json",
"target": "src/code_mower/control_surface_session_summary.accepted.json"
},
{
"kind": "fixture",
"source": "src/code_mower/control_surface_session_summary.expectations.json",
"target": "src/code_mower/control_surface_session_summary.expectations.json"
},
{
"kind": "fixture",
"source": "src/code_mower/control_surface_session_summary.fixture-manifest.json",
"target": "src/code_mower/control_surface_session_summary.fixture-manifest.json"
},
{
"kind": "fixture",
"source": "src/code_mower/control_surface_session_summary.rejected.json",
"target": "src/code_mower/control_surface_session_summary.rejected.json"
},
{
"kind": "schema",
"source": "src/code_mower/control_surface_session_summary.schema.json",
"target": "src/code_mower/control_surface_session_summary.schema.json"
},
{
"kind": "core",
"source": "src/code_mower/control_surface_summary.py",
"target": "src/code_mower/control_surface_summary.py"
},
{
"kind": "core",
"source": "src/code_mower/controller.py",
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ historical release evidence and are not current operating guidance.

## Lifecycle

The manifest currently classifies 62 supporting, 53 frozen, and 5 archived document(s).
The manifest currently classifies 63 supporting, 53 frozen, and 5 archived document(s).
See [Documentation lifecycle](documentation-lifecycle.md) before adding, moving,
or changing release-sensitive documentation.
92 changes: 92 additions & 0 deletions docs/control-surface-telemetry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Control-surface session summaries

This supporting contract describes the metadata-only lifecycle summaries used
by the optional [Slack integration](slack-setup.md). Installation and Slack
administration remain in the Slack guide. General upload behavior remains in
the [cloud data contract](cloud-data-contract.md), and local work presentation
remains in the [Board data contract](board-data-contract.md).

## Boundary

`code_mower.controlSurfaceSessionSummary.v1` is a closed specialization of the
existing `code_mower.benchmarkEvent.v1` envelope. It reports one meaningful
session observation rather than a command, message, or transcript stream. The
packaged schema and fixtures are the normative machine-readable contract:

- `control_surface_session_summary.schema.json`;
- `control_surface_session_summary.accepted.json`;
- `control_surface_session_summary.rejected.json`;
- `control_surface_session_summary.expectations.json`;
- `control_surface_session_summary.fixture-manifest.json`.

The summary allows an opaque Code Mower correlation key, repository slug,
categorical provider, lifecycle state and outcome, observation time, operation
counts, closed lifecycle reason, bounded owner action, optional pull-request
metadata, and already available elapsed-time or Devin ACU measurements. The
producer hashes its local logical session into a 32-character key before
building the event. It never forwards a Slack identity or provider session
reference.

The lifecycle state, reason, and owner action form one closed policy triple.
Routine status observations use `none`; owner waits use `answer_question` or
`respond_to_approval`; failures and suspensions use `inspect_failure`.
`collect` observations with `result_not_ready` or `result_unavailable` use
`inspect_provider` regardless of whether the provider is still pending,
running, owner-waiting, or terminal. This preserves both facts: the provider
state remains truthful, and the missing result remains visible as the current
owner action. Unsupported combinations fail closed.

The closed validator rejects unknown root, dimension, metric, and tool
provenance fields. Tool provenance is limited to the fixed Code Mower reporter
identity, a bounded package version, and a coarse local/CI runtime category. Task
text, messages, answers, prompts, response URLs, Slack identities, source,
diffs, transcripts, tokens, paths, context or graph data, provider references,
and raw output cannot enter this event.

## Local Board projection

Slack-requested work uses the existing public remote-session lifecycle and
local Board adapter. The adapter maps `pending`, `running`, owner-waiting,
completion, failure, suspension, termination, archival, and uncertainty into
the same Board phases used by other remote work. This is local observation;
it does not require cloud access and grants no dispatch or merge authority.

## Hosted capability gate

Production emission stays disabled until the hosted service advertises this
exact closed object:

```json
{
"accepting": true,
"capability_version": 1,
"fixture_manifest_sha256": "<sha256 of the exact packaged fixture manifest bytes>",
"schema": "code_mower.controlSurfaceSessionSummaryCapability.v1",
"summary_schema": "code_mower.controlSurfaceSessionSummary.v1"
}
```

Every field must match, and unknown fields fail closed. A missing capability,
version mismatch, digest mismatch, or `accepting: false` leaves operation local
only. Client rollback stops new emission without rewriting accepted rows.
The client reads this object from
`GET /api/health` at `capabilities.control_surface_session_summary`; the cloud
doctor exposes an accepted exact match in its service-check detail.

After acceptance, the producer emits the first observation and then only a
meaningful lifecycle, operation-count, owner-action, or pull-request change.
Timestamp-only changes and changing elapsed time or usage on a nonterminal
session do not create another event. Terminal elapsed-time or usage changes
remain meaningful reconciliation evidence.

The hosted service must vendor the five packaged resources byte-for-byte,
scope repository identity through authenticated tenant policy, and implement
the retention, export, deletion, tenant-isolation, and aggregate-reconciliation
expectations in the packaged expectations fixture.

## Contract changes

The fixture manifest hashes exact UTF-8 file bytes. It does not hash itself;
the hosted capability advertises the SHA-256 of the manifest bytes separately.
After qualification, changing the schema or any fixture requires a new schema
or manifest version. Do not refresh hashes in place to accept changed meaning.
2 changes: 2 additions & 0 deletions docs/docs-manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ documents:
status: supporting
- path: docs/commercial-boundary.md
status: supporting
- path: docs/control-surface-telemetry.md
status: supporting
- path: docs/context-connections.md
status: supporting
- path: docs/context-delivery.md
Expand Down
1 change: 1 addition & 0 deletions src/code_mower/cloud_client/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"board_snapshot",
"builder_run",
"calibration_run",
"control_surface_session_summary",
"controller_decision",
"dogfood_upload",
"lane_policy_snapshot",
Expand Down
5 changes: 5 additions & 0 deletions src/code_mower/cloud_client/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import urllib.request
from typing import Any

from code_mower.control_surface_summary import capability_from_health


DEFAULT_HEALTH_PATH = "/api/health"
DEFAULT_DASHBOARD_PATH = "/dashboard"
Expand Down Expand Up @@ -100,6 +102,9 @@ def probe_cloud_service(endpoint: str, *, timeout: float) -> dict[str, Any]:
for key in ("app", "supabaseConfigured"):
if key in parsed and isinstance(parsed[key], str | bool | int | float):
detail[key] = parsed[key]
summary_capability = capability_from_health(parsed)
if summary_capability is not None:
detail["control_surface_session_summary"] = summary_capability
return {
"name": "service",
"status": "pass",
Expand Down
7 changes: 7 additions & 0 deletions src/code_mower/cloud_client/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
from typing import Any, Mapping

from code_mower import __version__
from code_mower.control_surface_summary import (
EVENT_TYPE as CONTROL_SURFACE_SUMMARY_EVENT_TYPE,
validate_control_surface_summary,
)
from code_mower.participants import PARTICIPANTS
from code_mower.provider_registry import REFERENCE_PROVIDERS
from code_mower.providers import (
Expand Down Expand Up @@ -417,6 +421,8 @@ def validate_cloud_event(value: Any) -> dict[str, Any]:
validate_adoption_run_payload(value)
if value["event_type"] == REVIEWER_FINDING_OUTCOME_EVENT_TYPE:
validate_reviewer_finding_outcome_payload(value)
if value["event_type"] == CONTROL_SURFACE_SUMMARY_EVENT_TYPE:
validate_control_surface_summary(value)
validate_work_type_metadata(value["dimensions"], value["event_type"], value["tool"])
return value

Expand Down Expand Up @@ -928,6 +934,7 @@ def normalize_event(value: dict[str, Any], event_type: str) -> dict[str, Any]:
PR_OUTCOME_EVENT_TYPE,
PRODUCTIVITY_EVENT_TYPE,
REVIEWER_FINDING_OUTCOME_EVENT_TYPE,
CONTROL_SURFACE_SUMMARY_EVENT_TYPE,
"value_report_snapshot",
}:
normalized["tool"] = build_code_mower_tool_provenance(
Expand Down
Loading
Loading