diff --git a/docs/parliament.md b/docs/parliament.md index e2a4365..1e5466c 100644 --- a/docs/parliament.md +++ b/docs/parliament.md @@ -64,7 +64,7 @@ Peer 在 Realm 证书互鉴后做 **临时 X25519 密钥交换**,后续议会 bash scripts/docker_parliament_test.sh ``` -Alpine 内构建,chair↔member 握手 + 密封通道。 +Alpine 内构建,chair↔member 握手 + 密封通道同步(`sealed=1` / `peer_sync`)。 ## 相关标志 diff --git a/include/cst_version.h b/include/cst_version.h index bc65884..f814ed1 100644 --- a/include/cst_version.h +++ b/include/cst_version.h @@ -3,7 +3,7 @@ #define CST_VERSION_MAJOR 0 #define CST_VERSION_MINOR 9 -#define CST_VERSION_PATCH 17 +#define CST_VERSION_PATCH 18 #define CST_VERSION_STR_(x) #x #define CST_VERSION_STR(x) CST_VERSION_STR_(x) diff --git a/scripts/docker_parliament_test.sh b/scripts/docker_parliament_test.sh index 3bf19ae..5160621 100755 --- a/scripts/docker_parliament_test.sh +++ b/scripts/docker_parliament_test.sh @@ -43,17 +43,22 @@ done docker logs cst-parl-a 2>&1 | tail -8 echo "== member dials ==" -docker run --rm --name cst-parl-b --network "$NET" \ +dial_out="$(docker run --rm --name cst-parl-b --network "$NET" \ -v "$VOL:/realm-config" -e XDG_CONFIG_HOME=/realm-config \ - "$IMG" --peer-dial="cst-parl-a:${LISTEN_PORT}" --instance member-b + "$IMG" --peer-dial="cst-parl-a:${LISTEN_PORT}" --instance member-b)" +echo "$dial_out" +echo "$dial_out" | grep -q 'sealed=1' +echo "$dial_out" | grep -q 'peer_sync rev=' echo "== chair result ==" for i in $(seq 1 50); do - if docker logs cst-parl-a 2>&1 | grep -q "peer_ok"; then + if docker logs cst-parl-a 2>&1 | grep -q "peer_sync rev="; then break fi sleep 0.2 done -docker logs cst-parl-a 2>&1 | grep peer_ok +docker logs cst-parl-a 2>&1 | grep -E 'peer_ok|peer_sync' +docker logs cst-parl-a 2>&1 | grep -q 'sealed=1' +docker logs cst-parl-a 2>&1 | grep -q 'peer_sync rev=' echo "docker_parliament_test: OK" diff --git a/src/agent_loop.c b/src/agent_loop.c index 006507c..f1211bc 100644 --- a/src/agent_loop.c +++ b/src/agent_loop.c @@ -1030,9 +1030,13 @@ static void sandbox_escalate(turn_t *t, cst_tool_ctx_t *ctx, } if (ans == CST_PERM_DENY) { const char *note = - "\n[cst: blocked by the sandbox (writes are limited to the " - "workspace and temp dir); the user did not allow running it " - "outside]\n"; + (t->hooks && t->hooks->on_permission) + ? "\n[cst: blocked by the sandbox (writes are limited to the " + "workspace and temp dir); the user did not allow running it " + "outside]\n" + : "\n[cst: blocked by the sandbox (writes are limited to the " + "workspace and temp dir); no interactive approval channel to " + "rerun outside — use CST_APPROVAL=yolo or the TUI]\n"; cst_buf_t b = {0}; if (tr->out) cst_buf_append(&b, tr->out, tr->out_len); @@ -1284,7 +1288,13 @@ static void exec_begin(turn_t *t, pcall_t *pc, exec_t *x) { pc->status = PC_DUP; hook_status(t, "skip duplicate read"); } else if (!allow) { - set_output(pc, "denied by user", 14, 1); + const char *msg = + (t->hooks && t->hooks->on_permission) + ? "denied by user" + : "error: tool needs approval but no interactive channel is " + "available (headless/auto without YOLO). Set CST_APPROVAL=yolo " + "or CST_AUTO_APPROVE=1, or run in the TUI."; + set_output(pc, msg, strlen(msg), 1); pc->status = PC_DENIED; hook_tool(t, "denied", c, detail); } else { diff --git a/src/parliament.c b/src/parliament.c index 2f43907..327afde 100644 --- a/src/parliament.c +++ b/src/parliament.c @@ -468,5 +468,6 @@ char *cst_parliament_status_text(void) { svc_id[0] ? svc_id : "-", svc_port); else cst_buf_puts(&b, "service stopped\n"); + cst_buf_puts(&b, "peer_sync sealed (X25519 + XChaCha20-Poly1305)\n"); return cst_buf_steal(&b); } diff --git a/src/parliament_cli.c b/src/parliament_cli.c index f66603b..a3385d8 100644 --- a/src/parliament_cli.c +++ b/src/parliament_cli.c @@ -247,7 +247,27 @@ int cst_parliament_cli_peer_listen(int port, const char *instance_id) { os_net_close(ls); return 1; } - printf("peer_ok remote=%s host=%s\n", sess.remote.instance_id, sess.host); + printf("peer_ok remote=%s host=%s sealed=%d\n", sess.remote.instance_id, + sess.host, sess.sealed); + fflush(stdout); + { + cst_peer_sync_bundle_t local = {0}; + cst_parliament_meta_t m; + memset(&m, 0, sizeof(m)); + (void)cst_parliament_load(&m); + local.revision = m.revision; + snprintf(local.parliament_id, sizeof(local.parliament_id), "%s", + m.parliament_id); + snprintf(local.project_id, sizeof(local.project_id), "%s", m.project_id); + snprintf(local.chair_instance, sizeof(local.chair_instance), "%s", + m.chair_instance); + local.context = cst_parliament_context_get(); + if (cst_peer_sync(&sess, &local, 15000, err, sizeof(err)) != 0) + fprintf(stderr, "peer_sync failed: %s\n", err); + else + printf("peer_sync rev=%lu sealed=1\n", local.revision); + free(local.context); + } cst_peer_close(&sess); os_net_close(ls); return 0; @@ -274,7 +294,26 @@ int cst_parliament_cli_peer_dial(const char *host, int port, fprintf(stderr, "dial/handshake failed: %s\n", err); return 1; } - printf("peer_ok remote=%s\n", sess.remote.instance_id); + printf("peer_ok remote=%s sealed=%d\n", sess.remote.instance_id, sess.sealed); + fflush(stdout); + { + cst_peer_sync_bundle_t local = {0}; + cst_parliament_meta_t m; + memset(&m, 0, sizeof(m)); + (void)cst_parliament_load(&m); + local.revision = m.revision; + snprintf(local.parliament_id, sizeof(local.parliament_id), "%s", + m.parliament_id); + snprintf(local.project_id, sizeof(local.project_id), "%s", m.project_id); + snprintf(local.chair_instance, sizeof(local.chair_instance), "%s", + m.chair_instance); + local.context = cst_parliament_context_get(); + if (cst_peer_sync(&sess, &local, 15000, err, sizeof(err)) != 0) + fprintf(stderr, "peer_sync failed: %s\n", err); + else + printf("peer_sync rev=%lu sealed=1\n", local.revision); + free(local.context); + } cst_peer_close(&sess); return 0; } diff --git a/tests/test_parliament.c b/tests/test_parliament.c index 963e44e..7ec9d01 100644 --- a/tests/test_parliament.c +++ b/tests/test_parliament.c @@ -73,6 +73,7 @@ int main(void) { char *st = cst_parliament_status_text(); assert(st && strstr(st, "parliament")); assert(strstr(st, "service")); + assert(strstr(st, "peer_sync") && strstr(st, "sealed")); free(st); assert(cst_parliament_is_active()); assert(cst_parliament_daemon_info(NULL, NULL, 0, NULL) != 0);