chore: initialize go-sip repository

This commit is contained in:
2026-09-21 08:56:04 +08:00
commit 643b11b21f
309 changed files with 40521 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
# W02 error, idempotency, and fencing contract
This document is part of the `agent.v1` project-owned baseline. It does not
change the SaaS/MQ contract.
## Transport and domain errors
Handlers return normal gRPC status codes and, when a response message exists,
put the stable `FailureCode` in `Failure.code` as well:
| `FailureCode` | gRPC status | Retry rule |
| --- | --- | --- |
| `INVALID_ARGUMENT` | `InvalidArgument` | Fix the request; never retry unchanged |
| `UNAUTHENTICATED` | `Unauthenticated` | Re-establish mTLS/session; do not replay business work |
| `PERMISSION_DENIED` | `PermissionDenied` | Stop; require a new authorization |
| `FAILED_PRECONDITION` | `FailedPrecondition` | Refresh state/barrier, then use the original operation ID only if allowed |
| `ABORTED` | `Aborted` | Re-read the CAS revision; do not assume the operation applied |
| `RESOURCE_EXHAUSTED` | `ResourceExhausted` | Wait for durable quota/resource release |
| `UNAVAILABLE` | `Unavailable` | Reconnect and reconcile the original operation before any retry |
| `DEADLINE_EXCEEDED` | `DeadlineExceeded` | Result is unknown unless a durable receipt exists |
| `NOT_FOUND` | `NotFound` | Do not create a substitute execution |
| `ALREADY_EXISTS` | `AlreadyExists` | Read the existing operation/result; do not create a second one |
A transport success is not an application receipt. `RESULT_CODE_ACCEPTED` means
that the receiving side durably recorded the request; `APPLIED` requires the
specified state transition and barrier evidence.
## Idempotency keys
`RequestMeta.idempotency_key` is required for mutating methods. The key is
scoped by `(agent_id, operation_id, idempotency_key)` and the durable operation
record also stores the request content digest. Reusing a key with different
content returns `ABORTED`/`RESULT_CODE_CONFLICT`; it never overwrites the first
request.
- `ActivateAgent`: `activation_operation_id` is the idempotency key. A repeated
identical request returns the same session generation and credential metadata.
- `SetAdmissionState`: `barrier_id` plus the expected admission generation is
persisted. A replay cannot move the generation twice.
- `Execute`: the execution binding and permit ID are the deduplication identity.
An unknown result is reconciled with `QueryExecution`; it is never retried as
a new originate.
- `GetExecutionPermit`: the reservation, binding, expected revision and
idempotency key are persisted. A permit is not issued after the reservation is
released or fenced.
- `ApplyTaskControl`: `(execution_id, expected_task_revision, action,
idempotency_key)` is CAS-checked. `STOP` is terminal; `PAUSE` may be resumed
only by a new authorized request.
- `ReportExecutionEvent`: `(fact_id, content_sha256)` is the durable fact key.
An identical duplicate returns the original receipt; a digest mismatch is a
conflict.
- `RequestUpload`/`CompleteUpload`: `upload_id` and the asset checksum are
retained. Completion is returned only after the OSS/SaaS verification state
is known; a local PUT success is not `recording.ready`.
Read-only methods may be retried, but callers still preserve the original
request and trace identity: `GetAgentStatus`, `GetBootstrap`, and
`QueryExecution`.
## Fencing and session rules
1. The Dispatcher creates a new opaque `dispatcher_epoch` when its active
instance changes. The Agent accepts mutations only for the active epoch.
2. The Agent identity is the mTLS certificate plus the Dispatcher-approved
`(agent_id, cell_id, boot_id, session_generation)` binding. Self-reported
identity or endpoint values are not authorization.
3. A newer boot or session generation fences older requests. The old request
returns `UNAUTHENTICATED` or `ABORTED` and cannot release an unknown lease.
4. A permit contains the dispatcher epoch, session generation, reservation and
`fencing_token`. The Agent checks all of them immediately before originate.
5. Admission close/drain is a prerequisite barrier. `SetAdmissionState` and
`ApplyTaskControl` are applied only when their expected generation/revision
matches durable state.
6. When the result of a mutation is unknown, the caller first queries the
original operation/execution and records `UNKNOWN` if evidence is absent.
No new execution ID or new permit is invented for recovery.
## Upload boundary
The Agent receives a restricted `UploadGrant` and uploads directly to the
approved OSS target. The Dispatcher never receives audio bytes. The Agent
reports only asset metadata/checksum through `CompleteUpload`; the Dispatcher
coordinates the SaaS completion/verification and publishes the resulting OSS ID
through the existing MQ event path.
+32
View File
@@ -0,0 +1,32 @@
# W02 Unary gRPC contract
This directory is the project-owned W02 protocol baseline, created from the
confirmed R01–R03/R05/R07–R13 responsibilities in `docs/通信与事件数据交互_v0.1.md`
and the crash/authorization rules in `docs/G0开发准备与契约冻结提案_v0.1.md`.
- Transport is Unary gRPC over mTLS; no internal MQ, bidirectional audio stream,
or HTTP call-execution callback is introduced.
- The protocol carries control, authorization, facts, metadata and restricted
upload grants. It never carries recording/audio bytes.
- `call_execute_json` and `payload_json` preserve the approved external JSON
bytes; this Proto does not create a second external SaaS Schema.
- `accepted` is durable receipt only; `applied`, terminal state and verified
asset facts require later evidence.
- IDs, epochs, boot/session generations, operation IDs and explicit idempotency
keys are retained for idempotency, fencing and unknown-result reconciliation.
- `ERRORS.md` is the companion error/CAS/fencing contract; it defines when a
response is only accepted and when a caller must reconcile instead of retrying.
Generation is deterministic with the pinned local tools:
```sh
buf lint
buf breaking --against '.git#branch=HEAD'
buf generate
```
The protocol is versioned by the `agent.v1` package and the `protocol_version`
metadata. Field numbers are never reused. Production mTLS credentials and
endpoints are deployment inputs, not repository contents. `ERRORS.md` and the
Proto are released together; a field or semantic change requires a new
compatibility review.
+450
View File
@@ -0,0 +1,450 @@
syntax = "proto3";
package agent.v1;
option go_package = "git.ipao.vip/rogee/go-sip/gen/agent/v1;agentv1";
// AgentControl is the project-owned Unary gRPC boundary between the single
// active Dispatcher and a Cell Agent. It carries metadata and facts, never
// audio bytes or an internal message-bus replacement.
service AgentControlService {
rpc GetAgentStatus(GetAgentStatusRequest) returns (GetAgentStatusResponse);
rpc ActivateAgent(ActivateAgentRequest) returns (ActivateAgentResponse);
rpc GetBootstrap(GetBootstrapRequest) returns (GetBootstrapResponse);
rpc SetAdmissionState(SetAdmissionStateRequest) returns (SetAdmissionStateResponse);
rpc Execute(ExecuteRequest) returns (ExecuteResponse);
rpc GetExecutionPermit(GetExecutionPermitRequest) returns (GetExecutionPermitResponse);
rpc ApplyTaskControl(ApplyTaskControlRequest) returns (ApplyTaskControlResponse);
rpc QueryExecution(QueryExecutionRequest) returns (QueryExecutionResponse);
rpc ReportExecutionEvent(ReportExecutionEventRequest) returns (ReportExecutionEventResponse);
rpc RequestUpload(RequestUploadRequest) returns (RequestUploadResponse);
rpc CompleteUpload(CompleteUploadRequest) returns (CompleteUploadResponse);
}
enum ResultCode {
RESULT_CODE_UNSPECIFIED = 0;
RESULT_CODE_ACCEPTED = 1;
RESULT_CODE_APPLIED = 2;
RESULT_CODE_REJECTED = 3;
RESULT_CODE_UNKNOWN = 4;
RESULT_CODE_CONFLICT = 5;
}
enum FailureCode {
FAILURE_CODE_UNSPECIFIED = 0;
FAILURE_CODE_INVALID_ARGUMENT = 1;
FAILURE_CODE_UNAUTHENTICATED = 2;
FAILURE_CODE_PERMISSION_DENIED = 3;
FAILURE_CODE_FAILED_PRECONDITION = 4;
FAILURE_CODE_ABORTED = 5;
FAILURE_CODE_RESOURCE_EXHAUSTED = 6;
FAILURE_CODE_UNAVAILABLE = 7;
FAILURE_CODE_DEADLINE_EXCEEDED = 8;
FAILURE_CODE_NOT_FOUND = 9;
FAILURE_CODE_ALREADY_EXISTS = 10;
}
enum ActivationState {
ACTIVATION_STATE_UNSPECIFIED = 0;
ACTIVATION_STATE_PENDING = 1;
ACTIVATION_STATE_ACTIVE = 2;
ACTIVATION_STATE_CONFLICT = 3;
ACTIVATION_STATE_REVOKED = 4;
}
enum AdmissionState {
ADMISSION_STATE_UNSPECIFIED = 0;
ADMISSION_STATE_OPEN = 1;
ADMISSION_STATE_CLOSED = 2;
ADMISSION_STATE_DRAINING = 3;
ADMISSION_STATE_QUARANTINED = 4;
}
enum ControlAction {
CONTROL_ACTION_UNSPECIFIED = 0;
CONTROL_ACTION_PAUSE = 1;
CONTROL_ACTION_RESUME = 2;
CONTROL_ACTION_STOP = 3;
}
enum ActiveCallPolicy {
ACTIVE_CALL_POLICY_UNSPECIFIED = 0;
ACTIVE_CALL_POLICY_DRAIN = 1;
ACTIVE_CALL_POLICY_HANGUP = 2;
}
enum ExecutionState {
EXECUTION_STATE_UNSPECIFIED = 0;
EXECUTION_STATE_PREPARED = 1;
EXECUTION_STATE_PERMIT_GRANTED = 2;
EXECUTION_STATE_DISPATCHING = 3;
EXECUTION_STATE_OBSERVED = 4;
EXECUTION_STATE_UNKNOWN = 5;
EXECUTION_STATE_TERMINAL = 6;
}
enum AssetKind {
ASSET_KIND_UNSPECIFIED = 0;
ASSET_KIND_RECORDING = 1;
ASSET_KIND_TRANSCRIPT = 2;
}
enum UploadState {
UPLOAD_STATE_UNSPECIFIED = 0;
UPLOAD_STATE_REQUESTED = 1;
UPLOAD_STATE_UPLOADING = 2;
UPLOAD_STATE_COMPLETED = 3;
UPLOAD_STATE_FAILED = 4;
UPLOAD_STATE_EXPIRED = 5;
}
enum FactKind {
FACT_KIND_UNSPECIFIED = 0;
FACT_KIND_EXECUTION_ACCEPTED = 1;
FACT_KIND_CALL_STATUS = 2;
FACT_KIND_CALL_FINISHED = 3;
FACT_KIND_TRANSCRIPT_UPDATED = 4;
FACT_KIND_TRANSCRIPT_FAILED = 5;
FACT_KIND_CONTACT_OPT_OUT = 6;
FACT_KIND_RECORDING_PROGRESS = 7;
}
message RequestMeta {
string protocol_version = 1;
string request_id = 2;
string trace_id = 3;
string operation_id = 4;
int64 deadline_unix_ms = 5;
string dispatcher_epoch = 6;
string agent_id = 7;
string cell_id = 8;
string boot_id = 9;
uint64 session_generation = 10;
string idempotency_key = 11;
}
message ResponseMeta {
string protocol_version = 1;
string request_id = 2;
string trace_id = 3;
string operation_id = 4;
int64 observed_at_unix_ms = 5;
string dispatcher_epoch = 6;
string agent_id = 7;
string cell_id = 8;
string boot_id = 9;
uint64 session_generation = 10;
}
message Failure {
FailureCode code = 1;
bool retryable = 2;
string detail = 3;
string field = 4;
}
message OperationReceipt {
ResponseMeta meta = 1;
ResultCode result = 2;
Failure failure = 3;
string fact_id = 4;
string content_sha256 = 5;
int64 accepted_at_unix_ms = 6;
}
message AgentBinding {
string agent_id = 1;
string cell_id = 2;
string expected_boot_id = 3;
string dispatcher_epoch = 4;
uint64 session_generation = 5;
string endpoint_id = 6;
}
message Capability {
string name = 1;
string version = 2;
string value = 3;
}
message ResourceSample {
int64 observed_at_unix_ms = 1;
double cpu_used_ratio = 2;
int64 memory_available_bytes = 3;
int64 fd_used = 4;
int64 fd_limit = 5;
int64 spool_used_bytes = 6;
int64 spool_capacity_bytes = 7;
int64 media_ports_used = 8;
int64 media_ports_capacity = 9;
bool sample_fresh = 10;
string missing_reason = 11;
}
message AppliedConfig {
string kind = 1;
string revision = 2;
string config_sha256 = 3;
string state = 4;
int64 observed_at_unix_ms = 5;
}
message AgentStatus {
string agent_id = 1;
string cell_id = 2;
string boot_id = 3;
string software_version = 4;
string protocol_version = 5;
string asterisk_version = 6;
AdmissionState admission_state = 7;
repeated Capability capabilities = 8;
ResourceSample resources = 9;
repeated AppliedConfig applied_configs = 10;
bool mtls_authenticated = 11;
bool session_active = 12;
string status_reason = 13;
}
message Session {
string dispatcher_epoch = 1;
uint64 session_generation = 2;
int64 expires_at_unix_ms = 3;
bytes session_credential = 4;
}
message ConfigReference {
string kind = 1;
string version = 2;
string sha256 = 3;
string source = 4;
}
message UploadPolicy {
bool enabled = 1;
int64 max_asset_bytes = 2;
int64 min_retention_ms = 3;
repeated string allowed_hosts = 4;
}
message ExecutionBinding {
string tenant_id = 1;
string tenant_key = 2;
string execution_id = 3;
string task_id = 4;
string task_item_id = 5;
int64 task_revision = 6;
string call_id = 7;
string attempt_id = 8;
string agent_version_id = 9;
string route_policy_id = 10;
string caller_profile_id = 11;
}
message AssetDescriptor {
AssetKind kind = 1;
string asset_id = 2;
string call_id = 3;
string execution_id = 4;
string format = 5;
int64 size_bytes = 6;
string checksum_sha256 = 7;
int32 channels = 8;
int32 sample_rate_hz = 9;
int64 duration_ms = 10;
}
message Header {
string name = 1;
string value = 2;
}
message GetAgentStatusRequest {
RequestMeta meta = 1;
AgentBinding target = 2;
}
message GetAgentStatusResponse {
ResponseMeta meta = 1;
AgentStatus status = 2;
Failure failure = 3;
}
message ActivateAgentRequest {
RequestMeta meta = 1;
AgentBinding binding = 2;
string activation_operation_id = 3;
bytes session_nonce = 4;
int64 session_expires_at_unix_ms = 5;
}
message ActivateAgentResponse {
ResponseMeta meta = 1;
ActivationState state = 2;
Session session = 3;
Failure failure = 4;
}
message GetBootstrapRequest {
RequestMeta meta = 1;
string agent_id = 2;
string cell_id = 3;
string boot_id = 4;
uint64 session_generation = 5;
}
message GetBootstrapResponse {
ResponseMeta meta = 1;
ActivationState state = 2;
repeated ConfigReference runtime_configs = 3;
UploadPolicy upload_policy = 4;
Failure failure = 5;
}
message SetAdmissionStateRequest {
RequestMeta meta = 1;
AgentBinding target = 2;
AdmissionState state = 3;
string barrier_id = 4;
uint64 expected_admission_generation = 5;
string reason = 6;
}
message SetAdmissionStateResponse {
OperationReceipt receipt = 1;
uint64 applied_admission_generation = 2;
}
message ExecuteRequest {
RequestMeta meta = 1;
ExecutionBinding binding = 2;
bytes call_execute_json = 3;
string config_sha256 = 4;
uint64 admission_generation = 5;
string resource_reservation_id = 6;
string permit_id = 7;
}
message ExecuteResponse {
OperationReceipt receipt = 1;
ExecutionState state = 2;
}
message GetExecutionPermitRequest {
RequestMeta meta = 1;
ExecutionBinding binding = 2;
string resource_reservation_id = 3;
int64 expected_task_revision = 4;
uint64 admission_generation = 5;
string config_sha256 = 6;
}
message ExecutionPermit {
string permit_id = 1;
string resource_reservation_id = 2;
int64 issued_at_unix_ms = 3;
int64 expires_at_unix_ms = 4;
string dispatcher_epoch = 5;
uint64 session_generation = 6;
string fencing_token = 7;
string config_sha256 = 8;
}
message GetExecutionPermitResponse {
OperationReceipt receipt = 1;
ExecutionPermit permit = 2;
}
message ApplyTaskControlRequest {
RequestMeta meta = 1;
ExecutionBinding binding = 2;
ControlAction action = 3;
ActiveCallPolicy active_call_policy = 4;
int64 expected_task_revision = 5;
string reason = 6;
}
message ApplyTaskControlResponse {
OperationReceipt receipt = 1;
int64 applied_task_revision = 2;
ExecutionState state = 3;
}
message QueryExecutionRequest {
RequestMeta meta = 1;
ExecutionBinding binding = 2;
}
message ExecutionSnapshot {
ExecutionBinding binding = 1;
ExecutionState state = 2;
string call_state = 3;
string attempt_id = 4;
string reason_code = 5;
int64 observed_at_unix_ms = 6;
bool unknown = 7;
repeated AssetDescriptor assets = 8;
}
message QueryExecutionResponse {
ResponseMeta meta = 1;
ExecutionSnapshot snapshot = 2;
Failure failure = 3;
}
message ExecutionFact {
string fact_id = 1;
string content_sha256 = 2;
ExecutionBinding binding = 3;
FactKind kind = 4;
int64 observed_at_unix_ms = 5;
string source_boot_id = 6;
uint64 source_sequence = 7;
bytes payload_json = 8;
}
message ReportExecutionEventRequest {
RequestMeta meta = 1;
ExecutionFact fact = 2;
}
message ReportExecutionEventResponse {
OperationReceipt receipt = 1;
}
message RequestUploadRequest {
RequestMeta meta = 1;
ExecutionBinding binding = 2;
AssetDescriptor asset = 3;
string upload_id = 4;
}
message UploadGrant {
string upload_id = 1;
string target_url = 2;
repeated Header headers = 3;
int64 expires_at_unix_ms = 4;
string object_key = 5;
string required_checksum_sha256 = 6;
int64 max_bytes = 7;
}
message RequestUploadResponse {
OperationReceipt receipt = 1;
UploadGrant grant = 2;
UploadState state = 3;
}
message CompleteUploadRequest {
RequestMeta meta = 1;
ExecutionBinding binding = 2;
AssetDescriptor asset = 3;
string upload_id = 4;
int64 uploaded_size_bytes = 5;
string uploaded_checksum_sha256 = 6;
}
message CompleteUploadResponse {
OperationReceipt receipt = 1;
UploadState state = 2;
string oss_id = 3;
}
+51
View File
@@ -0,0 +1,51 @@
{
"package": "agent.v1",
"service": "AgentControlService",
"status": "project-owned-development-baseline",
"external_authority": false,
"generator": {
"tool": "buf",
"configuration": "buf.gen.yaml",
"plugins": [
"protoc-gen-go",
"protoc-gen-go-grpc"
]
},
"files": [
{
"path": "buf.gen.yaml",
"bytes": 181,
"sha256": "00fa0ef6ae59e067dc005e9995ada15d92bbd60aec7e4c10b3b36463da30745f"
},
{
"path": "buf.yaml",
"bytes": 100,
"sha256": "62e59b0299c930c1eb5324c8a139232dd59109c4f8dbac577bc7ca0f8627d96b"
},
{
"path": "proto/ERRORS.md",
"bytes": 4706,
"sha256": "0fb4ae7a41c3e7127645e393ee175b660f2aba9f9d27b8c8c033f1f6c334d163"
},
{
"path": "proto/README.md",
"bytes": 1579,
"sha256": "d2754ceb47fd54bdfc4a05c5b5be818fe984399748b502f7ea8707fddf56c7dd"
},
{
"path": "proto/agent/v1/agent.proto",
"bytes": 10681,
"sha256": "083d17eb761566e533d91d63cb118da056de498ab4187663de0294704c93e7fa"
},
{
"path": "gen/agent/v1/agent.pb.go",
"bytes": 144947,
"sha256": "01ce926ed3d3e90888719d361ffad2e6da84bb2bc9df7b06f0bbab3ec901a9df"
},
{
"path": "gen/agent/v1/agent_grpc.pb.go",
"bytes": 23035,
"sha256": "e87a0114e6d7d1d3d0801a7bba302e76945e3d244ec67cf56249eef0e838617d"
}
]
}