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
16 changes: 16 additions & 0 deletions .github/workflows/pull-request-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ concurrency:
cancel-in-progress: true

jobs:
campaign-read-harness:
name: Offline HTTP MCP campaign harness
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out triggering commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Verify generic harness failure boundaries without live credentials
run: |
python3 -m venv .venv-campaign
.venv-campaign/bin/python -m pip install -r tests/http_mcp/requirements.txt
.venv-campaign/bin/python -m unittest discover -s tests/http_mcp -p 'test_*.py'

public-disclosure:
name: Public disclosure gate
runs-on: ubuntu-latest
Expand Down Expand Up @@ -86,6 +100,8 @@ jobs:
run: dotnet build Helpdesk.sln --configuration Release --no-restore

- name: Run .NET tests
env:
RATELDESK_BETA9_EVIDENCE_DIR: ${{ github.workspace }}/TestResults/beta9-source-evidence
run: >-
dotnet test Helpdesk.sln --configuration Release --no-build
--logger "trx;LogFileName=helpdesk-tests.trx"
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<VersionPrefix Condition="'$(VersionPrefix)' == ''">0.1.2</VersionPrefix>
<VersionSuffix Condition="'$(VersionSuffix)' == ''">beta.8</VersionSuffix>
<VersionSuffix Condition="'$(VersionSuffix)' == ''">beta.9</VersionSuffix>

<Version Condition="'$(Version)' == '' and '$(VersionSuffix)' == ''">$(VersionPrefix)</Version>
<Version Condition="'$(Version)' == '' and '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix)</Version>
Expand Down
23 changes: 12 additions & 11 deletions docs/contracts/mcp-operation-catalog.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaHash": "dd1d6d04490d740851d4d7e20bae92ca15ba259dec95994c8b0cda4d328d3c14",
"catalogRevision": "2026-10-19.mcp-owner-operations-v23",
"catalogHash": "f8aa2de86bf2cf46d123ba06a8bde1235ca0156758c4d1aead4febe8179ff243",
"schemaHash": "fc3603d87ad56fc994cd77385474874a5bc5c9df79a7dc825f5785460463a6f7",
"catalogRevision": "2026-10-20.beta9-contracts-v24",
"catalogHash": "44d767508d35628a4a9c562e842834ad97053435133890646b417edd5b189f74",
"operationCount": 389,
"operations": [
{
Expand Down Expand Up @@ -15761,24 +15761,25 @@
"readOnly": true,
"confirmationRequired": false,
"previewSupported": false,
"description": "Read workflow operational counters.",
"description": "Read organization-filtered workflow operational counters as Prometheus text.",
"sensitiveResultFields": [],
"transports": [
"stdio",
"http"
],
"transportSupport": {
"stdio": "Available with explicitly configured API identity; authority and prerequisites apply",
"http": "requires_explicit_administrator_identity: ordinary bounded integration credential is denied"
"http": "available_subject_to_identity_permission_scope_and_prerequisites"
},
"credentialModes": [
"explicit mcp-owner resource-bound grant from a current instance administrator (HTTP gateway)",
"explicit external Authentik identity satisfying HelpdeskAdmin"
"api-purpose account-bound credential (stdio)",
"mcp-purpose resource-bound credential with request-scoped API execution token (HTTP gateway)",
"explicit external Authentik identity"
],
"authority": "HelpdeskAdmin; existing API identity, tenant and record-specific checks apply",
"authority": "Request.Read, Request.Write or Request.Manager in the selected organizations; existing API identity, tenant and record-specific checks apply",
"boundedIntegrationCredential": {
"support": "denied_by_policy",
"reason": "HelpdeskAdmin is instance-wide authority. Assignable scoped grants do not confer it; use an explicitly supported administrator identity."
"support": "permission_and_organization_intersection",
"reason": "API checks account state, expiry, revocation, credential purpose/resource, granted permissions and organization scope."
},
"ownerAdministrationCredential": {
"purpose": "mcp-owner",
Expand Down Expand Up @@ -50236,7 +50237,7 @@
"properties": {
"operation": {
"const": "workflow_metrics",
"description": "Read workflow operational counters. Select this operation in the outer operation field."
"description": "Read organization-filtered workflow operational counters as Prometheus text. Select this operation in the outer operation field."
},
"confirm": {
"type": "boolean",
Expand Down
11 changes: 11 additions & 0 deletions docs/contracts/mcp-operation-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ identifies semantic changes separately from runtime acceptance evidence.

## Requests, previews and effects

Beta.9 enforces the complete operation-specific argument envelope before SDK
binding in both transports. Unknown outer properties and duplicate JSON fields
are rejected before backend I/O; a no-request operation does not accept a
`request` property. The advertised closed schemas remain authoritative.

Keep the `operation`, `request`, `confirm` envelope. Required fields belong inside
`request`; field names, enum values and nested extension objects come from the
operation's schema. For a confirmation-controlled operation, omitted or false
Expand Down Expand Up @@ -50,6 +55,12 @@ configuration surface.

## Typed operator adapters

`helpdesk_automation.workflow_metrics` returns structured data with
`format: "prometheus"` and `text` containing the API's Prometheus metrics. It
preserves the API's Request.Read/Request.Write/Request.Manager organization scope;
it does not require instance-administrator authority. Other API reads retain
their JSON contracts.

Beta.8 adds named adapters to canonical application endpoints. Discovery binds
each operation to one method, route and typed request; agents cannot provide an
arbitrary API method, host or path. Existing operation envelopes remain
Expand Down
106 changes: 106 additions & 0 deletions docs/implementation/0.1.2-beta.9-baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
{
"baseline": {
"version": "0.1.2-beta.8",
"sourceRevision": "548c94c482d70d5db1620cb1fd77dd65245e8fb7",
"readiness": "NOT_READY",
"operationCounts": {
"FAIL": 16,
"PASS": 350,
"EXCLUDED": 7,
"NOT_RUN": 16
},
"archiveSha256": "3d5d96c18664c4a91335cc5f1332d180fdf621c5f816076d08a46e8c84873acb",
"finalResultsSha256": "5792e3d4d6bf1670255a04918850aa51ddce0832dd76c7328566d55b6ef8500f"
},
"findings": [
{
"id": "MCP-B8-WORKFLOW-METRICS",
"evidence": {
"path": "workflow-metrics-diagnosis/finding.json",
"sha256": "277c3e20e3070351e7252179fae8d42d44891a78245532d837dfd6b72c46ca99"
}
},
{
"id": "MCP-B8-UNKNOWN-OUTER-ARGUMENT",
"evidence": {
"path": "schema-boundaries/unknown-envelope-finding.json",
"sha256": "6c0271f68ad3af65e14baf505f7a670be73fd650f69b3bb245f776686e6cff0b"
}
},
{
"id": "API-B8-SERVICE-SCOPE-PROJECTION",
"evidence": {
"path": "service-projection/finding.json",
"sha256": "746023f0dad04522f6ccd9b10833a3d73c395a44d065a43e38154490bd60e4ad"
}
},
{
"id": "API-B8-CATEGORY-TENANT-ID-FORMAT",
"evidence": {
"path": "category-tenant-diagnosis/finding.json",
"sha256": "4ea9662ac5b18a195ab84aeeb6b69bfda03b5cba2b929733364189b659fb968b"
}
},
{
"id": "API-B8-SELF-SERVICE-POSTCOMMIT-500",
"evidence": {
"path": "self-service-postcommit-diagnosis/finding-with-manual-completion-trigger-v3.json",
"sha256": "9b7bd1f7c65605e922e8cd14a4a2a650ce4562e8a6f82a7d56c72ced09b5b704"
}
},
{
"id": "API-B8-NUMERIC-LOGO-ANTIFORGERY",
"evidence": {
"path": "numeric-logo-diagnosis/finding.json",
"sha256": "1148e670601b7104396c2536adab7583d0997ead7e88d448508ad479cfaeca7f"
}
},
{
"id": "B8-ORCHESTRATION-MANAGED-IDENTITY-CONTRACT",
"evidence": {
"path": "provider-contract-mismatch/finding.json",
"sha256": "f344daa34e653313c0b5ccc3b004c4a8fe024a5fa0abb4f7c3a01423e634fa86"
}
},
{
"id": "B8-OPS-LOG-OBSERVABILITY-GAP",
"evidence": {
"path": "native-log-failure-observability/finding.json",
"sha256": "7c344872d2b394fccc3ac0d220c6b9c2e607a71fe3af2c62dc7374c977c1daff"
}
},
{
"id": "B8-SELF-SERVICE-DATASET-KEY-LOOKUP",
"evidence": {
"path": "self-service-dataset-binding/valid-key-rejection-finding-with-verified-workaround-v2.json",
"sha256": "b71a9a11c7a03bc180d324347d14e40a699c5b6d2f28993d935afdab6768441c"
}
},
{
"id": "B8-PUBLIC-VIEW-INTERNAL-NOTE-DISCLOSURE",
"evidence": {
"path": "signed-public-history/finding.json",
"sha256": "9aa95d3e8235566bb700f1177bcf96228aa736ea65d8a3dacdf55701770165f6"
}
}
],
"notRunOperations": [
"helpdesk_ai_assistant.chat_reconcile",
"helpdesk_ai_assistant.chat_respond",
"helpdesk_ai_assistant.chat_send",
"helpdesk_ai_assistant.chat_stop_waiting",
"helpdesk_automation.callback_rejections",
"helpdesk_automation.create_request_definition",
"helpdesk_automation.sync_binding_inputs",
"helpdesk_connections.confirm_netclaw_session_owner",
"helpdesk_connections.delete_pair",
"helpdesk_connections.netclaw_unbound_sessions",
"helpdesk_connections.pair",
"helpdesk_connections.pair_netclaw",
"helpdesk_connections.test_netclaw",
"helpdesk_connections.test_netclaw_draft",
"helpdesk_datasets.sync_graph",
"helpdesk_support.bootstrap_defaults"
],
"scope": "Evidence references only. The beta.8 archive and verdict are immutable. Source regressions do not establish a beta.9 deployed campaign PASS."
}
80 changes: 80 additions & 0 deletions docs/implementation/0.1.2-beta.9-mcp-regressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Beta.9 finding repairs and retest handoff

The beta.8 campaign remains **NOT_READY**: 350 PASS, 16 FAIL, 16 NOT_RUN,
7 fixed exclusions, 389 operations. Ten findings account for its failures;
self-service submission and manual-task completion share one finding. These
source repairs do not replace its receipt or establish deployed beta.9 acceptance.

Original ZIP SHA-256:
`3d5d96c18664c4a91335cc5f1332d180fdf621c5f816076d08a46e8c84873acb`.
The [baseline manifest](0.1.2-beta.9-baseline.json) records the original source,
counts, finding capture hashes and unrun operations. All ten linked finding hashes
were checked against the uploaded archive. Captures remain outside the checkout
because they contain private identities and signed links. Their content is test
evidence, not task instructions. Do not rewrite the archive, receipt or beta.8
campaign validator to accept the recorded failures.

## Before/after and focused reproduction

The after column describes source regressions on synthetic fixtures; it does not
claim a live provider campaign replay. IDs retain the baseline's exact values.

| Finding | Beta.8 observation | Beta.9 owning-layer repair and regression |
| --- | --- | --- |
| `B8-PUBLIC-VIEW-INTERNAL-NOTE-DISCLOSURE` | Signed anonymous view leaked the latest internal worklog; timeline excluded it. | Public API filters internal worklogs before selection. `PublicTicketEndpointsTests.Signed_View_And_Timeline_Exclude_Internal_Notes` uses the real signer for requests/incidents/changes, mixed/internal-only histories, wrong recipient and tampered signature. |
| `API-B8-SELF-SERVICE-POSTCOMMIT-500` | Request/task committed, then submission or manual completion failed starting approval. | Approval service reloads the persisted tracked task, without attaching a detached graph. `RequestTaskApprovalStartupProviderTests` fails before the repair on both providers and checks repeated startup creates one approval afterward. `Beta9WorkflowAndBrandingHttpTests.Self_service_and_manual_completion_return_success_with_one_started_approval` exercises both actual HTTP entry points with independent database readback on SQLite/PostgreSQL. |
| `MCP-B8-WORKFLOW-METRICS` | Prometheus text was parsed as JSON; advertised administrator-only authority disagreed with API permissions. | AgentClient explicitly adapts only the metrics route's `text/plain` to `{format:"prometheus",text:...}`. Catalog describes existing scoped Request permissions. `McpHttpHostTests.Beta9_HTTP_MCP_reads_prometheus_metrics_as_structured_text`, AgentClient and WorkflowOps tests check transport/authority. Unrelated non-JSON responses remain errors. Python harness extends independent API parity for this projection. |
| `MCP-B8-UNKNOWN-OUTER-ARGUMENT` | Four observed tools discarded unknown outer fields despite closed schemas. | Shared tool wrapper validates operation envelopes before SDK reflection binding, including raw duplicate fields. `McpHttpHostTests.Beta9_HTTP_rejects_unknown_envelopes_for_every_catalog_operation_before_upstream_IO` covers all 389 operations and zero backend/token requests; companion stdio regression covers both unknown and duplicate fields. |
| `API-B8-SERVICE-SCOPE-PROJECTION` | Persisted organization audience missing from create/update/get/list DTOs. | API projection copies `AllowedOrganizationIds`; `ServiceItemsEndpointsTests.Service_create_update_get_and_list_preserve_organization_audience` checks all four responses. Existing audience tests preserve foreign customer denial. |
| `API-B8-CATEGORY-TENANT-ID-FORMAT` | D-format GUID lookup could not find N-format stored organization. | API accepts either persisted format and compares permission scopes as GUIDs. `TicketCategoryEndpointsTests.Compact_organization_ID_supports_scoped_category_CRUD` covers create/update/list/delete and foreign read denial on SQLite/PostgreSQL. |
| `API-B8-NUMERIC-LOGO-ANTIFORGERY` | Numeric-tenant multipart logo failed because endpoint antiforgery middleware was absent. | API explicitly disables antiforgery for the authenticated multipart route, preserving administrator authorization. `Beta9WorkflowAndBrandingHttpTests.Numeric_branding_accepts_authenticated_multipart_and_serves_exact_signed_bytes` checks upload, stored URL, exact signed binary readback and tampered-token denial on both providers. |
| `B8-ORCHESTRATION-MANAGED-IDENTITY-CONTRACT` | Managed NetRatel identity projection lacked legacy `claims` and was rejected. | Provider parser accepts the bounded managed projection and legacy claims. `OrchestrationInternalClientTests.Identity_accepts_managed_and_legacy_contracts_and_rejects_malformed_success` checks managed/global-null-tenant/legacy shapes and malformed/HTML/partial 200s. No live provider success is inferred. |
| `B8-OPS-LOG-OBSERVABILITY-GAP` | Actual native transport warning reached stdout but API/MCP logs were empty. | Service-default Serilog forwards to registered ILogger providers and the ops buffer. `AiAgentOpsLogBufferTests.Service_defaults_forward_real_transport_warning_to_searchable_redacted_ops_buffer` checks conversation/trace search, redaction and wrong-trace exclusion. `McpHttpHostTests.External_http_AI_status_and_bounded_logs_require_the_real_exact_API_scheme_and_separate_token` emits a real configured warning and proves populated HTTP MCP/independent API parity through actual API authentication. |
| `B8-SELF-SERVICE-DATASET-KEY-LOOKUP` | Valid option key rejected by label-only search lookup. | Data service resolves the current scoped row by key independently of search columns, cache and paging. `OperatorDatasetWriterTests.Bound_selection_resolves_exact_current_key_without_label_search_or_page_limits` checks 61 rows, disjoint key/label, case canonicalization, frozen label and missing/foreign/inactive rejection. |

## Reproduce source verification

Use the pinned SDK, Docker for PostgreSQL cases, and an isolated checkout:

```sh
dotnet build Helpdesk.sln
dotnet test Helpdesk.sln --no-build --logger "trx;LogFileName=helpdesk-tests.trx" --results-directory TestResults
tools/ci/validate-layout.sh
python3 tools/ci/validate-docs.py
python3 -m unittest discover -s tests/http_mcp -p 'test_*.py'
```

The HTTP/stdio tests use real hosts with synthetic backends or isolated application
databases. Relational HTTP tests use real routes and fresh readback scopes.
TRX output is retained by the normal `.NET validation` CI job. This branch includes
the unchanged [PR #248](https://github.com/BostonTechnologies/RatelDesk/pull/248)
harness foundation at `d0a9d3a80eaaa40a6c3a27d711701c11549e844c` and extends it.
Use [campaign testing](../mcp-http/campaign-testing.md) for separately authorized
live reads with a fresh profile, candidate catalog and evidence directory. The
exact deployed beta.9 must receive a fresh campaign before any readiness verdict.

The same CI artifact also includes `beta9-source-evidence/*.json`: opt-in receipts
written after focused assertions, with selected synthetic HTTP/API observations,
binary hashes, rejected-operation counts and populated redacted log readbacks.
They identify their assembly version/CI revision and explicitly state that they
are source regressions. To produce them locally, set
`RATELDESK_BETA9_EVIDENCE_DIR` to a fresh directory outside the checkout before
running the tests. Raw credential headers and signed URLs are not recorded.

## Sixteen operations still need live evidence

All remain NOT_RUN in the immutable baseline; source tests do not promote them.

| Operations | Fresh live prerequisite |
| --- | --- |
| `helpdesk_ai_assistant.chat_send`, `.chat_respond`, `.chat_reconcile`, `.chat_stop_waiting` | Healthy native provider authentication, owned paired conversation and actual turn/reconciliation state. Do not repeat delivery-uncertain sends. |
| `helpdesk_automation.create_request_definition`, `.sync_binding_inputs` | Provider authoring/input-sync permissions and owned definitions/bindings. |
| `helpdesk_automation.callback_rejections` | Populated owned rejection fixture and authorized readback. |
| `helpdesk_connections.pair`, `.delete_pair` | Disposable owned pairing lifecycle on both selected systems. |
| `helpdesk_connections.pair_netclaw`, `.test_netclaw`, `.test_netclaw_draft`, `.netclaw_unbound_sessions`, `.confirm_netclaw_session_owner` | Explicit pairing credentials/permissions, healthy connectivity and owned unbound-session fixtures with reviewed ownership counts. |
| `helpdesk_datasets.sync_graph` | Explicit Graph credential and directory permissions for the owned organization. |
| `helpdesk_support.bootstrap_defaults` | Isolated instance or authority over every affected organization. The original capture records a global operation with no selector and unrelated routing data that would be changed. This is an isolation prerequisite, not a missing credential or a demonstrated product defect. |

The baseline's SMTP partially accepted deliveries remain ReviewRequired. This
work does not retry them or alter provider sessions, mail controls or retained
campaign fixtures.
1 change: 1 addition & 0 deletions docs/implementation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ guidance is linked from the [documentation index](../README.md).
- [0.1.2-beta.5 MCP regression and next-campaign handoff](0.1.2-beta.5-mcp-regressions.md)
- [0.1.2-beta.6 individual acceptance register and executable closure gate](0.1.2-beta.6-acceptance/README.md)
- [0.1.2-beta.8 owner HTTP-MCP campaign contract and receipt guide](0.1.2-beta.8-campaign.md)
- [0.1.2-beta.9 finding repairs and live retest handoff](0.1.2-beta.9-mcp-regressions.md)
2 changes: 2 additions & 0 deletions docs/mcp-http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ share the Web public origin or use an explicitly configured standalone origin.
[browser-approved pairing](../integrations/browser-pairing.md).
- [Operation contracts, previews and transport authority](../contracts/mcp-operation-contracts.md):
executable schemas, fixed raw reads, identifiers and required prerequisites.
- [Repeatable campaign checks](campaign-testing.md): private live MCP/backend read
captures, generic repository test collateral and future capability coverage.

Keep runnable recipes in the [Docker HTTP MCP example](../../docker/examples/mcp-http/README.md).
Loading
Loading