Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion .github/workflows/python-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ on:
push:
paths:
- "python/**"
- "scripts/check_sdk_hardening.py"
- ".github/workflows/python-integration.yml"
pull_request:
paths:
- "python/**"
- "scripts/check_sdk_hardening.py"
- ".github/workflows/python-integration.yml"
workflow_dispatch:
inputs:
Expand All @@ -24,6 +26,9 @@ jobs:
- name: Checkout cccc-sdk
uses: actions/checkout@v4

- name: Check SDK hardening contract
run: python3 scripts/check_sdk_hardening.py

- name: Checkout cccc (daemon)
uses: actions/checkout@v4
with:
Expand Down Expand Up @@ -54,7 +59,7 @@ jobs:
python -m pip install -U pip
python -m pip install -e python

- name: Smoke (daemon + sdk)
- name: Smoke current message and Mail contract
run: |
set -euo pipefail
export CCCC_HOME="$PWD/.tmp_cccc_home"
Expand All @@ -65,3 +70,46 @@ jobs:
trap cleanup EXIT
cccc daemon start
python python/examples/compat_check.py

python - <<'PY'
from cccc_sdk import CCCCClient

client = CCCCClient()
created = client.group_create(title="python-sdk-native-current")
group_id = str(created.get("group_id") or "")
if not group_id:
raise RuntimeError("group_create did not return group_id")

try:
client.group_start(group_id=group_id)
client.actor_add(
group_id=group_id,
actor_id="sdk-live",
runtime="custom",
runner="headless",
command=["/usr/bin/true"],
)
options = {
"group_id": group_id,
"text": "python SDK Mail replay probe",
"message_mode": "mail",
"to": ["sdk-live"],
"client_id": f"python-sdk:{group_id}:mail",
}
first = client.send(**options)
replay = client.send(**options)
if (first.get("event") or {}).get("id") != (replay.get("event") or {}).get("id"):
raise RuntimeError("stable client_id created two Mail events")
if replay.get("duplicate") is not True:
raise RuntimeError("Mail replay did not expose duplicate=true")

peeked = client.inbox_peek(group_id=group_id, actor_id="sdk-live", by="sdk-live")
consumed = client.inbox_read(group_id=group_id, actor_id="sdk-live", by="sdk-live")
if len(peeked.get("messages") or []) != 1 or len(consumed.get("messages") or []) != 1:
raise RuntimeError("Mail was not visible and consumed exactly once")
empty = client.inbox_read(group_id=group_id, actor_id="sdk-live", by="sdk-live")
if empty.get("messages"):
raise RuntimeError("Mail remained unread after inbox_read")
finally:
client.group_delete(group_id=group_id)
PY
33 changes: 30 additions & 3 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ on:
push:
paths:
- "rust/**"
- "scripts/check_sdk_hardening.py"
- ".github/workflows/rust-ci.yml"
pull_request:
paths:
- "rust/**"
- "scripts/check_sdk_hardening.py"
- ".github/workflows/rust-ci.yml"

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check SDK hardening contract
run: python3 scripts/check_sdk_hardening.py
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy,rustfmt
Expand All @@ -23,17 +27,39 @@ jobs:
run: cargo fmt --check
- name: Lint
working-directory: rust
run: cargo clippy --all-targets --all-features -- -D warnings
run: cargo clippy --locked --all-targets --all-features -- -D warnings
- name: Test
working-directory: rust
run: cargo test --all-targets
run: cargo test --locked --all-targets
- name: Package
working-directory: rust
run: cargo package --locked

msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.74.0"
- name: Check declared MSRV
working-directory: rust
run: cargo check --locked

windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Test Windows client behavior
working-directory: rust
run: cargo test --locked --all-targets

integration:
runs-on: ubuntu-latest
needs: test
env:
CCCC_RUN_LIVE_RELIABILITY: "1"
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -73,4 +99,5 @@ jobs:
trap cleanup EXIT
cccc daemon start
test -f "$CCCC_HOME/daemon/ccccd.addr.json"
cargo run --manifest-path rust/Cargo.toml --example compat_check
cargo run --locked --manifest-path rust/Cargo.toml --example compat_check
cargo test --locked --manifest-path rust/Cargo.toml --test live_reliability -- --nocapture
5 changes: 5 additions & 0 deletions .github/workflows/spec-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ on:
paths:
- "spec/**"
- "scripts/check_specs_against_cccc.sh"
- "scripts/check_sdk_hardening.py"
- ".github/workflows/spec-drift.yml"
pull_request:
paths:
- "spec/**"
- "scripts/check_specs_against_cccc.sh"
- "scripts/check_sdk_hardening.py"
- ".github/workflows/spec-drift.yml"
schedule:
- cron: "17 3 * * *"
Expand All @@ -36,3 +38,6 @@ jobs:

- name: Compare mirrored standards
run: bash scripts/check_specs_against_cccc.sh cccc-core

- name: Check SDK hardening contract
run: python3 scripts/check_sdk_hardening.py
81 changes: 58 additions & 23 deletions .github/workflows/ts-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ on:
push:
paths:
- "ts/**"
- "scripts/check_sdk_hardening.py"
- ".github/workflows/ts-ci.yml"
pull_request:
paths:
- "ts/**"
- "scripts/check_sdk_hardening.py"
- ".github/workflows/ts-ci.yml"

jobs:
Expand All @@ -17,6 +19,9 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Check SDK hardening contract
run: python3 scripts/check_sdk_hardening.py

- uses: actions/setup-node@v4
with:
node-version: "20"
Expand Down Expand Up @@ -75,7 +80,7 @@ jobs:
working-directory: cccc
run: cargo build --locked -p cccc --bin cccc

- name: SDK <-> daemon integration smoke
- name: SDK against current message and Mail contract
run: |
set -euo pipefail
export CCCC_HOME="$PWD/.tmp_cccc_home"
Expand All @@ -95,14 +100,13 @@ jobs:
import fs from 'node:fs/promises';
import { CCCCClient, discoverEndpoint } from './ts/dist/index.js';

// Validate daemon TCP endpoint normalization for 0.0.0.0/localhost style descriptors.
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'cccc-sdk-ts-'));
const fakeDaemon = path.join(fakeHome, 'daemon');
await fs.mkdir(fakeDaemon, { recursive: true });
await fs.writeFile(
path.join(fakeDaemon, 'ccccd.addr.json'),
JSON.stringify({ v: 1, transport: 'tcp', host: '0.0.0.0', port: 12345 }),
'utf-8'
'utf-8',
);
const fakeEndpoint = await discoverEndpoint(fakeHome);
if (fakeEndpoint.host !== '127.0.0.1') {
Expand All @@ -120,7 +124,11 @@ jobs:
'group_preamble_set',
'group_preamble_reset',
'send',
'reply',
'send_files',
'inbox_peek',
'inbox_read',
'message_history',
'events_stream',
'memory_search',
'memory_get',
Expand All @@ -140,27 +148,54 @@ jobs:

const created = await client.groupCreate({ title: 'ts-integration' });
const groupId = String(created.group_id || '');
if (!groupId) {
throw new Error('groupCreate did not return group_id');
}

const eventPromise = (async () => {
for await (const item of client.eventsStream({ groupId, by: 'user' })) {
if (item.t === 'event') return item.event;
if (!groupId) throw new Error('groupCreate did not return group_id');

try {
await client.groupStart(groupId);
await client.actorAdd({
groupId,
actorId: 'sdk-live',
runtime: 'custom',
runner: 'headless',
command: ['/usr/bin/true'],
});

const eventPromise = (async () => {
for await (const item of client.eventsStream({ groupId, by: 'user' })) {
if (item.t === 'event') return item.event;
}
return null;
})();
await new Promise((resolve) => setTimeout(resolve, 200));
await client.send({ groupId, text: 'hello from ts-ci', mode: 'send', to: ['user'] });
const event = await Promise.race([
eventPromise,
new Promise((_, reject) => setTimeout(() => reject(new Error('stream timeout')), 5000)),
]);
if (!event || typeof event.id !== 'string' || event.id.length === 0) {
throw new Error('stream event is missing canonical id');
}
return null;
})();

await new Promise((r) => setTimeout(r, 200));
await client.send({ groupId, text: 'hello from ts-ci', mode: 'send', by: 'user', to: ['user'] });

const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('timed out waiting for stream event')), 5000)
);
const event = await Promise.race([eventPromise, timeoutPromise]);

if (!event || typeof event.id !== 'string' || event.id.length === 0) {
throw new Error('stream event is missing canonical id field');
const mailOptions = {
groupId,
text: 'TS SDK Mail replay probe',
mode: 'mail',
to: ['sdk-live'],
clientId: `ts-sdk:${groupId}:mail`,
};
const first = await client.send(mailOptions);
const replay = await client.send(mailOptions);
if (first.event?.id !== replay.event?.id || replay.duplicate !== true) {
throw new Error('stable clientId did not replay the Mail event');
}
const peeked = await client.inboxPeek({ groupId, actorId: 'sdk-live', by: 'sdk-live' });
const consumed = await client.inboxRead({ groupId, actorId: 'sdk-live', by: 'sdk-live' });
if (peeked.messages?.length !== 1 || consumed.messages?.length !== 1) {
throw new Error('Mail was not visible and consumed exactly once');
}
const empty = await client.inboxRead({ groupId, actorId: 'sdk-live', by: 'sdk-live' });
if (empty.messages?.length) throw new Error('Mail remained unread after inboxRead');
} finally {
await client.groupDelete(groupId);
}
console.log('integration smoke passed', { groupId, eventId: event.id });
JS
27 changes: 25 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ CCCC line and exposes the IPC surface available on that line.
- Reduced legacy Group Space synchronization to its contractually supported
read-only status operation. Explicit ingest and source operations remain the
mutation path.
- Reworked the Rust identity-bound adapter around explicit message modes,
stable `client_id` replay, and the daemon's atomic Mail Inbox transaction;
removed its emulation of retired ACK/read cursor operations.

### Compatibility

Expand All @@ -28,8 +31,9 @@ CCCC line and exposes the IPC surface available on that line.
implementation label alone.
- Internal Web upload preflight and relay-only operations remain available via
generic calls but intentionally have no first-class public wrapper.
- Package versions are deliberately unchanged. Version selection and publishing
remain a separate release decision.
- Python and TypeScript package versions remain unchanged. The Rust hardening
branch keeps its already-selected `0.0.2` source version; publishing remains a
separate release decision.

## [0.4.34] — Unreleased

Expand All @@ -45,6 +49,10 @@ CCCC line and exposes the IPC surface available on that line.
after an exchange has begun.
- Scheduled CI drift detection for all three mirrored CCCC standards, automatic
Python integration coverage, and a live Rust-SDK/current-daemon smoke job.
- Python and TypeScript Web Model wait/complete helpers with a required stable
`delivery_id` replay key.
- Rust 0.0.2 identity-bound reliable messaging with explicit Send / Send + Reply
/ Mail modes, stable write keys, and atomic Mail consumption.

### Changed

Expand Down Expand Up @@ -80,6 +88,10 @@ CCCC line and exposes the IPC surface available on that line.
- TypeScript's `INVALID_REQUEST` constant now matches the daemon's
`invalid_request` code and includes current request-size and Remote Access
administrator-token errors.
- TypeScript connection, response, and stream-handshake phases now share one
deadline; handshake cancellation closes the socket, buffered stream data is
byte-capped before decoding, and transport cleanup removes only SDK-owned
listeners.
- The daemon IPC mirror now matches current CCCC core, including Remote Access
administrator-token state and enforcement fields.

Expand All @@ -91,6 +103,17 @@ CCCC line and exposes the IPC surface available on that line.
maps and side-effect-free compatibility probing. TypeScript also compiles
exported option fixtures so documented contract values cannot silently drift
out of the published declaration surface.
- Added current native-daemon integration for message replay and atomic Mail
consumption, Rust 1.74 MSRV and Windows jobs, and a static SDK hardening gate.

## Rust crate [0.0.2] — Unreleased

### Added

- Least-privilege `IdentityBoundClient` adapters for explicit-mode idempotent
send/reply and daemon-owned Mail Inbox operations.
- Nullable native cursor decoding and direct use of atomic `inbox_read`, without
retaining the retired all-message mark-read compatibility model.

## Rust crate [0.0.1] — 2026-08-03

Expand Down
5 changes: 3 additions & 2 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ SDK と CCCC Web が同じ `CCCC_HOME` を参照していれば、書き込み
主な用途:
- リアルタイム更新が必要な Web/IDE プラグイン(`events_stream`)
- Working Group を監視して自動応答する bot/service
- identity-bound な冪等 write と atomic Mail 消費を必要とする高信頼 Rust worker
- group / actors / shared context / capability ポリシー / Group Space をプログラムから管理する社内ツール
- `tracked_send`、Context Ops v3 task/agent state、capability discovery、ローカル memory API を使う workflow 連携

Expand Down Expand Up @@ -90,7 +91,7 @@ python python/examples/send.py --group g_xxx --text "FYI" --mode mail

```toml
[dependencies]
cccc-sdk = "0.0.1"
cccc-sdk = "0.0.2"
```

Rust クライアントは `CCCC_HOME` の Unix Socket/TCP daemon を自動検出し、
Expand All @@ -102,7 +103,7 @@ Rust クライアントは `CCCC_HOME` の Unix Socket/TCP daemon を自動検
## バージョニングと互換性

SDK リリースは daemon のバージョン文字列ではなく contract に追従します:
- Python と TypeScript は現在の SDK リリースラインに追従し、Rust crate `0.0.1` から開始します
- Python と TypeScript は現在の SDK リリースラインに追従し、Rust crate は当面 `0.0.x` 系列です
- 実行時互換性は `assert_compatible(...)` で必要な capability/op を指定して確認します。

互換性は “契約/能力” で保証し、バージョン文字列の厳密一致には依存しません:
Expand Down
Loading
Loading