Connectors.Abstractions.cs
C# · 158 lines · 12,152 bytes · wiki path 10 Architecture/contracts/Connectors.Abstractions.cs · download the raw file · cited from Components — Configuration module · Components — Development module · Components — Support module · Agent Runtime · Case protocol — requests, records, prompts and results · Contracts · Developer implementation guide · Software Architecture
Same folder: configuration-change.schema.json · configuration-input.schema.json · connector-capability.schema.json · eval-set.schema.json · execution-obligations.schema.json · ledger-record.schema.json · message-envelope.schema.json · model-turn.schema.json · module-manifest.schema.json · openapi.yaml · realtime-events.schema.json · runtime-state.schema.json · validate_case_journeys.py · validate_contracts.py · validate_runtime_contracts.py · write-shape.schema.json
// Ablera.Serdica.AI.Support.Connectors.Abstractions — the connector contract (Software Architecture § 3, § 8 rule 2).
// A connector references ONLY this assembly and its own client SDK — never Sessions, Memory or Governance. It treats case/task ids as opaque correlation and idempotency keys, never as workflow logic; the grant check happens in the Runtime's permission pipeline BEFORE any method here is invoked,
// and again inside the connector (refusal before contact) because the connector is the enforcement point
// (Trust and Data § 2). Reach is direct only (D132). Every result is a labelled envelope (Agent Runtime § 2 rule 3).
// Status: [P] — the seam the 10–12 connector libraries are written against (Delivery § 1.3 track 5).
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Ablera.Serdica.AI.Support.Connectors.Abstractions;
// ---------------------------------------------------------------------------------------------------- vocabulary
public enum Mode { Read, DryRun, Write, Send, Ddl }
public enum EffectClass { Read, Transactional, Compensable, Irreversible, Ddl }
public enum Authorization { ReadGrant, WriteGrant, Gate }
public enum EffectOfTimeout { None, Unknown, Applied }
public enum Recovery { Retryable, ReconcileFirst, NeverRetry }
public enum TrustClass { Trusted, Derived, Untrusted, Absent }
public enum WriteShape { A, B, E } // Failure and Recovery § 1: A = apply+verify+replay in one call; B = deterministic leased transaction; E = capability-specific external/DDL effect
public enum RouteKind { Direct, Unreachable }
/// <summary>The environment dimension of every grant and every scope — a real credential boundary.</summary>
public sealed record EnvironmentScope(string CustomerCode, string Environment, string System);
/// <summary>A grant as the connector sees it: what this case may do, to which target, in which mode, until when,
/// bound to which artefact. Issued by the Gates service; the connector only checks it (Trust and Data § 2).</summary>
public sealed record GrantView(
string GrantId, EnvironmentScope Scope, string TargetPattern, Mode Mode,
string? BoundArtefactHash, int? ExpectedRows, DateTimeOffset ExpiresOn,
string? ReservedCallId, long FenceEpoch, IReadOnlyList<string> DecisionIds);
/// <summary>The capability description — one CONNECTOR_SCOPES row of kind operation (Failure and Recovery § 4).
/// Its absence fails closed: an operation without a description is refused as a capability gap, never improvised.
/// Serialised form: contracts/connector-capability.schema.json.</summary>
public sealed record ConnectorOperation(
string OperationId, int Version, EnvironmentScope Scope,
string InputContractJsonSchema, TargetIdentity Target,
EffectClass EffectClass, Authorization Authorization, IReadOnlyList<Mode> Modes,
string? IdempotencyKeyShape, TimeSpan Timeout, EffectOfTimeout EffectOfTimeout, Recovery Recovery,
IReadOnlyList<string> EvidenceReturned, WriteShape? WriteShape);
public sealed record TargetIdentity(string? Host, int? Port, string? Service, string? Schema, IReadOnlyList<string> Objects, IReadOnlyDictionary<string, string> Extra);
/// <summary>What every tool result is: the content plus its label. Origin, trust class, hash, size and truncation travel
/// with the content into the prompt and into the ledger (Agent Runtime § 2 rule 3). Large content is spilled to a paper
/// and referenced; the prompt gets the head.</summary>
public sealed record ToolResultEnvelope(
string OperationId, int OperationVersion, EnvironmentScope Scope,
TrustClass Trust, string ContentSha256, long ByteLength, bool Truncated,
string? InlineHead, string? PaperRef,
IReadOnlyDictionary<string, string> Evidence, // row_count, returned_ids, version_id, commit_sha, pipeline_id …
string WhatActuallyRan, // the statement/call as executed — never result rows
TimeSpan Duration);
/// <summary>A refusal is a normal, readable result the agent plans around — never an exception (Agent Runtime § 2 rule 2).</summary>
public sealed record Refusal(string OperationId, RefusalReason Reason, string Detail, string? WhatWouldBeNeeded);
public enum RefusalReason { NoCapabilityRow, OutsideGrantScope, GrantExpired, ArtefactHashMismatch, ExpectedRowsMismatch, PreconditionFailed, Unreachable, ModeNotOffered, KillSwitch, BudgetStop }
// ---------------------------------------------------------------------------------------------------- the connector
public interface IConnector
{
string Name { get; } // oracle-ipal · oracle-insis · abacus-gateway · jira · mail · hdesk · gitlab · kibana · rabbitmq · browser · bi-publisher · camunda
ICapabilityCatalogue Capabilities { get; }
/// <summary>Probe the reach row for a scope: direct or unreachable, with the probe that decided it. Registering a
/// connector does not establish live access (Failure and Recovery § 4); this does, and writes CONNECTOR_SCOPES.VERIFIED_ON_UTC.</summary>
Task<ReachProbe> ProbeAsync(EnvironmentScope scope, CancellationToken ct);
/// <summary>A read or dry-run operation under a read grant. Refused before contact when the grant does not cover
/// scope × target × mode. Statement logging, never rows.</summary>
Task<Either<ToolResultEnvelope, Refusal>> ReadAsync(OperationCall call, GrantView grant, CancellationToken ct);
/// <summary>Write shape A: apply + verify + replay collapsed inside ONE connector call and one transaction; the call returns
/// only after commit or rollback, so no agent turn ever spans an uncommitted write (CR-9). The grant must be bound to the
/// packet's artefact hash and the expected row count; a mismatch is a refusal, not a warning.</summary>
Task<Either<WriteResult, Refusal>> WriteCollapsedAsync(WritePacketCall call, GrantView grant, CancellationToken ct);
/// <summary>Non-transactional effect form E: DDL, send, deployment or other external operation.
/// Never claims rollback atomicity; the result can be unknown or partly applied.</summary>
Task<Either<WriteResult, Refusal>> ExecuteEffectAsync(WritePacketCall call, GrantView grant, CancellationToken ct);
/// <summary>Write shape B: a connector-owned transaction under a lease. Apply, verify and commit are three calls, but the
/// lease is driven ONLY by deterministic executor code within one operation. No LLM, gate or agent hop
/// occurs while it is open. Expiry rolls back an uncommitted transaction; an uncertain commit is reconciled.</summary>
Task<Either<WriteLease, Refusal>> BeginLeasedWriteAsync(WritePacketCall call, GrantView grant, TimeSpan leaseFor, long fenceEpoch, CancellationToken ct);
Task<Either<WriteResult, Refusal>> ApplyUnderLeaseAsync(WriteLease lease, CancellationToken ct);
Task<Either<VerifyResult, Refusal>> VerifyUnderLeaseAsync(WriteLease lease, IReadOnlyList<Assertion> assertions, CancellationToken ct);
Task<Either<CommitResult, Refusal>> CommitLeaseAsync(WriteLease lease, CancellationToken ct);
Task RollbackLeaseAsync(WriteLease lease, string reason, CancellationToken ct);
/// <summary>The resume handshake (Failure and Recovery § 3, worker dies mid-apply): "what actually landed?" — the connector
/// answers from the target and the idempotency key, never from memory. The runtime compares with BUILD_STATE before repeating a turn.</summary>
Task<LandedState> WhatLandedAsync(OperationCall call, GrantView grant, CancellationToken ct);
/// <summary>Post-commit verification on the surface the customer actually uses (Support S6) or the target the stage wrote
/// (Configuration S2–S5) — the same assertions, read-only, under a read grant.</summary>
Task<Either<VerifyResult, Refusal>> VerifyAsync(IReadOnlyList<Assertion> assertions, EnvironmentScope scope, GrantView grant, CancellationToken ct);
}
public interface ICapabilityCatalogue
{
IReadOnlyList<ConnectorOperation> List(EnvironmentScope scope);
ConnectorOperation? Find(string operationId, int? version, EnvironmentScope scope); // null = capability gap → DeadEnd
}
// ---------------------------------------------------------------------------------------------------- calls and results
public sealed record OperationCall(string OperationId, int Version, EnvironmentScope Scope, string InputJson, string IdempotencyKey, string CaseId, string TaskId, string RuntimeCallId, long FenceEpoch);
/// <summary>The instance of an approved write shape, as the auditor already passed it (contracts/write-shape.schema.json #WritePacket).
/// The connector re-derives nothing about the reasoning; it checks template ⊆ shape, scope ⊆ grant, hash == grant binding.</summary>
public sealed record WritePacketCall(
OperationCall Call, string ShapeKey, int ShapeVersion, string ArtefactHash,
IReadOnlyList<BoundStep> Steps, IReadOnlyList<Assertion> Assertions, IReadOnlyList<string> TeardownStatementHashes, bool PreflightRequired);
public sealed record BoundStep(string StepId, string StatementTemplateHash, IReadOnlyDictionary<string, object> BoundParameters, int ExpectedCount, EffectClass EffectClass);
public sealed record Assertion(string Name, string QueryOrCall, string Expect);
public sealed record WriteResult(
IReadOnlyList<StepOutcome> Steps, int? TotalRows, VerifyResult? Verification, string WhatActuallyRan,
string? TeardownLogRef, DateTimeOffset? CommittedOn, LandedDisposition Disposition);
public sealed record StepOutcome(string StepId, int ExpectedCount, int? ActualCount, IReadOnlyList<string> ReturnedIds, string StatementHash);
public sealed record VerifyResult(bool Pass, IReadOnlyList<AssertionOutcome> Assertions, string Level /* structural·priced·reachable·quoted·issued·verified */);
public sealed record AssertionOutcome(string Name, bool Pass, string Observed);
public sealed record CommitResult(DateTimeOffset CommittedOn, string SessionRef);
public sealed record WriteLease(string LeaseId, EnvironmentScope Scope, string SessionRef, long FenceEpoch, DateTimeOffset LeasedUntil);
public enum LandedDisposition { Unknown, NotApplied, PartlyApplied, Applied }
public sealed record LandedState(LandedDisposition Disposition, IReadOnlyList<StepOutcome> Landed, string Evidence); // Evidence = how it was determined (idempotency key hit, row present, commit sha …)
public sealed record ReachProbe(RouteKind Kind, string Probe, DateTimeOffset ProbedOn, string? Detail);
/// <summary>A minimal Either so refusals stay values (not exceptions) across the seam.</summary>
public readonly struct Either<TOk, TRefused> where TOk : class where TRefused : class
{
public TOk? Ok { get; }
public TRefused? Refused { get; }
public bool IsOk => Ok is not null && Refused is null;
private Either(TOk? ok, TRefused? refused) { Ok = ok; Refused = refused; }
public static Either<TOk, TRefused> FromOk(TOk ok) => new(ok ?? throw new ArgumentNullException(nameof(ok)), default);
public static Either<TOk, TRefused> FromRefusal(TRefused refused) => new(default, refused ?? throw new ArgumentNullException(nameof(refused)));
}
// ---------------------------------------------------------------------------------------------------- the pipeline the runtime runs BEFORE any IConnector call
/// <summary>Agent Runtime § 2 rule 2 — policy, grant, environment scope, in that order; deny beats everything (pattern 2 of the adopted
/// harness patterns). Implemented once in AI.Support.Runtime; connectors additionally enforce grant and scope themselves.</summary>
public interface IPermissionPipeline
{
Task<Either<GrantView, Refusal>> CheckAsync(string caseId, string taskId, OperationCall call, Mode requestedMode, CancellationToken ct);
}
/// <summary>The rendering of a case's live grants into the per-turn prompt (Trust and Data § 2: an agent is told what it may do).</summary>
public interface IGrantRenderer
{
string RenderForPrompt(IReadOnlyList<GrantView> grants);
}