Microsoft 365 Copilot Graph connectors for enterprise source systems, built
on a shared, production-hardened C#/.NET 10 chassis. Each connector has its own
solution, tests, config, docs and Docker image and lives in its own top-level
folder; all five reference the one Connector.Chassis/ project in this repo,
and CI runs from the workflows at the repository root.
| Connector | Source system | Highlights | Tests |
|---|---|---|---|
| SalesforceConnector | Salesforce CRM | Sharing-model ACLs, standard + custom objects, sovereign-cloud ready, large-group ACL scale guard | 1213 |
| ClarizenConnector | Planview AdaptiveWork (Clarizen) | REST v2 + TDW bulk, financial-field governance (filter by default), webhooks with anti-replay | 929 |
| SeismicConnector | Seismic (sales enablement) | Version-aware, fail-closed No-MNE exclusion filter, usage ranking, webhook anti-replay | 1025 |
| AltrataConnector | Altrata (relationship & wealth intelligence) | Licensed feeds, seat-only entitlement, purpose-of-use authz, DSAR erasure | 764 |
| HadoopConnector | BDH Hadoop data mart (nightly Salesforce mirror) | Filter-first at 150M+ scale, partition pruning, 24h-lag aware | 1021 |
Connector.Chassis/ (v1.20.0) is one real shared project: all five connectors
consume it via <ProjectReference>. It is not a NuGet package — there is no
version pinning and no feed. .github/workflows/conformance.yml asserts that
reference on every PR by resolving it through the csproj XML to a file on disk,
so "on the fleet" is checked rather than claimed.
Sharing is partial, and tracked rather than estimated. Of the chassis's 22
modules, 11 have no local copy anywhere — Chassis (identity/seams),
ServiceStop, ServiceHost, SecretProvider, MetricsRenderer, SqlGateway,
CircuitBreakerRegistry, ConfigException, LogRedaction,
StandardLogDialect and EnvFlags. The other 11 still exist as per-connector
implementations: DecisionLedger, EventLogSink, HaCoordinator and
LogPruner in four connectors each; CircuitBreaker in three; Tracing in
two; Alerting, HttpTransport, Logging, SecureDirectory and SqlExecutor
in one.
SqlStateStore used to be a twelfth. It was removed from the chassis rather
than consolidated: Seismic was its only consumer, it had no chassis tests, and
all four other connectors have recorded, permanent reasons not to adopt it — a
reference implementation nobody can migrate to is not a reference. It now lives
in SeismicConnector/Config/, and the five implementations are tracked as
cross-connector duplication so the count moved rather than vanished.
The two most recent consolidations are the two shapes this work comes in.
EnvFlags was stale: Clarizen, Hadoop and Salesforce carried the parser from
before the fleet's boolean vocabulary was hardened, so a security-relevant fix
applied to the chassis had never reached them. ServiceHost was duplication:
four copies identical in mechanism that differed only in two identity strings and
in what each told the Windows Event Log, so the mechanism moved to the chassis and
the wording stayed behind a host hook (Connector.Chassis/CONSOLIDATION.md).
That comes to 49 declared divergences — Salesforce 16, Clarizen 11,
Hadoop 12, Altrata 10, Seismic 0 — each recorded with a reason in
.github/conformance/divergences.tsv.
The register is a ratchet, not an amnesty: CI fails on a local copy that is not
declared, and equally on a declared copy that no longer exists, so the number
cannot drift and cannot quietly grow. Where a connector keeps its own contract
the chassis exposes a seam instead of forking — Chassis.LoggerFactory (how
Salesforce's CPython-style logging stack participates at all),
Logging.Dialect, Alerting.HandlerFactory (each connector keeps the HTTP
transport it was hardened with), and ServiceHost's lifecycle hooks.
That register answers one question — divergence from the chassis — and is
structurally blind to another: a capability the connectors built independently
that the chassis never acquired has no chassis type to collide with, so it cannot
be seen at all. The gate now also reports duplication across connectors, which
is how ContentGate and InjectionScanner (three connectors, three shapes,
zero register rows) became visible. Like the renamed rows it is recorded rather
than enforced: a name collision alone cannot tell shared capability from
per-connector design, and five connectors each declaring Program is five
connectors, not four pieces of debt.
Every connector ships the same foundation:
- Unified CLI (
guide,setup-connection,full-deployment,ingest,reconcile,validate-config, …) - Checkpointed full + incremental crawls with crash/stop resume
- Dead-letter queue +
retry-failed; Graph$batchingest with adaptive concurrency and exactRetry-Afterhandling; connection sharding - SQLite / SQL Server state, active-active HA leases, Azure Key Vault secrets
/health/ready/metrics, structured JSON logs, OpenTelemetry tracing- Circuit breakers with degraded-mode fail-safe
- Unified data classification & sensitivity tagging (Public → Restricted) — an advisory connector-applied tag, with optional ACL enforcement of the top tier
- SCM-aware Windows service, Docker image, GitHub Actions CI — fourteen
workflows at the repository root: one per connector (build + test on ubuntu
and windows, plus a Docker image build), one for the chassis (build, test,
pack), CodeQL, the chassis conformance gate, and the release pipeline (one
reusable workflow plus a caller per connector — see Releasing).
mainis protected: linear history, no force-push, and the two checks that run on every PR (Chassis conformance,Analyze (csharp)) are required, admins included - Enterprise operations pack: Windows Event Log/SIEM integration, corporate proxy + TLS-inspection CA support, certificate-credential Graph auth, threat model, runbooks, DR plan, Grafana dashboards + alert rules (see each connector's "Enterprise operations" README section). SBOM generation, MSI packaging and release signing run from the repository root — see Releasing.
- Bank-grade hardening — privacy- and compliance-safe defaults out of the box:
dead-letter payload redaction by default, entitlement re-sync on incremental
crawls, owner-only (0700) state directories, stale-index item TTL
(
expirationDateTime), signed-timestamp webhook anti-replay, purpose-of-use authorization (Altrata), and a tamper-evident hash-chained decision ledger
See each connector's own README.md for source-specific features, environment
variables, deployment and hardware guidance.
All five share one tenant's Graph quotas (30 connections, 50M indexed items): TENANT_GOVERNANCE.md allocates connections, item budgets, app registrations and crawl windows across the fleet.
Operator documentation (deploy / monitor / troubleshoot / support), written for
IT operators, lives in Operator_Guides/ — start with
00_START_HERE.pdf, then the Tenant & Common Concepts guide, then your
connector's guide.
cd <Connector> # e.g. SalesforceConnector
dotnet build
dotnet testRequires the .NET 10 SDK. Deployment target is Windows Server (service mode); the code is cross-platform for development.
Docker images build with the repository root as the build context, because each
connector references ../Connector.Chassis:
docker build -f <Connector>/Dockerfile .Connectors version and ship independently, so release tags are prefixed per connector rather than shared. Pushing a tag runs the full pipeline for exactly one connector:
| Connector | Tag | Image |
|---|---|---|
| SalesforceConnector | salesforce-v1.2.0 |
ghcr.io/<owner>/salesforce-copilot-connector |
| ClarizenConnector | clarizen-v1.2.0 |
ghcr.io/<owner>/clarizen-connector |
| SeismicConnector | seismic-v1.2.0 |
ghcr.io/<owner>/seismic-connector |
| AltrataConnector | altrata-v1.2.0 |
ghcr.io/<owner>/altrata-connector |
| HadoopConnector | hadoop-v1.2.0 |
ghcr.io/<owner>/hadoop-connector |
A fleet-wide v* tag is deliberately not used: it would start all five
pipelines against one tag, and only the first to finish could create the
release.
Each tag build gates on the connector's full suite (ubuntu and windows),
then produces a CycloneDX SBOM, self-contained single-file win-x64 and
linux-x64 bundles with SHA-256 checksums, a GHCR image, an experimental WiX
MSI, and a GitHub release with the bundles and SBOM attached.
The logic lives once in
.github/workflows/release-connector.yml;
the five release-<connector>.yml callers supply only paths and names.
Dry run before tagging. Every caller also accepts workflow_dispatch, which
takes the identical build → smoke-test → package path but pushes nothing and
creates no release:
gh workflow run release-salesforce.ymlSigning is optional. All four secrets below are absent by default; each
signing step skips with a ::notice:: and the release still ships (unsigned),
so forks and dry runs never fail on missing credentials.
| Secret | Signs |
|---|---|
AUTHENTICODE_PFX_BASE64 / AUTHENTICODE_PFX_PASSWORD |
the win-x64 binary (timestamped Authenticode) |
COSIGN_PRIVATE_KEY / COSIGN_PASSWORD |
the GHCR image, by digest |