q2c 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- q2c-0.1.1/ADAPTERS.md +131 -0
- q2c-0.1.1/ARCHITECTURE.md +121 -0
- q2c-0.1.1/CHANGELOG.md +160 -0
- q2c-0.1.1/CONTRIBUTING.md +63 -0
- q2c-0.1.1/DELIVERY-v0.1.0.md +68 -0
- q2c-0.1.1/DELIVERY-v0.1.1.md +72 -0
- q2c-0.1.1/LICENSE +21 -0
- q2c-0.1.1/MANIFEST.in +30 -0
- q2c-0.1.1/PKG-INFO +250 -0
- q2c-0.1.1/PROTOCOL.md +245 -0
- q2c-0.1.1/Q2C-BOUNDARY-AUDIT.md +648 -0
- q2c-0.1.1/Q2C-PRODUCT-SPEC-v0.1.md +422 -0
- q2c-0.1.1/README.md +226 -0
- q2c-0.1.1/SECURITY.md +86 -0
- q2c-0.1.1/TASKBOOK-Q2C-PRODUCT-01.md +512 -0
- q2c-0.1.1/bin/q2c +5 -0
- q2c-0.1.1/examples/quickstart.sh +42 -0
- q2c-0.1.1/examples/review-handoff.sh +28 -0
- q2c-0.1.1/pyproject.toml +48 -0
- q2c-0.1.1/q2c/__init__.py +27 -0
- q2c-0.1.1/q2c/__main__.py +8 -0
- q2c-0.1.1/q2c/_version.py +16 -0
- q2c-0.1.1/q2c/ack.py +297 -0
- q2c-0.1.1/q2c/adapters/__init__.py +20 -0
- q2c-0.1.1/q2c/adapters/_spawn.py +170 -0
- q2c-0.1.1/q2c/adapters/base.py +178 -0
- q2c-0.1.1/q2c/adapters/codex.py +252 -0
- q2c-0.1.1/q2c/adapters/inproc.py +103 -0
- q2c-0.1.1/q2c/adapters/loopback.py +188 -0
- q2c-0.1.1/q2c/adapters/qoder.py +168 -0
- q2c-0.1.1/q2c/artifacts.py +199 -0
- q2c-0.1.1/q2c/cli.py +341 -0
- q2c-0.1.1/q2c/config.py +145 -0
- q2c-0.1.1/q2c/ledger.py +314 -0
- q2c-0.1.1/q2c/protocol.py +557 -0
- q2c-0.1.1/q2c/security.py +443 -0
- q2c-0.1.1/q2c/sessions.py +163 -0
- q2c-0.1.1/q2c/stub_responder.py +119 -0
- q2c-0.1.1/q2c/trace.py +231 -0
- q2c-0.1.1/q2c/transport.py +1019 -0
- q2c-0.1.1/q2c.egg-info/PKG-INFO +250 -0
- q2c-0.1.1/q2c.egg-info/SOURCES.txt +82 -0
- q2c-0.1.1/q2c.egg-info/dependency_links.txt +1 -0
- q2c-0.1.1/q2c.egg-info/entry_points.txt +2 -0
- q2c-0.1.1/q2c.egg-info/top_level.txt +1 -0
- q2c-0.1.1/setup.cfg +4 -0
- q2c-0.1.1/tests/__init__.py +6 -0
- q2c-0.1.1/tests/helpers.py +160 -0
- q2c-0.1.1/tests/test_ack.py +331 -0
- q2c-0.1.1/tests/test_cli.py +137 -0
- q2c-0.1.1/tests/test_codex_stream_real_fixture.py +92 -0
- q2c-0.1.1/tests/test_credentials.py +327 -0
- q2c-0.1.1/tests/test_doc_references.py +107 -0
- q2c-0.1.1/tests/test_expiry_cancel_adapter_failure.py +196 -0
- q2c-0.1.1/tests/test_idempotency.py +138 -0
- q2c-0.1.1/tests/test_packaging.py +673 -0
- q2c-0.1.1/tests/test_process_safety.py +189 -0
- q2c-0.1.1/tests/test_protocol.py +309 -0
- q2c-0.1.1/tests/test_protocol_doc_sync.py +206 -0
- q2c-0.1.1/tests/test_qoder_terminal_real_fixture.py +224 -0
- q2c-0.1.1/tests/test_real_bidirectional.py +101 -0
- q2c-0.1.1/tests/test_real_handoff_evidence.py +325 -0
- q2c-0.1.1/tests/test_recovery.py +254 -0
- q2c-0.1.1/tests/test_release_domain_local_paths.py +114 -0
- q2c-0.1.1/tests/test_release_manifest.py +300 -0
- q2c-0.1.1/tests/test_retry_delivery.py +190 -0
- q2c-0.1.1/tests/test_shell_portability.py +152 -0
- q2c-0.1.1/tests/test_stores_and_honesty.py +358 -0
- q2c-0.1.1/tests/test_synthetic_fanout.py +133 -0
- q2c-0.1.1/tests/test_trace_readings.py +151 -0
- q2c-0.1.1/tests/test_vendor_claims.py +173 -0
- q2c-0.1.1/tools/ci-clean-machine.sh +134 -0
- q2c-0.1.1/tools/ci-readings.py +94 -0
- q2c-0.1.1/tools/clean-machine-test.sh +101 -0
- q2c-0.1.1/tools/count-audit.py +54 -0
- q2c-0.1.1/tools/gate-commit.sh +34 -0
- q2c-0.1.1/tools/make-manifest.py +244 -0
- q2c-0.1.1/tools/pkg-install-test.sh +355 -0
- q2c-0.1.1/tools/pkg-rehearsal.sh +29 -0
- q2c-0.1.1/tools/pypi-name-check.py +114 -0
- q2c-0.1.1/tools/real-handoff.py +403 -0
- q2c-0.1.1/tools/report-readings.py +77 -0
- q2c-0.1.1/tools/smoke-cli.py +69 -0
- q2c-0.1.1/tools/teeth.py +422 -0
q2c-0.1.1/ADAPTERS.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# 适配器(Adapter)
|
|
2
|
+
|
|
3
|
+
契约七枚:`capabilities / start / resume / send / receive / status / cancel`
|
|
4
|
+
(`q2c/adapters/base.py`)。分工写死:
|
|
5
|
+
|
|
6
|
+
| 位置 | 职责 |
|
|
7
|
+
|---|---|
|
|
8
|
+
| 适配器 | 对侧字段叫什么、命令怎么起、回执/事件流什么形状;把事实折成 `Receipt` |
|
|
9
|
+
| `q2c/ack.py` | 这些事实够不够叫**送达**(六闸+终态闸),对所有适配器同一套尺 |
|
|
10
|
+
| `q2c/transport.py` | 要不要投、投到哪一步、预算、恢复;不知道任何 provider 字段名 |
|
|
11
|
+
|
|
12
|
+
依赖顺序(产品定义):**官方 SDK > 公开 CLI > 公开兼容协议**。
|
|
13
|
+
v0.1 的两个正式适配器走**公开 CLI**,并显式列出不碰的东西。
|
|
14
|
+
|
|
15
|
+
## 在册四个适配器
|
|
16
|
+
|
|
17
|
+
| 名字 | live | terminal_evidence | split_send_receive | 用途 |
|
|
18
|
+
|---|---|---|---|---|
|
|
19
|
+
| `codex` | 是(需 `Q2C_LIVE=1`) | 是 | 是 | OpenAI Codex CLI 会话 |
|
|
20
|
+
| `qoder` | 是(需 `Q2C_LIVE=1`) | 是 | **否**(同步调用) | Qoder CLI 会话 |
|
|
21
|
+
| `loopback` | 否 | 否 | 是 | Quick Start 与真子进程判据 |
|
|
22
|
+
| `inproc` | 否 | 否 | 是 | 负载判据专用(LOAD-ONLY) |
|
|
23
|
+
| `notify`(配置项,不是适配器) | — | — | — | 失败通知通道 `notification_cmd` |
|
|
24
|
+
|
|
25
|
+
## 1. Codex
|
|
26
|
+
|
|
27
|
+
公开 CLI 形态:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
投递 codex queue --thread <线程号> --message <派发文>
|
|
31
|
+
取答复 codex exec resume --json -c sandbox_mode="read-only" --skip-git-repo-check \
|
|
32
|
+
-o <末条消息文件> <线程号> <提示>
|
|
33
|
+
凭据 codex login status (只问能不能无人值守,stdout 丢弃)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**不碰**:Codex 私有 SQLite 队列库、`~/.codex/sessions/**/*.jsonl` 的内部形状、
|
|
37
|
+
内部 Rust 类型、隐藏参数。旧内核拿这些当"是否还有未消费项/这一轮是否已答复"的证据;
|
|
38
|
+
产品不用,因为那是内部结构,随时会变。
|
|
39
|
+
|
|
40
|
+
**代价(如实记账,不当能力报)**:恢复阶梯里"从会话落盘件反证这一轮已答复"两档在产品中不存在,
|
|
41
|
+
对应情形读成 `UNKNOWN ⇒ 停手等人`,不自动重发。宁可停手,不可重复烧一次真调用。
|
|
42
|
+
|
|
43
|
+
**限制**:公开 CLI 没有"零副作用新建空线程"的入口 ⇒ `start()` 直接拒绝,
|
|
44
|
+
要求 `q2c sessions bind` 登记已有线程号(不猜号、不另起一条把上下文混进来)。
|
|
45
|
+
|
|
46
|
+
## 2. Qoder
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
qoderclicn -p -r <会话号> [-w <工作区>] --permission-mode auto --output-format json [--model …] <正文>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
两点形状差必须知道:
|
|
53
|
+
|
|
54
|
+
- **同步**:一次调用既投递又答复 ⇒ `DELIVERED` 与 `STARTED` 之间没有可观测边界。
|
|
55
|
+
桥把这一腿记成一次迁移并在跟踪里写 `collapsed_delivery_start=true`——**不假装看见了起点**。
|
|
56
|
+
- **SDK 入口变量必须剥掉**(`QODER_AGENT_SDK_ENTRYPOINT` 那一族):被无头拉起的子 CLI
|
|
57
|
+
一旦以为自己还在 SDK 里,会要求 stream-json 并直接拒启,表现是"叫起来就失败",
|
|
58
|
+
很容易被误读成对侧不肯回话。
|
|
59
|
+
|
|
60
|
+
**验到哪一枚**:代码里 `q2c/adapters/qoder.py` 写的是 `BIN = "qoderclicn"`——真跑过的那条腿
|
|
61
|
+
是 **Qoder CN** 的公开 CLI。Qoder **International 没跑过 ⇒ 记 `NOT_VERIFIED`,不在支持声明里**;
|
|
62
|
+
上面那条命令对它不构成任何保证。要把这条腿换到另一支,得先逐条核这四件:CLI 名、
|
|
63
|
+
参数形状(`-p -r -w --permission-mode --output-format`)、`QODER_AGENT_SDK_*` 剥离、
|
|
64
|
+
`{"type":"result", …}` 单帧收口;核完还得在真环境里跑一次双向交接拿到 ACKED 才算验过。
|
|
65
|
+
这一档与 README「验到哪一枚 CLI」那屏同源,判据在 `tests/test_vendor_claims.py`。
|
|
66
|
+
|
|
67
|
+
## 3. 逻辑会话与 provider 号(§7)
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
q2c_session_id(稳定) ──当前绑定──► provider_session_id(会变)
|
|
71
|
+
└── bindings[]: {provider, since, until, reason}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- provider 重启、换号 ⇒ 只追加绑定,**逻辑号不变**,历史交接身份不变;
|
|
75
|
+
- 重放旧消息按时间轴取当时那一枚(`provider_at()`),不永远用最新值;
|
|
76
|
+
因此时刻必须到微秒——秒级会在同一秒内的换绑上取错对象(有判据);
|
|
77
|
+
- 换绑必须写 `reason`;不同用途不共用一条会话(真机上共用线程那次,
|
|
78
|
+
答的是上一笔的 SHA:字段全过、证据自相矛盾)。
|
|
79
|
+
|
|
80
|
+
## 4. 送达判定(`q2c/ack.py`,PROTOCOL.md §4.2)
|
|
81
|
+
|
|
82
|
+
六闸:`rc=0` → 有可解析结构化记录 → 对侧自报成功(`is_error is False` 且 `subtype=="success"`)
|
|
83
|
+
→ 会话身份相等 → 独立整行 `ACKED` → 逐字整行回出 `Q2C-BIND: <本轮串>`。
|
|
84
|
+
真 CLI 还要过**终态闸**(事件流须以 `turn.completed` 之类合格终态收尾)。
|
|
85
|
+
|
|
86
|
+
**收答复那一腿只要求归因与可信收口,不要求 `ACKED`**:`ACKED` 属于结果腿
|
|
87
|
+
(发起方确认自己收到了回复)。这条口径是收敛出来的,不是宽松化——
|
|
88
|
+
旧内核把两腿混在一套词表里,正是"审核说 CHANGES_REQUESTED、验收复用同一句被判成通过"的来源。
|
|
89
|
+
|
|
90
|
+
### 4.1 终态证据有两套词表(2026-10-04 真腿第三跑定下的)
|
|
91
|
+
|
|
92
|
+
`terminal_evidence=True` 的对侧必须给出本轮终态,但"终态长什么样"两家长得不一样:
|
|
93
|
+
|
|
94
|
+
| 对侧 | 收口形态 | 读法 | 认什么 |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| Codex | 一条事件流 | `ack.scan_terminal(raw)` | `turn.completed` 为成功;`turn.failed`/`error` 为失败 |
|
|
97
|
+
| Qoder | 最后一条记录 | `ack.scan_result_frame_terminal(raw)` | `{"type":"result", is_error:false, subtype:"success"}` 为成功;`is_error:true` 或 `subtype` 以 `error` 起头为失败;缺该帧为 absent;有帧但读不出为 unknown |
|
|
98
|
+
|
|
99
|
+
四档读数互不折衷,`absent`/`unknown`/`failed` 都**不采信**为送达。两套词表不许并成一套
|
|
100
|
+
(并起来后任何开场白里出现 `result` 字样都会被当成本轮跑完)。三格反例都在
|
|
101
|
+
`tests/test_qoder_terminal_real_fixture.py`:正向采信(真件 stdout 复放)、
|
|
102
|
+
缺帧为 absent、串词表不许假绿(test_08/test_09)。
|
|
103
|
+
|
|
104
|
+
两处已修的真缺陷记在这里,防"顺手改回去":
|
|
105
|
+
|
|
106
|
+
1. `ack.receipt_from_cli_json(..., stream_terminal=…)` 这个参数曾被收下却**没写进回执**,
|
|
107
|
+
于是走这条参考实现又声明终态证据的腿永远读成"未提供";摘掉这一行的变异
|
|
108
|
+
(`ack-terminal-arg-ignored`)必须让判据变红。
|
|
109
|
+
2. Qoder 腿曾经直接用 Codex 的事件词表读自己的收口帧 ⇒ 每次真投递都被终态闸拒;
|
|
110
|
+
对应的摘词表变异(`qoder-read-with-codex-vocabulary`)同族。
|
|
111
|
+
|
|
112
|
+
### 4.2 真跑必须各用一条新会话(同一条流会"回上一轮")
|
|
113
|
+
|
|
114
|
+
复用同一条 Codex 线程时,上一跑留在队列里的那条消息会先被回答:本轮 `turn.completed`
|
|
115
|
+
确实在、会话号也对,但正文回的是**上一轮**的 `req-…` 与 `Q2C-BIND: …`。
|
|
116
|
+
归属那一刀拒得对(原件 `evidence/real-legs/05-codex-stale-bind-stream.jsonl`),
|
|
117
|
+
但真跑不该常态性撞它。所以 `tools/real-handoff.py` **默认每次新建会话**
|
|
118
|
+
(四个角色各一条:两枚线程、两枚会话),建会话那一次调用在报告里明确标
|
|
119
|
+
`Setup 调用:只为新建会话,不构成交接(无信封、无绑定串)`;
|
|
120
|
+
只有显式 `--reuse` 才允许拿旧号跑,且报告里会写 `reused=true`。
|
|
121
|
+
|
|
122
|
+
## 5. 写一个新适配器
|
|
123
|
+
|
|
124
|
+
1. 继承 `Adapter`,实现七枚方法;
|
|
125
|
+
2. `capabilities()` 如实报:`live`/`terminal_evidence`/`split_send_receive`/
|
|
126
|
+
`supports_cancel`/`evidence`/`limits`——取不到的证据位**不许报"支持"**;
|
|
127
|
+
3. 把对侧回执折成 `Receipt`(用 `ack.receipt_from_cli_json` 或等价解析),
|
|
128
|
+
**不要在适配器里判送达**;
|
|
129
|
+
4. 落盘件命名走 `adapters/_spawn.py`(含 `attempt`,重投不复用旧回执);
|
|
130
|
+
5. 加一格判据:能力位 honesty(参照 `test_11_capability_flags_are_honest`)
|
|
131
|
+
与 contract 完整性(`test_12_contract_methods_all_present`)。
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# 架构
|
|
2
|
+
|
|
3
|
+
## 1. 分层与依赖方向(单向,箭头指向"被依赖")
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
┌──────────────────────────────┐
|
|
7
|
+
用户 / CI ────►│ cli.py (11 个子命令) │
|
|
8
|
+
└───────────────┬──────────────┘
|
|
9
|
+
│
|
|
10
|
+
┌────────────────────────┼─────────────────────────┐
|
|
11
|
+
▼ ▼ ▼
|
|
12
|
+
transport.py sessions.py trace.py
|
|
13
|
+
(收单/投递/重投/ (逻辑会话↔ (只追加事件流
|
|
14
|
+
到期/取消/恢复) provider 绑定) +九问读数)
|
|
15
|
+
│ │ ▲
|
|
16
|
+
├──────────► ack.py ◄────┤ │
|
|
17
|
+
│ (六闸+终态闸:判据在核心) │
|
|
18
|
+
├──────────► ledger.py ◄─┘(台账:flock+原子写+三方合并)
|
|
19
|
+
│
|
|
20
|
+
├──────────► artifacts.py(typed ref+摘要+快照清单)
|
|
21
|
+
│
|
|
22
|
+
└──────────► adapters/base.py(契约七枚+注册表,未知名字硬拒)
|
|
23
|
+
│
|
|
24
|
+
┌───────────┼───────────┬─────────────┐
|
|
25
|
+
▼ ▼ ▼ ▼
|
|
26
|
+
codex.py qoder.py loopback.py inproc.py
|
|
27
|
+
│ │ │ │
|
|
28
|
+
└───────────┴─────┬─────┴─────────────┘
|
|
29
|
+
▼
|
|
30
|
+
security.py + _spawn.py
|
|
31
|
+
(凭据六条/路径/进程组/脱敏/原子落盘/退出码侧件)
|
|
32
|
+
▲
|
|
33
|
+
protocol.py(信封/枚举/状态机)
|
|
34
|
+
▲
|
|
35
|
+
config.py(默认+env 覆盖;拒绝持有凭据)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**不许反向依赖**:`security`/`protocol` 不认识 transport;`adapters/base` 不认识 transport;
|
|
39
|
+
`trace` 不认识适配器。核心永远不知道 provider 的字段名——那是适配器的本分。
|
|
40
|
+
|
|
41
|
+
## 2. 一条消息的调用图(`q2c send`)
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
cli.main
|
|
45
|
+
└ transport.send
|
|
46
|
+
├ transport.create
|
|
47
|
+
│ └ _create_one
|
|
48
|
+
│ ├ _check_workspace → security.check_ref_path / workspace_allowed
|
|
49
|
+
│ ├ 幂等扫描(同 idempotency_key ⇒ duplicate_suppressed+留痕)
|
|
50
|
+
│ ├ security.contains_secret_shape(只出标记,不改正文)
|
|
51
|
+
│ ├ ledger.load_for_write / ledger.save(flock+合并+原子写)
|
|
52
|
+
│ └ trace.emit ×2(created / queued)
|
|
53
|
+
└ 有界推进(max_steps=8,到界就返回,不自轮询)
|
|
54
|
+
└ transport.pump → _advance
|
|
55
|
+
├ _deliver_request
|
|
56
|
+
│ ├ _bind → sessions.get / adapters.build / adapter.resume|start
|
|
57
|
+
│ ├ state → DELIVERING
|
|
58
|
+
│ ├ adapter.check_credential(六条;不过 ⇒ 一次 CLI 都不叫)
|
|
59
|
+
│ ├ adapter.send(派发文逐字落盘+投递)
|
|
60
|
+
│ └ state → DELIVERED(可分腿)| STARTED(同步腿,记 collapsed_delivery_start)
|
|
61
|
+
├ _await_or_respond
|
|
62
|
+
│ ├ adapter.receive → _spawn.spawn(进程组/退出码侧件/到点不杀)
|
|
63
|
+
│ ├ ack.judge_ack(require_terminal=能力位, require_ack_line=False)
|
|
64
|
+
│ ├ ack.judge_result_shape(非空正文,不判长度)
|
|
65
|
+
│ └ _register_result(结果原件+SHA256)→ RESPONDED
|
|
66
|
+
└ _deliver_result
|
|
67
|
+
├ _render_result(要求逐字回出 Q2C-BIND 与 ACKED)
|
|
68
|
+
├ adapter.send / adapter.receive(结果腿)
|
|
69
|
+
└ ack.judge_ack(require_ack_line=True) → ACKED
|
|
70
|
+
└ 失败:_fail → DELIVERY_RETRY(预算内)| DELIVERY_FAILED(停放+失败通知)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 3. 状态机(`q2c/protocol.py::_TRANSITIONS`)
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
CREATED ─► QUEUED ─► DELIVERING ─► DELIVERED ─► STARTED ─► RESPONDED ─► ACKED ✚
|
|
77
|
+
│ │ │ │
|
|
78
|
+
└──────────────┴────────────┴───────────┴─► DELIVERY_RETRY ─► DELIVERING
|
|
79
|
+
│ (同一 message_id/绑定串/派发文)
|
|
80
|
+
└──► DELIVERY_FAILED(停放态)──► DELIVERY_RETRY | ACKED | EXPIRED | CANCELLED
|
|
81
|
+
任意非终态 ─► EXPIRED(到 expires_at) | CANCELLED(发送方取消投递)
|
|
82
|
+
终态:ACKED | EXPIRED | CANCELLED(之后任何事件只进跟踪,记 STALE_EVENT)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**`DELIVERY_FAILED` 是停放态而不是终态**:桥自己绝不从这里自动再试,
|
|
86
|
+
但"人显式 `retry-delivery`"与"迟到答复被收回"两条合法路可以推走它。
|
|
87
|
+
写成终态的实际后果是逼人伪造新 `request_id` 去绕开规则。
|
|
88
|
+
|
|
89
|
+
## 4. 落盘布局(`Q2C_HOME`,一个目录装全部,删掉即清除)
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
config.json 0600 配置(拒绝持有凭据)
|
|
93
|
+
state/ledger.json 0600 台账 {rev, records{}, written_at, writer_pid}
|
|
94
|
+
state/ledger.lock flock 的把手(整轮临界区)
|
|
95
|
+
state/sessions.json 0600 逻辑会话与绑定历史
|
|
96
|
+
state/inflight/<provider>/ 0600 每腿落盘件:<message_id>.a<attempt>.out(+.rc)、*.sent.txt
|
|
97
|
+
state/notify-ack.jsonl 0600 逐笔"送到人没有"的回执账
|
|
98
|
+
trace/events.jsonl 0600 只追加事件流(写前脱敏)
|
|
99
|
+
results/<request_id>.result.txt 0600 回复原文(引用+SHA)
|
|
100
|
+
mailbox/ inproc-box/ 假对侧信箱(loopback/inproc)
|
|
101
|
+
packages/ 证据快照(artifacts.export_package)
|
|
102
|
+
notify/<request_id>.txt 0600 失败通知原件
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 5. 为什么这些件在核心、那些在适配器
|
|
106
|
+
|
|
107
|
+
判据只有一条:**换一家 provider 时会不会变。**
|
|
108
|
+
"退 0 不算送达""绑定串要逐字整行""重投不换消息号""未知就停手"——换谁都不变 ⇒ 核心。
|
|
109
|
+
"`subtype=="success"`""`turn.completed`""`-r <会话号>`""要不要剥 SDK 变量"——换一家就变 ⇒ 适配器。
|
|
110
|
+
|
|
111
|
+
旧实现(fix02 交付根)里这两层是搅在一起的:同一段代码既读 provider 字段又判送达,
|
|
112
|
+
于是加一个对侧就得改核心,改核心就得重跑全部回归。
|
|
113
|
+
收敛结果与逐点分类见 `Q2C-BOUNDARY-AUDIT.md`;剥出去的五件事
|
|
114
|
+
(定级/批准/放行/串单/常驻循环)的原件与证据一个字节没动。
|
|
115
|
+
|
|
116
|
+
## 6. 规模与形状(现读,别抄这里的数字)
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
python3 tools/report-readings.py --json # 判据数、耗时、审计分档、HEAD
|
|
120
|
+
grep -c "^def \|^class " q2c/*.py q2c/adapters/*.py
|
|
121
|
+
```
|
q2c-0.1.1/CHANGELOG.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# 更新记录
|
|
2
|
+
|
|
3
|
+
格式遵循 Keep a Changelog;版本号遵循 SemVer。
|
|
4
|
+
**注意**:`protocol_version`(现在是 `q2c/1`)与包版本是两个独立编号——
|
|
5
|
+
加状态或改字段语义必须升协议主版本,见 `PROTOCOL.md` §9。
|
|
6
|
+
|
|
7
|
+
## 0.1.1 — 2026-10-04(包分发与厂商边界;协议仍是 `q2c/1`,产品逻辑没动)
|
|
8
|
+
|
|
9
|
+
**为什么升 `0.1.1` 而不是把 `0.1.0` 重发一次**(主理人 2026-10-04 裁定选 (a)):
|
|
10
|
+
tag `v0.1.0` 与它的 GitHub Release 附件**永久冻结**,PyPI 上的 `0.1.0` 不许指向另一套字节。
|
|
11
|
+
这一轮改的是「发得出去、装得上、边界说清」——按 SemVer 走补丁号。
|
|
12
|
+
任何人可自己核的那条:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
git diff --name-only v0.1.0..<v0.1.1 冻结点> -- q2c/ # 只应出现 _version.py 与 __init__.py 那一行 import
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 新增
|
|
19
|
+
|
|
20
|
+
- 包分发路径打通:`pip`/`pipx` 装得上、`q2c --help` 跑得起来、装完 Quick Start 到 `ACKED`。
|
|
21
|
+
- 版本唯一真源 `q2c/_version.py`(原先 `pyproject.toml` 与 `q2c/__init__.py` 各写一遍=双真源);
|
|
22
|
+
- `MANIFEST.in`:源码包必须带上判据树要读的那几件。**此前发出去的 sdist 带着判据却没有
|
|
23
|
+
`tests/__init__.py`**,别人解包后 `unittest discover` 当场 ImportError——
|
|
24
|
+
等于发了一套「声称可复跑其实跑不了」的判据;
|
|
25
|
+
- `tools/pkg-install-test.sh`:造件→`twine check`→全新 venv 分别装 wheel 与 sdist→CLI 入口→
|
|
26
|
+
**逐字执行 README 那一节**→用发出去的那份代码跑整批判据→pipx→校仓里不落构建产物;
|
|
27
|
+
退出码三档(0 合格/1 不合格/2 测不了)。CI 的 `package` job 只叫这一条命令,
|
|
28
|
+
本机与 CI 不存在两套口径;
|
|
29
|
+
- `tools/pypi-name-check.py`:PyPI 名字占用核查(只读、三档、取不到退 2,不折成「可用」);
|
|
30
|
+
- `tests/test_packaging.py` 先红后绿;`tools/teeth.py` 同批补变异盯这些新格。
|
|
31
|
+
- 厂商边界写进文档与 PyPI 页面:`README.md` 新增「验到哪一枚 CLI」一屏,四行机器可读口径
|
|
32
|
+
`QODER_CN_VERIFIED=YES`/`QODER_INTERNATIONAL_VERIFIED=NOT_VERIFIED`/`CODEX_VERIFIED=YES`/
|
|
33
|
+
`CORE_PROTOCOL_VENDOR_NEUTRAL=YES` + `CN_SPECIFIC_DEPENDENCIES` 逐条表;`ADAPTERS.md` §2 同步;
|
|
34
|
+
判据 `tests/test_vendor_claims.py` 钉住(含反向钉:谁把 International 写成已验证就红)。
|
|
35
|
+
|
|
36
|
+
### 变更
|
|
37
|
+
|
|
38
|
+
- 版本号改动态取值:`[tool.setuptools.dynamic] version = {attr = "q2c._version.__version__"}`。
|
|
39
|
+
- `license` 由表写法换 SPDX 串(构建告警点名 2027-02-18 起旧写法不再支持),构建下限提到 `setuptools>=77`。
|
|
40
|
+
- `[project.urls]` 那行 `Protocol = "PROTOCOL.md"`(坏链)换成真 URL,并补 Homepage/Repository/
|
|
41
|
+
Issues/Changelog/Security。
|
|
42
|
+
- 造件时间戳钉到提交时刻(`SOURCE_DATE_EPOCH`)⇒ 同一枚提交两次造出的 wheel 逐字节相同;
|
|
43
|
+
sdist 仍随造件时刻变(tar 顶层目录与 `egg-info/*` 的 mtime+gzip 头三处),身份按内容对。
|
|
44
|
+
这条取舍写在判据 docstring 里,不靠默契。
|
|
45
|
+
- 工装里写死的 `v0.1.0` 字面量全部改成跟版本走:清单文件名 `SHA256SUMS-v<版本>.txt`、
|
|
46
|
+
发布包名 `q2c-v<版本>-source.tar.gz`、发布报告按**形状**排除(`Q2C-v*-RELEASE-REPORT.md` 一律不进包)。
|
|
47
|
+
判据 `tests/test_release_manifest.py::TestVersionAwareNames` 钉住,另有「根级每枚非报告 md
|
|
48
|
+
必须被声明收录」的反向钉。
|
|
49
|
+
|
|
50
|
+
### 未闭合(不粉饰)
|
|
51
|
+
|
|
52
|
+
- PyPI 上传**没做**:不建账号、不要令牌、不碰公开不可逆动作。所以 `pipx install q2c` 是将来式;
|
|
53
|
+
今天能照抄的是 `pipx install git+https://github.com/6612907-netizen/q2c.git@<tag>`。
|
|
54
|
+
- 名字 `q2c` 现读「未被占用」(两个公开入口都 404),但**注册会不会被拒没证也证不了**。
|
|
55
|
+
- 真实双向交接那两格仍需 `Q2C_LIVE=1` 显式授权才跑;本轮一枚模型调用都没烧。
|
|
56
|
+
- Qoder International 那一支**没跑过**,因此不写「支持」。
|
|
57
|
+
|
|
58
|
+
## 0.1.0 — 2026-10-04(已公开发布:tag `v0.1.0`,见 Q2C-v0.1.0-RELEASE-REPORT.md)
|
|
59
|
+
|
|
60
|
+
### 新增
|
|
61
|
+
|
|
62
|
+
- 协议 `q2c/1`:17 项信封字段、6 类消息、11 枚传输态、8 枚失败归类、9 类产物引用;
|
|
63
|
+
显式排除 `TASK_COMPLETED`/`READY_TO_RELEASE` 等项目真相取值。
|
|
64
|
+
- 传输层:有界重投(只重投消息)、幂等抑制、到期、取消、重启恢复阶梯(未知≠没跑)。
|
|
65
|
+
- 送达确认六闸+终态闸,对所有适配器同一套尺;`ACKED` 定义为"回了话且可归因"。
|
|
66
|
+
- 逻辑会话与 provider 绑定表(换绑留档、要理由、按时间轴取当时那一枚)。
|
|
67
|
+
- 产物引用:引用+摘要、快照清单指纹、四态核验(`OK/MISSING/DIVERGED/UNVERIFIABLE`)。
|
|
68
|
+
- 只追加跟踪,九问读数;写前脱敏;事件流拒绝项目真相字段名。
|
|
69
|
+
- 安全层:凭据六条、路径与工作区白名单、进程组清理、退出码侧件、状态件 0600、fail-closed 总则。
|
|
70
|
+
- 适配器:`codex`/`qoder`(公开 CLI 档)+ `loopback`(真子进程判据与 Quick Start)
|
|
71
|
+
+ `inproc`(负载判据专用,能力位标 LOAD-ONLY)。
|
|
72
|
+
- CLI 11 子命令,退出码 `0/1/2/3` 为契约;只读子命令零写入。
|
|
73
|
+
- 判据全部走 unittest(无第三方依赖);**格数不在这里抄**——由 `tools/report-readings.py`
|
|
74
|
+
现读进发布报告(在册教训:手打的计数一定会漂成假话)。含 1000 枚合成交接:
|
|
75
|
+
0 静默丢失、0 意外重复效果。
|
|
76
|
+
- 文档:`README`/`SECURITY`/`PROTOCOL`/`ADAPTERS`/`ARCHITECTURE`/`CONTRIBUTING`
|
|
77
|
+
/`Q2C-PRODUCT-SPEC-v0.1`/`Q2C-BOUNDARY-AUDIT`(232 行逐点分类)。
|
|
78
|
+
|
|
79
|
+
### 变更(相对旧实现 `q2c-wakeup-fix02`)
|
|
80
|
+
|
|
81
|
+
- 状态词表:`COMPLETED` ⇒ `RESPONDED`/`ACKED`;`BLOCKED` ⇒ `DELIVERY_FAILED`(停放态)。
|
|
82
|
+
- `MIN_BODY=60` 的字数下限删除,改为"非空正文"形状判据(长度是质量打分,非空是防空口令)。
|
|
83
|
+
- 尾巴预算(驱动等收尾的墙钟)并入协议 `expires_at`/`EXPIRED`,不再作为独立预算存在。
|
|
84
|
+
- 叫人链保留机制(逐笔送达回执),剥离"待办/签字/审批"词汇。
|
|
85
|
+
- 不再依赖 Codex 私有 SQLite 队列库与 `~/.codex/sessions` 内部 JSONL 形状;
|
|
86
|
+
对应恢复证据位降级为 `UNVERIFIABLE ⇒ 停手等人`(代价写进 KNOWN_LIMITATIONS)。
|
|
87
|
+
|
|
88
|
+
### 移出产品路径(原件与证据一律未动)
|
|
89
|
+
|
|
90
|
+
验收定级、评审批准权、放行闸门(含豁免通道)、缺口判词、自动串单、常驻轮询循环、
|
|
91
|
+
一键清台账。逐条坐标与"移除会丢哪条保护"见 `Q2C-BOUNDARY-AUDIT.md` §C-2。
|
|
92
|
+
|
|
93
|
+
### 修复(冻结前四跑真腿+两枚独立 CI 复验抓出来的,逐条都有反例格)
|
|
94
|
+
|
|
95
|
+
- `ack.receipt_from_cli_json()` 的 `stream_terminal` 参数被收下却**没写进回执**:
|
|
96
|
+
凡声明 `terminal_evidence=True` 又走这条参考实现的适配器,终态恒读成"未提供",
|
|
97
|
+
真投递永远被终态闸拒。现在参数真的接进回执,`None`(不提供)与 `absent`(提供了但没终态)
|
|
98
|
+
保持两档不并。
|
|
99
|
+
- Qoder 腿用 **Codex 的事件词表**读自己的单帧收口 ⇒ 合格答复被拒。新增
|
|
100
|
+
`ack.scan_result_frame_terminal()`(成功/失败/absent/unknown 四档),
|
|
101
|
+
两套词表各自独立且互不采信(`ADAPTERS.md` §4.1)。
|
|
102
|
+
- 在飞子进程被判失败 ⇒ 下一拍重新入队并再次 `exec resume`,**同一请求被叫醒两次**
|
|
103
|
+
(真腿实测)。现在先看进程存活,再决定是失败还是在飞。
|
|
104
|
+
- 结果正文"除协议两行之外还有没有内容"从闸门降回**观测**(`result_body_only_protocol`),
|
|
105
|
+
桥不判答复质量;两条读数仍在。
|
|
106
|
+
- 发布门脚本的 shell 方言两处:`&&` 起行(macOS 与 ubuntu 的 `sh` 都在解析期拒)、
|
|
107
|
+
`$VAR` 紧跟全角标点被并进变量名(macOS 托管 runner 报未绑变量,脚本在末行状态前就死)。
|
|
108
|
+
本机 shell 看不见第二处 ⇒ 新增静态判据 `tests/test_shell_portability.py`(含 `sh -n`/`dash -n`
|
|
109
|
+
+变量紧邻非 ASCII+行首逻辑符+把门真跑一遍)。
|
|
110
|
+
- `install.pip` 那一档以前要求机器预装 setuptools 且 PATH 上有 `pip`,
|
|
111
|
+
把"能不能试"当成了前提,结果 macOS 托管 runner 明明装得上却被报 `UNVERIFIED`。
|
|
112
|
+
现在走 venv 自带的 pip;装不上才如实报,且 `FAIL`(装上了跑不起来)判红不豁免。
|
|
113
|
+
- CI 复验作业里 `python3 -m unittest … | tee` 会把退出码换成 `tee` 的 0 ⇒ 判据红了作业照绿。
|
|
114
|
+
现在退出码单独接、单独判,并加 `OK` 行核;`git status` 那格在非 git 环境报 `NO_GIT`,
|
|
115
|
+
不折成"改动数=0"。
|
|
116
|
+
- 真跑跑批件 `tools/real-handoff.py`:派发文里删掉手抄的第二份绑定行模板(占位符没填就发出去,
|
|
117
|
+
等于同一封信给对侧两条互相矛盾的指令);默认**每次新建会话**,避免对侧回上一轮那枚串
|
|
118
|
+
(`ADAPTERS.md` §4.2);每次跑完把原件逐枚登记 SHA256,判据从盘上重算比对。
|
|
119
|
+
- 跟踪九问两处**读数撒谎**(真腿第四跑抓出):`state_now` 只认 `ev=="state"` 而产品从不这么发事件名,
|
|
120
|
+
于是每条真记录都被报成 `UNVERIFIABLE`(时间线上明明看得见收口);`which_retries` 只认人手
|
|
121
|
+
`retry-delivery` 写的 `retry_of`,结果腿的补送达被漏读。现按 `to_state` 取末态、两条重投来路都可见,
|
|
122
|
+
并钉住反向(没有的证据不许读出来;同步形态的 `when_delivered` 仍须 `NOT_OBSERVED`)。
|
|
123
|
+
- 文档与注释里五处指向**从没落地过**的判据件名(`test_correlation.py` 那一族)与一枚不存在的
|
|
124
|
+
模块名 `notify.py` ⇒ 新增 `tests/test_doc_references.py` 逐个查存在性;审计表补
|
|
125
|
+
"计划名 → 实际在册件"的落点对照(`Q2C-BOUNDARY-AUDIT.md`)。
|
|
126
|
+
- 真跑报告新增 `--from-run` 复分模式:跑批件自己改字段时从已在盘的原件重建报告,
|
|
127
|
+
**零新调用、不改任何原件**(前后逐枚哈希同校,已复算通过)。
|
|
128
|
+
- 真跑证据的身份改为**声明**而不是猜:新增 `evidence/real-legs/LATEST.json` 指针件
|
|
129
|
+
(记 run_id/报告路径/报告自身的 SHA256/判决),判据只读指针指的那一枚。
|
|
130
|
+
旧规则"按 mtime 挑最新"在 `git archive` 解包件里所有 mtime 相同 ⇒ 两枚 runner 各挑到
|
|
131
|
+
不同的旧失败报告,同一份代码报出两种 `REAL_HANDOFF_EVIDENCE`。
|
|
132
|
+
同时钉死一条:有报告却没指针 ⇒ **报红**,不许静默 skip(摘掉写指针那一步不该让整组证据判据消失)。
|
|
133
|
+
- 清单工具那一处同族缺陷(`exclude_prefix` 参数写下没用)另见上;新增两页清单与
|
|
134
|
+
`tests/test_release_manifest.py`(解包件里没有 `.git` ⇒ 如实 skip 并写清测不到)。
|
|
135
|
+
- 发布包与清单的分工定死(并由判据钉住):包里只装代码/文档/工装/判据;
|
|
136
|
+
`evidence/` 与本报告自身**不随包走**。理由不是省事——报告里最后一行是发布结论,
|
|
137
|
+
若报告算发布物,则"写下结论"这一步会改动发布物字节,结论要么先写(预言)要么后写(包不符)。
|
|
138
|
+
代价写明白:包里读不到本次结论,读者去仓里或交付副本读。
|
|
139
|
+
- 清单校验的对象改成**清单头记的那枚提交**(不再是分支尖):冻结后往 `evidence/` 存新原件
|
|
140
|
+
不会让清单变红;动了发布域文件则报 `DRIFTED` 并非零退出。旧规则下每存一份证据就得重打一张
|
|
141
|
+
清单,而重打的那张又让下次校验变 STALE——那是个死循环。
|
|
142
|
+
- 新增判据 `test_06`(包的件集=发布域清单项,逐一对齐)/`test_07`(临时小仓里验"加证据不变红、
|
|
143
|
+
改发布物必变红")/`test_08`(报告不在包里、但在证据域清单里);`tools/ci-clean-machine.sh`
|
|
144
|
+
的打包 pathspec 与之同源。
|
|
145
|
+
- 补一枚**本机复演**件 `tools/pkg-rehearsal.sh`:从提交打包 → 解到全新目录 → 跑整包判据 →
|
|
146
|
+
跑清洁机门。独立 CI 连红三轮,后两轮的拒因(按 mtime 挑报告、包里没证据件时某格没自己的门)
|
|
147
|
+
只在那种"没有 `.git`、mtime 齐平、没有 `evidence/`"的树上现形,开发机跑整包看不见。
|
|
148
|
+
这格现在先在本机烧,不再拿托管 runner 当本地测不出来的补锅。
|
|
149
|
+
独立性判定同时收严:要 `RUNNER_ENVIRONMENT=github-hosted` **且** `GITHUB_RUN_ID` 才算托管,
|
|
150
|
+
复演走显式 `REHEARSAL=1` 并明写 `ENVIRONMENT=REHEARSAL_ON_DEV_MACHINE`,不伪造变量。
|
|
151
|
+
- 复演第一跑就抓到一处:`tests/test_real_handoff_evidence.py` 的 test_14 属于没带门的那个类,
|
|
152
|
+
包里没有报告时 `open("")` 抛 FileNotFoundError ⇒ 独立环境整包红。该格现在有自己的门。
|
|
153
|
+
- 新增 `tools/ci-readings.py`:从 CI 原始日志 grep 出那四件要求(环境身份/包 SHA256/
|
|
154
|
+
完整日志/末行状态)折成一页读数,取不到写 `MISSING`;发布报告里的独立复验数字全部出自它,
|
|
155
|
+
不手打。
|
|
156
|
+
- 公开前脱敏:发布域 71 个文件里扫出本机绝对路径、外置卷标与另一个私人项目的目录名
|
|
157
|
+
(`/Users/<用户名>/…`、`<外部卷>/…`)——这些对读者零价值,只是把这台机器的坐标公开。
|
|
158
|
+
已全部换成 `<本地构建根>`/`<外部卷>` 形态占位,证据域不动(那里本机路径是事实的一部分,
|
|
159
|
+
且它不进包、不进公开仓)。并补一格静态闸 `tests/test_release_domain_local_paths.py`:
|
|
160
|
+
发布域再出现这类坐标=判据红("路径外泄没有测试会红"这条老坑,这次提前堵)。
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# 怎么改 q2c
|
|
2
|
+
|
|
3
|
+
## 先读三份文件
|
|
4
|
+
|
|
5
|
+
1. `Q2C-PRODUCT-SPEC-v0.1.md` —— 定位与十条"不许放回核心"的能力;
|
|
6
|
+
2. `PROTOCOL.md` —— 字段/枚举/状态机是**行为契约**,不是装饰;
|
|
7
|
+
3. `Q2C-BOUNDARY-AUDIT.md` —— 你要加的东西是不是已经被判定为项目真相。
|
|
8
|
+
|
|
9
|
+
## 本地跑什么
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
python3 -W error::ResourceWarning -m unittest discover -s tests -t . # 全部判据
|
|
13
|
+
sh tools/clean-machine-test.sh # 清洁机复验
|
|
14
|
+
python3 tools/report-readings.py --json # 发布读数(不许手打数字)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
真实双向交接那一组默认 `SKIP`(需要 `Q2C_LIVE=1` +已存在的线程/会话号)。
|
|
18
|
+
**别为了变绿把 SKIP 改成 pass**,也别拿零模型的绿顶替那一格。
|
|
19
|
+
|
|
20
|
+
## 六条规矩(违反者按不合格处理)
|
|
21
|
+
|
|
22
|
+
1. **不许改判据来变绿。** 测不到就写测不到(`UNVERIFIABLE`/`NOT_OBSERVED`/
|
|
23
|
+
`SKIP` 三档都在读数里)。旧内核里"改个状态名就等于自己开了闸门"这类事被抓住过不止一次。
|
|
24
|
+
2. **不许把项目真相放回核心。** 想加 `approve`/`grade`/`release` 类动词或字段,
|
|
25
|
+
先拿产品授权,再谈实现;`test_10_no_project_management_verbs_exist` 与
|
|
26
|
+
`trace.FORBIDDEN_EVENT_KEYS` 会直接红。
|
|
27
|
+
3. **重投 = 重投消息。** 任何"再叫一次对侧模型"的写法都是缺陷(协议 §5)。
|
|
28
|
+
PR 里请附上 `responder_calls()` 的前后对比,或等价证据。
|
|
29
|
+
4. **未知输入不许"挑最多的那条路"。** 新枚举值必须与 fail-closed 分支同批出现,
|
|
30
|
+
且有一格反例判据(`UNKNOWN_*` ⇒ 拒绝+零副作用+退出码 2)。
|
|
31
|
+
5. **文档与代码不许两份真源。** 改了 `protocol.py` 的名单,`PROTOCOL.md` 必须同步——
|
|
32
|
+
`tests/test_protocol_doc_sync.py` 逐字比,包括 §2 字段顺序与"有行读不出取值即红"。
|
|
33
|
+
6. **只读命令不许写盘。** `inspect/list/trace/doctor/adapters/version` 跑完,
|
|
34
|
+
被观测对象的哈希集合必须一字不变(`test_28_read_only_commands_write_nothing`)。
|
|
35
|
+
|
|
36
|
+
## 加一个适配器
|
|
37
|
+
|
|
38
|
+
照 `ADAPTERS.md` §5。要点:`capabilities()` 必须如实报限制;解析放适配器、判定放核心;
|
|
39
|
+
能力位 honesty 与契约完整性两格判据要跟着加。
|
|
40
|
+
|
|
41
|
+
## 提交前的门(退出码说话,不靠人盯输出)
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
sh tools/gate-commit.sh # 全量判据 + smoke;任何一步非零或没打出 OK/ACKED ⇒ 退 1
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
为什么单独有这条(2026-10-04 的自伤):我把一串动作写成
|
|
48
|
+
`unittest discover … | grep -E "^Ran |^OK|^FAILED" && git commit …`——`grep` 匹配到 `FAILED`
|
|
49
|
+
仍退 0,于是判据红着也照样提了,那一笔把 CI 全矩阵判红。
|
|
50
|
+
**"我看见红了"不等于"门拦住了"**:门必须是退出码。所以提交前跑这一条,别拿 grep 的返回值当门。
|
|
51
|
+
|
|
52
|
+
## 提交与 CHANGELOG
|
|
53
|
+
|
|
54
|
+
- 每个 PR 更新 `CHANGELOG.md`(`Unreleased` 段),写清**行为**变化而不是文件清单;
|
|
55
|
+
- `protocol_version` 的兼容规则见 `PROTOCOL.md` §9:加可选字段可以同版本,
|
|
56
|
+
**加状态或改语义必须升主版本**;
|
|
57
|
+
- 破坏性动作(清理类)必须显式指定目标根才允许执行——这条继承自实验根的一次真事故。
|
|
58
|
+
|
|
59
|
+
## 代码风格
|
|
60
|
+
|
|
61
|
+
纯标准库、Python ≥3.9、不引入常驻服务/网络出站。
|
|
62
|
+
含中文的字符串一律用全角引号或三引号(ASCII 双引号嵌进中文串会在解析期炸,踩过)。
|
|
63
|
+
git 路径一律 `-z`/NUL 分隔,不解析人类格式输出(`quotePath` 坑过中文路径)。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# q2c v0.1.0 交付与发布说明
|
|
2
|
+
|
|
3
|
+
更新:2026-10-04 21:0x(钟点取 `date` 现读)。这份文件放在仓里(不放交付目录独有),
|
|
4
|
+
因为交付副本是 `rsync --delete` 出来的——只存在于副本里的文件会被同步带走,这个坑我踩过一次。
|
|
5
|
+
|
|
6
|
+
## 发布状态
|
|
7
|
+
|
|
8
|
+
| 项 | 值 |
|
|
9
|
+
|---|---|
|
|
10
|
+
| 公开仓 | https://github.com/6612907-netizen/q2c (PUBLIC,匿名可读已核) |
|
|
11
|
+
| release | https://github.com/6612907-netizen/q2c/releases/tag/v0.1.0 |
|
|
12
|
+
| tag | `v0.1.0` → tag 对象 `a13f86ae59ad27e3840ea7fcb1f8df58dbd41d61` → 提交 `2213ba68561d8562d666f51b41aa9876f8b2776a` |
|
|
13
|
+
| 发布物 | `q2c-v0.1.0-source.tar.gz`,SHA256 `c37374b5868db984604391f4a165edf852cae93ea7fec81f2440b1f5b63afd4c`(71 件) |
|
|
14
|
+
| 三处同字节已核 | tag 归档=release 附件的 API digest=CI 归档验过的那一枚 |
|
|
15
|
+
| 独立复验 | 公开仓 run 37202188662(托管 macOS+Ubuntu 双 job success)+ run 37202515727(6 组合 ci 全绿) |
|
|
16
|
+
| 内容身份 | 发布物清单头记 `464e518548017a92f8b8975aa7694212b2919e4b`(两张清单见 `evidence/`) |
|
|
17
|
+
| 私有侧 | `q2c-staging`(私有,完整历史+全部证据,未删);其 Actions 已关,权威 CI 在公开仓 |
|
|
18
|
+
| 发布后一轮 | main 上另有"包分发"那几笔(wheel/sdist、pip/pipx、CI 打包门):**tag 与 Release 附件未动**,也**未上传 PyPI**;读数与身份见 `Q2C-v0.1.0-RELEASE-REPORT.md` 末节「发布后一轮」 |
|
|
19
|
+
|
|
20
|
+
## 三行自检(装了 Python 3.9+ 的任何一台机器)
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
shasum -a 256 q2c-v0.1.0-source.tar.gz # 期望 c37374b5868db984…
|
|
24
|
+
tar -xzf q2c-v0.1.0-source.tar.gz && cd q2c && python3 -m unittest discover -s tests -t .
|
|
25
|
+
sh tools/clean-machine-test.sh # 期望末行 CLEAN_MACHINE_STATE=ACKED
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
不依赖我方原件的发布物验法(在公开仓跑,tag 只在那里):
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
git clone https://github.com/6612907-netizen/q2c v && cd v
|
|
32
|
+
git archive --format=tar.gz --prefix=q2c/ v0.1.0 -- . ':(exclude)evidence' \
|
|
33
|
+
':(exclude)Q2C-v0.1.0-RELEASE-REPORT.md' | shasum -a 256
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 现在被验到什么程度
|
|
37
|
+
|
|
38
|
+
- 真实双向交接两个方向各一次,均以 `ACKED` 收口;原件在 `evidence/real-legs/run-20261004-171323/`,
|
|
39
|
+
哪一枚报告算数由 `evidence/real-legs/LATEST.json` 声明,判据每次从盘上重算那 33 枚 SHA256。
|
|
40
|
+
- 判据 269 格(unittest,零第三方依赖),独立两台主机各自 `Ran 269 tests` 全绿。
|
|
41
|
+
- 27 处"把保护摘掉"的变异全部把对应格打红(`tools/teeth.py`)。
|
|
42
|
+
- 提交前的门 `tools/gate-commit.sh`:判据非零或没打出 `OK` ⇒ 退 1(反照做过:塞一格红 ⇒ rc=1)。
|
|
43
|
+
|
|
44
|
+
## 公开仓与源码包里为什么不放证据
|
|
45
|
+
|
|
46
|
+
`evidence/` 与发布报告不进包也不进公开仓:那些原件带本机绝对路径、外置卷标、会话号与额度读数,
|
|
47
|
+
公开出去泄露的是构建用的那台机器,不是产品。这条边界由
|
|
48
|
+
`tests/test_release_domain_local_paths.py`(发布域扫本机坐标)与
|
|
49
|
+
`tests/test_release_manifest.py`(包件集=清单项、报告不在包里)钉住;
|
|
50
|
+
README 的"验证证据在哪"一节向读者解释去哪复核。
|
|
51
|
+
|
|
52
|
+
## 发布之后 main 上还会走,怎么读 CI 颜色
|
|
53
|
+
|
|
54
|
+
- `python3 tools/make-manifest.py` 今后会正常报 `DRIFTED`:它说的是"清单不再描述分支尖",
|
|
55
|
+
不是"发布物被改动"。发布物用上面 `git archive v0.1.0` 那条命令验,字节不随 main 变。
|
|
56
|
+
- 已发生的一次:私有 staging 在提交 `a14fd5c` 上全矩阵判红——我把"扫描规则要抓的字面量"
|
|
57
|
+
原样写进了报告的脱敏说明里,于是发布域扫描格抓到报告本身。修在 `7efa3e7`,两作业回到 success;
|
|
58
|
+
账与三条"不推翻发布结论"的现读证据在 `evidence/ci-staging-a14fd5c-note.md`。
|
|
59
|
+
同批把 CI 噪声源头关掉(staging Actions `enabled=false`),并把"grep 退 0 不算门"这次自伤
|
|
60
|
+
变成一条退出码门。
|
|
61
|
+
|
|
62
|
+
## 已知边界(不粉饰)
|
|
63
|
+
|
|
64
|
+
1. 真实双向交接只跑过这一批(一台机器、一对账号、一种网络状况);跨机器/跨账号/断网中途重启
|
|
65
|
+
那些形状,桩级判据覆盖的是协议行为不是环境。
|
|
66
|
+
2. Codex 腿重启后的证据比早期实现少两档(不读私有队列库与内部 JSONL):读成 `UNKNOWN` ⇒ 停手等人。
|
|
67
|
+
3. Qoder 公开 CLI 是同步的:投递与开始之间没有可观测边界,九问里那一问在该腿上永远 `NOT_OBSERVED`。
|
|
68
|
+
4. 无 Windows 支持路径;桥不拥有、不读取、不写入任何凭据(见 `SECURITY.md`)。
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# q2c v0.1.1 交付与发布说明
|
|
2
|
+
|
|
3
|
+
这一版改的是**装得上、跑得起来、边界说清**:产品逻辑与协议 `q2c/1` 一字未动
|
|
4
|
+
(自证命令在本文件最后一屏)。v0.1.0 那份交付说明仍在仓里(`DELIVERY-v0.1.0.md`),
|
|
5
|
+
它记的是上一版发布,本轮不改写。
|
|
6
|
+
|
|
7
|
+
## 怎么装
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pipx install q2c # PyPI 正式版本(0.1.1)——需主理人完成身份授权后才有
|
|
11
|
+
python3 -m pip install q2c # 等价
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
还没等到 PyPI 的时候,今天就能跑的等价命令(真跑过):
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
pipx install git+https://github.com/6612907-netizen/q2c.git@v0.1.1
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 装完第一条命令
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
q2c --help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 第一次跑通(零模型调用,不需要 Codex 也不需要 Qoder)
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
export Q2C_HOME=$(mktemp -d)
|
|
30
|
+
q2c init
|
|
31
|
+
S=$(q2c sessions create --role sender --adapter loopback --label demo-s |
|
|
32
|
+
python3 -c 'import json,sys;print(json.load(sys.stdin)["session_id"])')
|
|
33
|
+
R=$(q2c sessions create --role receiver --adapter loopback --label demo-r |
|
|
34
|
+
python3 -c 'import json,sys;print(json.load(sys.stdin)["session_id"])')
|
|
35
|
+
q2c send --from "$S" --to "$R" --payload "请把这件事接手过去:回一句话说明你收到了这一笔。"
|
|
36
|
+
q2c list
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
看到 `"state": "ACKED"` 就成功了。`--adapter loopback` 不能省(适配器名空缺一律拒,零副作用、退 2)。
|
|
40
|
+
|
|
41
|
+
## 三行自检(别人在自己机器上复跑,不依赖我方原件)
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
sh tools/pkg-install-test.sh # 造件→装件→CLI→Quick Start→用发出去的那份代码跑整批判据
|
|
45
|
+
python3 -W error::ResourceWarning -m unittest discover -s tests -t .
|
|
46
|
+
sh tools/clean-machine-test.sh
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 版本身份(这一版为什么是 0.1.1)
|
|
50
|
+
|
|
51
|
+
- `v0.1.0` 的 tag 与 GitHub Release 附件**永久冻结**;PyPI 上的 `0.1.0` 不指向本轮那套字节
|
|
52
|
+
(主理人 2026-10-04 裁定选 (a))。
|
|
53
|
+
- 协议仍是 `q2c/1`;`protocol_version` 与包版本是两个独立编号(见 `PROTOCOL.md` §9)。
|
|
54
|
+
- 自证「产品逻辑没动」:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
git diff --name-only v0.1.0 v0.1.1 -- q2c/ # 只应出现 _version.py 与 __init__.py
|
|
58
|
+
git show v0.1.0:q2c/protocol.py | shasum -a 256
|
|
59
|
+
git show v0.1.1:q2c/protocol.py | shasum -a 256 # 两行应相同
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 验到哪一枚 CLI(别把没跑过的写成支持)
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
QODER_CN_VERIFIED=YES
|
|
66
|
+
QODER_INTERNATIONAL_VERIFIED=NOT_VERIFIED
|
|
67
|
+
CODEX_VERIFIED=YES
|
|
68
|
+
CORE_PROTOCOL_VENDOR_NEUTRAL=YES
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
真跑过的那条 Qoder 腿用的是 CN 版 CLI(`qoderclicn`);International 那一支**没跑过**,
|
|
72
|
+
本文档对它不作任何保证。逐条依赖与原因见 `README.md`「验到哪一枚 CLI」与 `ADAPTERS.md` §2。
|
q2c-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 q2c contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|