openvibers/openvibe.codes Commits
account-data: pin openvibe-sdk 0.74.0 and guard every table with a person column (#182)
Changes
@@@ -94,7 +94,7 @@ More: [docs/cli.md](docs/cli.md).- **OpenVibe.Network** — SSO (OAuth client `codes`, PKCE S256) and its JWKS.- **PostgreSQL 18 and Valkey 9** (OpenVibe.Host `roles/data/`, ADR-035): `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional).--- **openvibe-contracts v0.243.0**, **openvibe-sdk v0.73.0**, **openvibe-shared v4.48.0** (pinned tag tarballs).++- **openvibe-contracts v0.243.0**, **openvibe-sdk v0.74.0**, **openvibe-shared v4.48.0** (pinned tag tarballs).No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).@@@ -170,6 +170,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [<!-- versions:start -->- openvibe-contracts: v0.247.0--- openvibe-sdk: v0.73.0++- openvibe-sdk: v0.74.0- openvibe-shared: v4.48.0<!-- versions:end -->@@@ -8,11 +8,11 @@"port": 4900,"domain": "openvibe.codes","contracts": "openvibe-contracts v0.247.0",-- "sdk": "openvibe-sdk v0.73.0",++ "sdk": "openvibe-sdk v0.74.0","shared": "openvibe-shared v4.48.0","packages": ["openvibe-contracts v0.247.0",-- "openvibe-sdk v0.73.0",++ "openvibe-sdk v0.74.0","openvibe-shared v4.48.0"],"summary": "Public at https://openvibe.codes (sudo ovhost deploy codes; unit openvibe-codes on 127.0.0.1:4900). Since 2026-10-08 Codes is the open coding-agent harness: openvibe-codes (a command-line tool and Node.js module, v0.5.0) runs a coding task on Claude Code, Claude Code on another provider, Codex, OpenCode, Command Code, MiniMax Code, Aider, the DeepSeek API or a person's own OpenAI-compatible model on their machine, hands it to the next agent when one fails or goes quiet, and keeps every run; the site carries the agent catalog with a public routing API, getting started, Improve OpenVibe and the community documents. The developer console that used to live here moved to OpenVibe.Services (https://openvibe.services) and every one of its addresses answers a permanent redirect there.",@@@ -16,7 +16,7 @@"helmet": "^8.1.0","iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.247.0",-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.73.0",++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.74.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.48.0","pg": "^8.23.0"},@@@ -1165,9 +1165,9 @@}},"node_modules/openvibe-sdk": {-- "version": "0.73.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.73.0",-- "integrity": "sha512-XClxoVm9M6RA8eXlGgYZ0u6q9n5h79mCXqAAEg9fX21KgcqP69RmG+cX+6wrOf7TaEj7eSBWMxHjiIeIa9N7Dg==",++ "version": "0.74.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.74.0",++ "integrity": "sha512-DO9KVPnUriY/I4lX6ZEJtXaUvRDbJyqnqJxvlpl+zkNGFILpmuBXVJogNOCfK071Qci1gg7C4Iw1Xrseklcajg==","license": "MIT","bin": {"openvibe-download": "bin/openvibe-download.js"@@@ -31,7 +31,7 @@"helmet": "^8.1.0","iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.247.0",-- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.73.0",++ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.74.0","openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.48.0","pg": "^8.23.0"},@@@ -9,12 +9,15 @@* - the run tokens minted for their attempts (codes_run_tokens, C3: ids and expiry, never the token);* - their workspace choices (codes_user_settings, C4b);* - the events Codes sent about them (codes_outbox);-- * - telemetry samples that name them.++ * - telemetry samples that name them: the SDK covers telemetry_samples itself (telemetry.json, then subject NULL).** Export carries the tasks whole, and the task-keyed rows as session-lines.json, attempts.json and proof.json.* Deletion erases everything task-keyed and then the tasks, in one transaction. Telemetry samples stay as numbers with* the person's id removed. The Run jobs themselves are OpenVibe.Run's records (usage billed to the project) and are* Run's to answer for.++ *++ * test/account-data.test.js asserts uncovered() is empty, so a new table with a person column fails CI until it is++ * added to TABLES or TASK_KEYED, or exempted there with a reason.*/const { createAccountData, TOPICS } = require('openvibe-sdk/account-data');@@@ -33,12 +36,6 @@ const TABLES = [// The token itself is never stored; its jti is an internal handle and stays out of the export.{ table: 'codes_run_tokens', subject: 'subject', file: 'run-tokens.json', order: 'created_at', columns: ['id', 'task_id', 'attempt_id', 'provider', 'expires_at', 'created_at'] },{ table: 'codes_outbox', subject: 'subject', file: 'events-sent.json', order: 'created_at', columns: ['event_id', 'topic', 'created_at', 'published_at'] },-- {-- table: 'telemetry_samples', subject: 'subject', file: 'telemetry.json', order: 'at',-- columns: ['at', 'operation', 'resource', 'status', 'latency_ms'],-- // The sample stays (latency and error rates are the service's), without the person in it.-- erase: { anonymize: {} },-- },];// The rows that hang under a person's tasks: exported under the task, erased before it.@@@ -7,9 +7,10 @@*/const assert = require('assert');const http = require('http');--const { createNetworkSender } = require('openvibe-sdk/account-data');++const { createNetworkSender, uncovered } = require('openvibe-sdk/account-data');const { signDeliveryHeaders } = require('openvibe-sdk/events');const { ids } = require('openvibe-contracts');++const { TABLES, TASK_KEYED } = require('../server/account-data');const { boot, check, done } = require('./helpers/boot');const SECRET = `whsec_${'fixture'.repeat(6)}`;@@@ -63,6 +64,18 @@ async function startNetworkStub() {const ev = (id, type, payload) => ({ event_id: id, event_type: type, source: 'network', version: 1, timestamp: new Date().toISOString(), payload });try {++ await check('every table that names a person is covered, task-keyed or exempted with a reason', async () => {++ const missing = await uncovered(db, {++ tables: TABLES,++ taskKeyed: TASK_KEYED.map((k) => k.table),++ exempt: {++ account_data_events: 'the ledger of requests itself: which export and deletion Codes applied',++ event_outbox: 'no person column (the SDK outbox keeps envelopes); dropped with the console in 0002 anyway',++ },++ });++ assert.deepStrictEqual(missing, [], `a table with a person column is neither covered nor exempted: ${missing.join(', ')}`);++ });++await check('an unsigned delivery is refused', async () => {const r = await deliver(ev(`evt_${ids.ulid()}`, 'network.account.deleted', { deletion_id: `del_${ids.ulid()}`, subject: ada, aliases: [] }), { secret: null });assert.strictEqual(r.status, 401);@@@ -86,6 +99,10 @@ async function startNetworkStub() {}const tok = part.body.files.find((x) => x.name === 'run-tokens.json').content[0];assert.ok(!('jti' in tok), 'a run token’s jti stays out of the export');++ const telemetry = part.body.files.find((x) => x.name === 'telemetry.json');++ assert.ok(telemetry, 'telemetry.json is in the export (the SDK covers the table)');++ assert.strictEqual(telemetry.content.length, 1, 'Ada’s sample only');++ assert.deepStrictEqual(Object.keys(telemetry.content[0]).sort(), ['at', 'latency_ms', 'operation', 'resource', 'service', 'status']);});await check('a deletion erases them, keeps everyone else’s, and confirms once', async () => {
@@ -94,7 +94,7 @@ More: [docs/cli.md](docs/cli.md).
@@ -94,7 +94,7 @@ More: [docs/cli.md](docs/cli.md).
- **OpenVibe.Network** — SSO (OAuth client `codes`, PKCE S256) and its JWKS.
- **OpenVibe.Network** — SSO (OAuth client `codes`, PKCE S256) and its JWKS.
- **PostgreSQL 18 and Valkey 9** (OpenVibe.Host `roles/data/`, ADR-035): `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional).
- **PostgreSQL 18 and Valkey 9** (OpenVibe.Host `roles/data/`, ADR-035): `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional).
-- **openvibe-contracts v0.243.0**, **openvibe-sdk v0.73.0**, **openvibe-shared v4.48.0** (pinned tag tarballs).
+- **openvibe-contracts v0.243.0**, **openvibe-sdk v0.74.0**, **openvibe-shared v4.48.0** (pinned tag tarballs).
No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).
No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).
@@ -170,6 +170,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
@@ -170,6 +170,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.247.0
- openvibe-contracts: v0.247.0
-- openvibe-sdk: v0.73.0
+- openvibe-sdk: v0.74.0
- openvibe-shared: v4.48.0
- openvibe-shared: v4.48.0
<!-- versions:end -->
<!-- versions:end -->
@@ -8,11 +8,11 @@
@@ -8,11 +8,11 @@
"port": 4900,
"port": 4900,
"domain": "openvibe.codes",
"domain": "openvibe.codes",
"contracts": "openvibe-contracts v0.247.0",
"contracts": "openvibe-contracts v0.247.0",
- "sdk": "openvibe-sdk v0.73.0",
+ "sdk": "openvibe-sdk v0.74.0",
"shared": "openvibe-shared v4.48.0",
"shared": "openvibe-shared v4.48.0",
"packages": [
"packages": [
"openvibe-contracts v0.247.0",
"openvibe-contracts v0.247.0",
- "openvibe-sdk v0.73.0",
+ "openvibe-sdk v0.74.0",
"openvibe-shared v4.48.0"
"openvibe-shared v4.48.0"
],
],
"summary": "Public at https://openvibe.codes (sudo ovhost deploy codes; unit openvibe-codes on 127.0.0.1:4900). Since 2026-10-08 Codes is the open coding-agent harness: openvibe-codes (a command-line tool and Node.js module, v0.5.0) runs a coding task on Claude Code, Claude Code on another provider, Codex, OpenCode, Command Code, MiniMax Code, Aider, the DeepSeek API or a person's own OpenAI-compatible model on their machine, hands it to the next agent when one fails or goes quiet, and keeps every run; the site carries the agent catalog with a public routing API, getting started, Improve OpenVibe and the community documents. The developer console that used to live here moved to OpenVibe.Services (https://openvibe.services) and every one of its addresses answers a permanent redirect there.",
"summary": "Public at https://openvibe.codes (sudo ovhost deploy codes; unit openvibe-codes on 127.0.0.1:4900). Since 2026-10-08 Codes is the open coding-agent harness: openvibe-codes (a command-line tool and Node.js module, v0.5.0) runs a coding task on Claude Code, Claude Code on another provider, Codex, OpenCode, Command Code, MiniMax Code, Aider, the DeepSeek API or a person's own OpenAI-compatible model on their machine, hands it to the next agent when one fails or goes quiet, and keeps every run; the site carries the agent catalog with a public routing API, getting started, Improve OpenVibe and the community documents. The developer console that used to live here moved to OpenVibe.Services (https://openvibe.services) and every one of its addresses answers a permanent redirect there.",
@@ -16,7 +16,7 @@
@@ -16,7 +16,7 @@
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.247.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.247.0",
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.73.0",
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.74.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.48.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.48.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
@@ -1165,9 +1165,9 @@
@@ -1165,9 +1165,9 @@
}
}
},
},
"node_modules/openvibe-sdk": {
"node_modules/openvibe-sdk": {
- "version": "0.73.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.73.0",
- "integrity": "sha512-XClxoVm9M6RA8eXlGgYZ0u6q9n5h79mCXqAAEg9fX21KgcqP69RmG+cX+6wrOf7TaEj7eSBWMxHjiIeIa9N7Dg==",
+ "version": "0.74.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.74.0",
+ "integrity": "sha512-DO9KVPnUriY/I4lX6ZEJtXaUvRDbJyqnqJxvlpl+zkNGFILpmuBXVJogNOCfK071Qci1gg7C4Iw1Xrseklcajg==",
"license": "MIT",
"license": "MIT",
"bin": {
"bin": {
"openvibe-download": "bin/openvibe-download.js"
"openvibe-download": "bin/openvibe-download.js"
@@ -31,7 +31,7 @@
@@ -31,7 +31,7 @@
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.247.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.247.0",
- "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.73.0",
+ "openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.74.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.48.0",
"openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.48.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
@@ -9,12 +9,15 @@
@@ -9,12 +9,15 @@
* - the run tokens minted for their attempts (codes_run_tokens, C3: ids and expiry, never the token);
* - the run tokens minted for their attempts (codes_run_tokens, C3: ids and expiry, never the token);
* - their workspace choices (codes_user_settings, C4b);
* - their workspace choices (codes_user_settings, C4b);
* - the events Codes sent about them (codes_outbox);
* - the events Codes sent about them (codes_outbox);
- * - telemetry samples that name them.
+ * - telemetry samples that name them: the SDK covers telemetry_samples itself (telemetry.json, then subject NULL).
*
*
* Export carries the tasks whole, and the task-keyed rows as session-lines.json, attempts.json and proof.json.
* Export carries the tasks whole, and the task-keyed rows as session-lines.json, attempts.json and proof.json.
* Deletion erases everything task-keyed and then the tasks, in one transaction. Telemetry samples stay as numbers with
* Deletion erases everything task-keyed and then the tasks, in one transaction. Telemetry samples stay as numbers with
* the person's id removed. The Run jobs themselves are OpenVibe.Run's records (usage billed to the project) and are
* the person's id removed. The Run jobs themselves are OpenVibe.Run's records (usage billed to the project) and are
* Run's to answer for.
* Run's to answer for.
+ *
+ * test/account-data.test.js asserts uncovered() is empty, so a new table with a person column fails CI until it is
+ * added to TABLES or TASK_KEYED, or exempted there with a reason.
*/
*/
const { createAccountData, TOPICS } = require('openvibe-sdk/account-data');
const { createAccountData, TOPICS } = require('openvibe-sdk/account-data');
@@ -33,12 +36,6 @@ const TABLES = [
@@ -33,12 +36,6 @@ const TABLES = [
// The token itself is never stored; its jti is an internal handle and stays out of the export.
// The token itself is never stored; its jti is an internal handle and stays out of the export.
{ table: 'codes_run_tokens', subject: 'subject', file: 'run-tokens.json', order: 'created_at', columns: ['id', 'task_id', 'attempt_id', 'provider', 'expires_at', 'created_at'] },
{ table: 'codes_run_tokens', subject: 'subject', file: 'run-tokens.json', order: 'created_at', columns: ['id', 'task_id', 'attempt_id', 'provider', 'expires_at', 'created_at'] },
{ table: 'codes_outbox', subject: 'subject', file: 'events-sent.json', order: 'created_at', columns: ['event_id', 'topic', 'created_at', 'published_at'] },
{ table: 'codes_outbox', subject: 'subject', file: 'events-sent.json', order: 'created_at', columns: ['event_id', 'topic', 'created_at', 'published_at'] },
- {
- table: 'telemetry_samples', subject: 'subject', file: 'telemetry.json', order: 'at',
- columns: ['at', 'operation', 'resource', 'status', 'latency_ms'],
- // The sample stays (latency and error rates are the service's), without the person in it.
- erase: { anonymize: {} },
- },
];
];
// The rows that hang under a person's tasks: exported under the task, erased before it.
// The rows that hang under a person's tasks: exported under the task, erased before it.
@@ -7,9 +7,10 @@
@@ -7,9 +7,10 @@
*/
*/
const assert = require('assert');
const assert = require('assert');
const http = require('http');
const http = require('http');
-const { createNetworkSender } = require('openvibe-sdk/account-data');
+const { createNetworkSender, uncovered } = require('openvibe-sdk/account-data');
const { signDeliveryHeaders } = require('openvibe-sdk/events');
const { signDeliveryHeaders } = require('openvibe-sdk/events');
const { ids } = require('openvibe-contracts');
const { ids } = require('openvibe-contracts');
+const { TABLES, TASK_KEYED } = require('../server/account-data');
const { boot, check, done } = require('./helpers/boot');
const { boot, check, done } = require('./helpers/boot');
const SECRET = `whsec_${'fixture'.repeat(6)}`;
const SECRET = `whsec_${'fixture'.repeat(6)}`;
@@ -63,6 +64,18 @@ async function startNetworkStub() {
@@ -63,6 +64,18 @@ async function startNetworkStub() {
const ev = (id, type, payload) => ({ event_id: id, event_type: type, source: 'network', version: 1, timestamp: new Date().toISOString(), payload });
const ev = (id, type, payload) => ({ event_id: id, event_type: type, source: 'network', version: 1, timestamp: new Date().toISOString(), payload });
try {
try {
+ await check('every table that names a person is covered, task-keyed or exempted with a reason', async () => {
+ const missing = await uncovered(db, {
+ tables: TABLES,
+ taskKeyed: TASK_KEYED.map((k) => k.table),
+ exempt: {
+ account_data_events: 'the ledger of requests itself: which export and deletion Codes applied',
+ event_outbox: 'no person column (the SDK outbox keeps envelopes); dropped with the console in 0002 anyway',
+ },
+ });
+ assert.deepStrictEqual(missing, [], `a table with a person column is neither covered nor exempted: ${missing.join(', ')}`);
+ });
+
await check('an unsigned delivery is refused', async () => {
await check('an unsigned delivery is refused', async () => {
const r = await deliver(ev(`evt_${ids.ulid()}`, 'network.account.deleted', { deletion_id: `del_${ids.ulid()}`, subject: ada, aliases: [] }), { secret: null });
const r = await deliver(ev(`evt_${ids.ulid()}`, 'network.account.deleted', { deletion_id: `del_${ids.ulid()}`, subject: ada, aliases: [] }), { secret: null });
assert.strictEqual(r.status, 401);
assert.strictEqual(r.status, 401);
@@ -86,6 +99,10 @@ async function startNetworkStub() {
@@ -86,6 +99,10 @@ async function startNetworkStub() {
}
}
const tok = part.body.files.find((x) => x.name === 'run-tokens.json').content[0];
const tok = part.body.files.find((x) => x.name === 'run-tokens.json').content[0];
assert.ok(!('jti' in tok), 'a run token’s jti stays out of the export');
assert.ok(!('jti' in tok), 'a run token’s jti stays out of the export');
+ const telemetry = part.body.files.find((x) => x.name === 'telemetry.json');
+ assert.ok(telemetry, 'telemetry.json is in the export (the SDK covers the table)');
+ assert.strictEqual(telemetry.content.length, 1, 'Ada’s sample only');
+ assert.deepStrictEqual(Object.keys(telemetry.content[0]).sort(), ['at', 'latency_ms', 'operation', 'resource', 'service', 'status']);
});
});
await check('a deletion erases them, keeps everyone else’s, and confirms once', async () => {
await check('a deletion erases them, keeps everyone else’s, and confirms once', async () => {