refactor(proc)!: 版本身份只由版本号本身承载,删除全部自定版本号

内核侧配合 SDK 仓 b2872f3:兼容性不再依赖任何自造版本号。

删除三处自造号:
  - ProtocolVersion(协议号)→ 改为比对主版本段(MajorVersion)
  - shmVersion(共享段 header)→ 改为校验描述符个数(field_count)
  - unifiedVersion(SuperBlock)→ 结构自描述,不再校验

握手契约随之调整:
  HandshakeParams:  protocol(int) → core_version(string)
  HandshakeResult:  protocol(int) → sdk_version 承担判据
  内核比对 MajorVersion(meta.Version) == MajorVersion(res.SDKVersion),
  跨主版本即拒并提示重编(主版本跃迁 = 破坏性变更的定义)。

为什么废除:额外的号需要人去记得升,而它没有任何结构性约束兜底,
必然腐烂。实证:ProtocolVersion 常量在多次布局变更里只升过一次,
对应的守卫测试长期失败无人察觉(期望 18/1,实际 19/2)。

新增判据:
  - TestProcess_MajorVersionMismatchRejected:跨主版本被拒(testdata/badproto)
  - TestProcess_SameMajorDifferentMinorAccepted:同主版本放行(新增 testdata/oldminor)
    后者是 §七「1.x 内纯追加、不调新面不用重编」规约的落地 —— 若误用全等
    比较,未重编的存量插件会被无谓地全部拒绝。
  两条均经变异验证:改成全等比较 → 前者过/后者挂;删掉校验 → 前者挂。
This commit is contained in:
JianFeeeee
2026-10-13 09:39:34 +08:00
parent 31de3921ed
commit ceb8a7b165
18 changed files with 184 additions and 88 deletions

View File

@ -496,12 +496,20 @@ GITCODE_REPO=JianFeeeee/homeagent-sdk ASSET_DIR=<sdk>/dist/release \
测试替身(`fakeCoreSDK`、`injectCapture`、`capability_test.go` 的手工方法清单)。
还要同步 `yaegi/mocksdk`——它没有任何代码对着编译,漂移**不会被编译器抓到**。
### 4. 「接口纯追加」不等于「无需重编」
### 4. 兼容性只认主版本号(不再有独立的协议号)
插件运行协议版本(`ProtocolVersion`)与 SDK 接口版本是**两件事**。
协议升级(如 1.2.0 的 fd3 布局变更,不支持滚动升级)时,`ProtocolVersion` 不匹配
会在握手时被明确拒绝并提示用配套 `hmapdev` 重编。
必须把两者分开说,否则会被误读成"既然纯追加就还能用旧产物"。
**不自定协议版本号**:兼容性由双方版本号的**主版本段**表态
(内核 `meta.Version` ↔ 插件 `sdk_version`)。主版本相同即兼容,跨主版本则
在握手时被拒绝并提示用配套 `hmapdev` 重编。
为什么不要独立协议号:语义版本变动频繁(修 bug、加字段),额外的号必然被
漏升而腐烂(历史实证:曾有一个 `ProtocolVersion` 常量,多次布局变更里只升过
一次,而对应守卫测试长期失败无人察觉)。兼容性只挂在**始终会被发的那个号**上,
就不存在「忘同步」这个失效模式。
布局一致性(如 1.2.0 的 fd3 → 统一共享区)另由**结构派生事实**把关:
统一区魔数 + `field_count`(描述符个数)在握手上显式校验。
它们是二进制里的实际值,不需人去维护,也不同“谁记得升号”。
### 5. 合回 main 前要同步的东西

View File

@ -88,15 +88,15 @@ func FullVersion() string {
return KernelName + " v" + Version + " (" + Commit + ")"
}
// ---- 协议版本 ----
// ---- 协议兼容性 ----
//
// 子进程 RPC 的协议版本是一个独立的小整数,与内核语义版本解耦:
// 语义版本变动频繁(修 bug、加字段),而 wire 协议只在**帧格式或握手语义**
// 变化时才升。当前值见 internal/plugin/proc/protocol.go 的 ProtocolVersion。
// **不自定协议号**:兼容性只认双方版本号的**主版本段**。
// 主版本相同即兼容(1.x 内全是纯追加);跨主版本则是破坏性变更
// (如 1.0.0 从 C ABI 改子进程),握手时显式拒绝并提示重编。
//
// C ABI 时代的 ABIVersion / CABINum / 51 个 Core<Method> 整数 ID 已随
// Part 6.2 删除 internal/plugin/cabi/ 一并退场:
// - 整数 method id 平移为 method 名字符串(proc/protocol.go 的 Method* 常量)
// - 版本协商改为握手帧里的 protocol 字段
// - 版本协商改为握手帧里的 core_version / sdk_version 主版本比对
//
// 保留那些常量只会让人以为它们还在生效。

View File

@ -255,10 +255,9 @@ func (p *Process) handshake(timeout time.Duration) error {
defer cancel()
raw, err := p.CallContext(ctx, MethodHandshake, HandshakeParams{
Protocol: ProtocolVersion,
CoreVersion: meta.Version,
PluginName: p.name,
ShmVersion: shmVersion,
FieldCount: uint32(stageFieldCount),
ShmSize: p.shmSize,
EvtRingSize: p.evtRingSize,
})
@ -269,12 +268,13 @@ func (p *Process) handshake(timeout time.Duration) error {
if err := json.Unmarshal(raw, &res); err != nil {
return fmt.Errorf("proc: %s 握手应答解析失败: %w", p.name, err)
}
if res.Protocol != ProtocolVersion {
return fmt.Errorf("proc: %s 协议版本不匹配(插件 %d,内核 %d)——请用配套 hmapdev 重编",
p.name, res.Protocol, ProtocolVersion)
// 主版本号是唯一兼容判据:跨主版本必是被破坏性变更分开的两端。
if want, got := MajorVersion(meta.Version), MajorVersion(res.SDKVersion); want != got {
return fmt.Errorf("proc: %s 主版本不匹配(插件 SDK %s,内核 %s)——请用配套 hmapdev 重编",
p.name, res.SDKVersion, meta.Version)
}
log.Printf("[proc] %s 已建链(pid=%d protocol=%d sdk=%s)",
p.name, p.PID(), res.Protocol, res.SDKVersion)
log.Printf("[proc] %s 已建链(pid=%d sdk=%s core=%s)",
p.name, p.PID(), res.SDKVersion, meta.Version)
return nil
}

View File

@ -315,19 +315,19 @@ func TestProcess_ConcurrentCallsRouteCorrectly(t *testing.T) {
}
}
// 协议版本不匹配必须显式拒绝,不能半兼容运行。
// 主版本不匹配必须显式拒绝,不能半兼容运行。
//
// v2 引入的必要性就靠这条:v1 插件(只读内联 args)遇上 v2 内核,跟 v2 插件
// 发 blocks_ref 给 v1 内核,都会静默失效、不报任何错。只有在这里显式拦下,
// 那双错配才会变成一条带修复指令的启动失败。
func TestProcess_ProtocolMismatchRejected(t *testing.T) {
// 为什么需要显式拦下:主版本两端(如 C ABI 时代与子进程时代)的帧格式与
// 共享段布局都不同,错配不会报错、只会静默失效(空参数、字段被忽略)。
// 判据是双方版本号的**主版本段**,不是任何自定协议号。
func TestProcess_MajorVersionMismatchRejected(t *testing.T) {
bin := buildTestPlugin(t, "badprotoplugin.go")
_, err := Spawn("badproto", bin, Options{Handler: noopHandler})
if err == nil {
t.Fatal("协议版本不匹配应拒绝建链")
t.Fatal("主版本不匹配应拒绝建链")
}
if !strings.Contains(err.Error(), "协议版本不匹配") {
t.Errorf("错误应说明版本不匹配,实际: %v", err)
if !strings.Contains(err.Error(), "主版本不匹配") {
t.Errorf("错误应说明主版本不匹配,实际: %v", err)
}
// 运维可读性:光报“不匹配”不能定位到行动。生产上碰到它的现场是
// “只更新了内核没重编插件”,所以错误里必须带出这条修复指令。
@ -336,6 +336,19 @@ func TestProcess_ProtocolMismatchRejected(t *testing.T) {
}
}
// 同主版本内的中版本差异(内核 1.4.0 vs 插件 SDK 1.1.0)必须**允许**建链。
//
// 这是 §七「1.x 内纯追加、不调新面就不用重编」规约的落地:若不校验主版本
// 而是全等比较,未重编的存量插件会被无谓地全部拒绝。
func TestProcess_SameMajorDifferentMinorAccepted(t *testing.T) {
bin := buildTestPlugin(t, "oldminorplugin.go")
p, err := Spawn("oldminor", bin, Options{Handler: noopHandler})
if err != nil {
t.Fatalf("同主版本(1.x)应允许建链: %v", err)
}
defer func() { _ = p.Stop() }()
}
func TestProcess_SpawnRequiresHandler(t *testing.T) {
if _, err := Spawn("x", "/bin/true", Options{}); err == nil {
t.Fatal("缺少 Handler 应报错(插件无法回调内核)")

View File

@ -1,6 +1,9 @@
package proc
import "encoding/json"
import (
"encoding/json"
"strings"
)
// RPC 协议定义:控制面(§3.2 method id 平移为 method 名)。
//
@ -12,17 +15,29 @@ import "encoding/json"
// JSON 序列化 3-8 µs 对比 LLM 单轮 2-8 秒占 0.0001%,可读性与可调试性更值。
// 大 payload(多媒体二进制)走共享内存 arena,不进 RPC 帧(§3.3 实验 10:18-22x)。
// 协议版本:与共享段版本独立演进。
// 插件握手时上报,内核校验——不匹配显式拒绝,避免半兼容导致的诡异行为。
// 协议兼容性判据:**大版本号唯一**。
//
// v2(§13.6 / §13.13):内核→插件的 payload 改为调用帧承载。
// v1 插件只读内联 args,遇上 v2 内核会拿到空参数;v2 插件发 blocks_ref,
// v1 内核反序列化时静默忽略(旧内核 io.setToolBlocks 还是桩)。两种错配
// 都不会报错,只会静默失效——所以必须 bump 版本,让它在握手上就**显式**失败。
// 不自定协议号。插件的兼容性由双方各自的版本号表态:内核侧 meta.Version、
// 插件侧 sdk_version(SDK 版本跟随内核中版本,patch 恒为 .0)。
// 版本号唯一事实源就是这两个 meta.Version(见 docs/zh/ci-cd-runbook.md)。
//
// 部署纪律:内核与全部插件必须同批重建、同批安装;改协议就要改这个常量,
// 不得依赖“两边大致兼容”。
const ProtocolVersion = 2
// 判据是**主版本相同**:
// - 1.x ↔ 1.y:兼容。1.0.0 之后全是纯追加,不调新面就不受影响。
// - 跨主版本(如 1.x → 2.x):拒绝建链,提示用配套 hmapdev 重编。
// 主版本跃迁正是**破坏性变更**的定义(1.0.0 从 C ABI 改子进程即属此列),
// 所以它天然是正确的分界,不需要另一个号来复述这件事。
//
// 布局一致性另由 stageFieldCount(描述符个数)把关:它是结构的派生事实,
// 也不是版本号。两者错配都在握手上显式失败,不半兼容运行。
// MajorVersion 取版本号的主版本段("1.4.0" → "1","" → "")。
func MajorVersion(v string) string {
v = strings.TrimPrefix(strings.TrimSpace(v), "v")
if i := strings.IndexByte(v, '.'); i >= 0 {
return v[:i]
}
return v
}
// Direction 无需显式字段:靠 Method 是否为空区分请求与响应
// (与 clawhubadapter/sidecar 的成熟做法一致)。
@ -184,19 +199,20 @@ const (
// HandshakeParams 是内核 → 插件的建链首帧:告知内核侧规格。
type HandshakeParams struct {
Protocol int `json:"protocol"` // 内核支持的协议版本
CoreVersion string `json:"core_version"` // 内核版本(诊断用)
CoreVersion string `json:"core_version"` // 内核版本(插件据此判主版本兼容)
PluginName string `json:"plugin_name"` // 内核分配的插件名
ShmVersion uint32 `json:"shm_version"`
ShmSize int `json:"shm_size"`
// FieldCount 是 StageContext 描述符个数。插件据此与自己的编译期
// 字段数比对:不匹配意味着标志位基址会错位,必须重编(而非静默错读)。
FieldCount uint32 `json:"field_count"`
ShmSize int `json:"shm_size"`
// EvtRingSize 是事件环段大小(0 表示不支持事件环)。插件据此 mmap fd 4。
EvtRingSize int `json:"evt_ring_size,omitempty"`
}
// HandshakeResult 是插件 → 内核的建链应答:上报自身信息。
type HandshakeResult struct {
Protocol int `json:"protocol"` // 必须等于 ProtocolVersion
SDKVersion string `json:"sdk_version"` // 插件编译时链接的公开 SDK 版本
// SDKVersion 是插件编译时链接的公开 SDK 版本。内核据此判主版本兼容。
SDKVersion string `json:"sdk_version"`
PluginName string `json:"plugin_name"`
PID int `json:"pid"`
}

View File

@ -1,7 +1,7 @@
// Package proc 实现外部插件的子进程加载通道(plugin.bin)。
//
// 数据面设计:定长头 + arena,变长数据用相对基址的 {off,len} 描述符
//(相对偏移是关键:各进程 mmap 到不同虚拟地址也能正确解引用)。
// (相对偏移是关键:各进程 mmap 到不同虚拟地址也能正确解引用)。
//
// homed ──spawn──> plugin(纯 Go 二进制,无 cgo)
// ├── stdio JSON-RPC 控制面:51 个 method id 平移为 method 名(§3.2)
@ -17,19 +17,22 @@ import (
"sync/atomic"
)
// 共享段魔数与版本,用于挂载时校验对端布局一致。
// shmMagic 标识 HomeAgent 的 StageContext 共享段。
//
// v2(2026-10-12):新增 fExtraBag —— Extra 具名 4 键之外的插件自建键
// (如 qq 的 qq_auth 绑帧身份)跨进程/跨 stage 透传。加字段必升版:
// AttachSegment 与插件握手对版本失败即拒,旧插件不会静默错读新布局。
// **不设版本号**:段布局自描述 —— 内核把描述符个数写进 header
// (offFieldCount),插件据此定位标志位,不再用自己编译期的常量推算。
// 这样「加字段」不再需要一个人工维护、必然腐烂的版本号来兜底:
// 布局不一致由描述符个数这种**派生事实**自动检出(见 AttachSegment)。
//
// 协议兼容性由双方版本号的**主版本**在握手上校验(见 protocol.go 的
// MajorVersion)。
const (
shmMagic uint32 = 0x48415348 // "HASH" — HomeAgent SHared
shmVersion uint32 = 2
shmMagic uint32 = 0x48415348 // "HASH" — HomeAgent SHared
)
// 段布局(所有偏移均相对**段起始**,arena 内偏移相对 arenaBase):
//
// [0, headerSize) Header:魔数/版本/arena 游标
// [0, headerSize) Header:魔数/描述符个数/arena 游标
// [headerSize, ctxEnd) ShmStageCtx:每字段一个 Slice{off,len} 描述符
// [arenaBase, arenaBase+arenaCap) arena:append-only 变长数据区
//
@ -38,13 +41,13 @@ const (
headerSize = 64
// Header 内字段偏移
offMagic = 0 // uint32
offVersion = 4 // uint32
offArenaBase = 8 // uint32
offArenaCap = 12 // uint32
offArenaUsed = 16 // uint32(原子 bump 游标)
offCtxBase = 20 // uint32
offSeq = 24 // uint64:每次成功写回自增,供乐观读校验
offMagic = 0 // uint32
offFieldCount = 4 // uint32:StageContext 描述符个数(插件据此定位标志位)
offArenaBase = 8 // uint32
offArenaCap = 12 // uint32
offArenaUsed = 16 // uint32(原子 bump 游标)
offCtxBase = 20 // uint32
offSeq = 24 // uint64:每次成功写回自增,供乐观读校验
)
// Slice 是 arena 内变长数据的描述符,off 相对 arenaBase。
@ -136,7 +139,7 @@ func NewSegment(data []byte) (*Segment, error) {
arenaCap := uint32(len(data)) - arenaBase
binary.LittleEndian.PutUint32(data[offMagic:], shmMagic)
binary.LittleEndian.PutUint32(data[offVersion:], shmVersion)
binary.LittleEndian.PutUint32(data[offFieldCount:], uint32(stageFieldCount))
binary.LittleEndian.PutUint32(data[offArenaBase:], arenaBase)
binary.LittleEndian.PutUint32(data[offArenaCap:], arenaCap)
binary.LittleEndian.PutUint32(data[offArenaUsed:], 0)
@ -151,7 +154,10 @@ func NewSegment(data []byte) (*Segment, error) {
}
// AttachSegment 挂载一块已由 NewSegment 初始化的区域(插件进程侧调用)。
// 校验魔数与版本,避免版本不一致时静默错读。
//
// 校验魔数与**描述符个数**(而非人工版本号):个数是段布局的派生事实,
// 加字段时自动变化,因此不需要谁记得去升一个号。插件若按旧字段数推算
// 标志位基址会静默错位,这里显式拒绝。
func AttachSegment(data []byte) (*Segment, error) {
if len(data) < headerSize+ctxSize {
return nil, fmt.Errorf("proc: 共享段过小(%d 字节)", len(data))
@ -159,9 +165,9 @@ func AttachSegment(data []byte) (*Segment, error) {
if got := binary.LittleEndian.Uint32(data[offMagic:]); got != shmMagic {
return nil, fmt.Errorf("proc: 共享段魔数不匹配(0x%x,期望 0x%x)", got, shmMagic)
}
if got := binary.LittleEndian.Uint32(data[offVersion:]); got != shmVersion {
return nil, fmt.Errorf("proc: 共享段版本不匹配(%d,本内核 %d)——插件需用配套 hmapdev 重编",
got, shmVersion)
if got := binary.LittleEndian.Uint32(data[offFieldCount:]); got != uint32(stageFieldCount) {
return nil, fmt.Errorf("proc: 共享段描述符个数不匹配(%d,本内核 %d)——插件需用配套 hmapdev 重编",
got, stageFieldCount)
}
return &Segment{data: data}, nil
}

View File

@ -187,7 +187,7 @@ func main() {
shm = m[ctxOff : ctxOff+ctxSize]
}
send(response{ID: req.ID, Result: map[string]interface{}{
"protocol": 2, "sdk_version": "test",
"sdk_version": "1.0.0",
"plugin_name": "append-" + tag, "pid": os.Getpid(),
}})

View File

@ -1,6 +1,9 @@
//go:build ignore
// badprotoplugin 上报错误的协议版本,验证内核显式拒绝而非半兼容运行。
// badprotoplugin 上报不同主版本的 SDK,验证内核显式拒绝而非半兼容运行。
//
// 主版本跃迁是破坏性变更的定义(如 1.0.0 从 C ABI 改子进程),
// 所以它是兼容性的正确分界——不需要另一个自定的协议号来复述。
package main
import (
@ -38,8 +41,7 @@ func main() {
}
if req.Method == "handshake" {
send(response{ID: req.ID, Result: map[string]interface{}{
"protocol": 999, // 故意不匹配
"sdk_version": "ancient",
"sdk_version": "99.0.0", // 主版本不同,故意不匹配
"plugin_name": "badproto",
"pid": os.Getpid(),
}})

View File

@ -95,8 +95,7 @@ func main() {
switch req.Method {
case "handshake":
send(response{ID: req.ID, Result: map[string]interface{}{
"protocol": 2,
"sdk_version": "test",
"sdk_version": "1.0.0",
"plugin_name": "cb",
"pid": os.Getpid(),
}})

View File

@ -40,7 +40,7 @@ func main() {
switch req.Method {
case "handshake":
send(response{ID: req.ID, Result: map[string]interface{}{
"protocol": 2, "sdk_version": "test", "plugin_name": "crash", "pid": os.Getpid(),
"sdk_version": "1.0.0", "plugin_name": "crash", "pid": os.Getpid(),
}})
case "tool.invoke":
// 模拟插件 bug:直接 panic,进程带非零码退出

View File

@ -43,8 +43,7 @@ func main() {
switch req.Method {
case "handshake":
send(response{ID: req.ID, Result: map[string]interface{}{
"protocol": 2,
"sdk_version": "test",
"sdk_version": "1.0.0",
"plugin_name": "echo",
"pid": os.Getpid(),
}})

View File

@ -57,7 +57,7 @@ func main() {
switch req.Method {
case "handshake":
send(response{ID: req.ID, Result: map[string]interface{}{
"protocol": 2, "sdk_version": "test", "plugin_name": "fork", "pid": os.Getpid(),
"sdk_version": "1.0.0", "plugin_name": "fork", "pid": os.Getpid(),
}})
case "tool.invoke":
// 不回应答,直接退出:模拟插件突然死亡(崩溃/被 kill)。

View File

@ -42,7 +42,7 @@ func main() {
switch req.Method {
case "handshake":
send(response{ID: req.ID, Result: map[string]interface{}{
"protocol": 2, "sdk_version": "test", "plugin_name": "hang", "pid": os.Getpid(),
"sdk_version": "1.0.0", "plugin_name": "hang", "pid": os.Getpid(),
}})
case "tool.invoke":
// 永久卡住,永不回应答

View File

@ -0,0 +1,57 @@
//go:build ignore
// oldminorplugin 上报同主版本但更早的中版本 SDK,验证内核**不会**无谓拒绝。
//
// 规约(docs/git-branching.md §七.1):1.x 内的中版本跃迁全部是纯追加 ——
// 新增方法由插件调用、内核实现,不调就不受影响。因此未重编的存量插件
// (如用 SDK 1.0.0/1.1.0 编的)必须能在新内核上直接建链。
//
// 若判据误用「全等比较」而非「主版本比较」,这条会失败 —— 正是它的价值。
package main
import (
"bufio"
"encoding/json"
"os"
)
type request struct {
ID uint64 `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
type response struct {
ID uint64 `json:"id"`
Result interface{} `json:"result,omitempty"`
Error string `json:"error,omitempty"`
}
func main() {
in := bufio.NewScanner(bufio.NewReader(os.Stdin))
out := bufio.NewWriter(os.Stdout)
send := func(v interface{}) {
b, _ := json.Marshal(v)
out.Write(b)
out.WriteByte('\n')
out.Flush()
}
for in.Scan() {
var req request
if err := json.Unmarshal(in.Bytes(), &req); err != nil {
continue
}
if req.Method == "handshake" {
send(response{ID: req.ID, Result: map[string]interface{}{
"sdk_version": "1.0.0", // 同主版本,更早的中版本
"plugin_name": "oldminor",
"pid": os.Getpid(),
}})
continue
}
if req.ID != 0 {
send(response{ID: req.ID})
}
}
}

View File

@ -139,7 +139,7 @@ func main() {
shm = m[ctxOff : ctxOff+ctxSize]
}
send(response{ID: req.ID, Result: map[string]interface{}{
"protocol": 2, "sdk_version": "test", "plugin_name": "readonly", "pid": os.Getpid(),
"sdk_version": "1.0.0", "plugin_name": "readonly", "pid": os.Getpid(),
}})
case "plugin.init":

View File

@ -354,7 +354,7 @@ func main() {
shm = m[ctxOff : ctxOff+ctxSize]
}
send(response{ID: req.ID, Result: map[string]interface{}{
"protocol": 2, "sdk_version": "test", "plugin_name": "stage", "pid": os.Getpid(),
"sdk_version": "1.0.0", "plugin_name": "stage", "pid": os.Getpid(),
}})
case "plugin.init":

View File

@ -34,8 +34,7 @@ import (
const (
// unifiedMagic 标识统一共享内存区域。
unifiedMagic uint32 = 0x554D5352 // "UMSR" — Unified Memory Shared Region
unifiedVersion uint32 = 1
unifiedMagic uint32 = 0x554D5352 // "UMSR" — Unified Memory Shared Region
superBlockSize = 64 // SuperBlock 占前 64 字节
)
@ -43,7 +42,7 @@ const (
// SuperBlock 布局:64 字节,所有偏移相对区域起始。
//
// [0,4) magic
// [4,8) version
// [4,8) reserved(原 version,已废:SuperBlock 靠 magic + capacity 自描述)
// [8,16) generation(atomic uint64,resize 时 bump)
// [16,20) capacity(区域总字节数)
// [20,24) ctxOff(StageContext segment 偏移)
@ -55,7 +54,7 @@ const (
// [44,64) reserved
const (
sbOffMagic = 0
sbOffVersion = 4
sbOffReserved0 = 4 // 保留(原 version 槽位,布局不变以免动其余偏移)
sbOffGeneration = 8
sbOffCapacity = 16
sbOffCtxOff = 20
@ -133,7 +132,6 @@ func initUnifiedRegion(data []byte, ctxTotal, evtTotal int) (*unifiedRegion, err
arenaCap := cap - arenaOff
putU32(data[sbOffMagic:], unifiedMagic)
putU32(data[sbOffVersion:], unifiedVersion)
putU64(data[sbOffGeneration:], 0)
putU32(data[sbOffCapacity:], cap)
putU32(data[sbOffCtxOff:], ctxOff)
@ -163,10 +161,8 @@ func attachUnifiedRegion(data []byte) (*unifiedRegion, error) {
if magic != unifiedMagic {
return nil, fmt.Errorf("unified: 魔数不匹配(0x%x,期望 0x%x)", magic, unifiedMagic)
}
ver := getU32(data[sbOffVersion:])
if ver != unifiedVersion {
return nil, fmt.Errorf("unified: 版本不匹配(%d,期望 %d)", ver, unifiedVersion)
}
// 不自描述校验版本号:SuperBlock 靠 magic + capacity + 各段越界检查把关
//(这些是结构的派生事实,不存在“忘同步一个人工号”的失效模式)。
ctxOff := getU32(data[sbOffCtxOff:])
ctxSize := getU32(data[sbOffCtxSize:])
evtOff := getU32(data[sbOffEvtOff:])

View File

@ -103,15 +103,15 @@ func FullVersion() string {
return SDKName + " v" + Version + " (" + Commit + ")"
}
// ---- 协议版本 ----
// ---- 协议兼容性 ----
//
// 子进程 RPC 的协议版本是一个独立的小整数,与 SDK/内核语义版本解耦:
// 语义版本变动频繁(修 bug、加字段),而 wire 协议只在**帧格式或握手语义**
// 变化时才升。当前值见核心仓 internal/plugin/proc/protocol.go 的 ProtocolVersion。
// **不自定协议号**:兼容性只认双方版本号的**主版本段**。
// 主版本相同即兼容(1.x 内全是纯追加);跨主版本则是破坏性变更
// (如 1.0.0 从 C ABI 改子进程),握手时显式拒绝并提示重编。
//
// C ABI 时代的 ABIVersion / CABINum / 51 个 Core<Method> 整数 ID 已随
// Part 6.2 删除 internal/plugin/cabi/ 一并退场:
// - 整数 method id 平移为 method 名字符串(proc/protocol.go 的 Method* 常量)
// - 版本协商改为握手帧里的 protocol 字段
// - 版本协商改为握手帧里的 core_version / sdk_version 主版本比对
//
// 保留那些常量只会让人以为它们还在生效。