Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/create-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
options:
- acp
- console
- iii-database
- database
- iii-directory
- iii-lsp
- iii-lsp-vscode
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: iii-database E2E
name: database E2E

on:
pull_request:
paths:
- 'iii-database/**'
- '.github/workflows/iii-database-e2e.yml'
- 'database/**'
- '.github/workflows/database-e2e.yml'
workflow_dispatch:

concurrency:
group: iii-database-e2e-${{ github.ref }}
group: database-e2e-${{ github.ref }}
cancel-in-progress: true

env:
Expand All @@ -31,14 +31,14 @@ jobs:
- name: Cache cargo registry & build
uses: Swatinem/rust-cache@v2
with:
workspaces: iii-database
workspaces: database

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: iii-database/tests/e2e/workers/harness/package-lock.json
cache-dependency-path: database/tests/e2e/workers/harness/package-lock.json
Comment on lines 36 to +41
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

fd -e yml -e yaml .github/workflows | while read -r wf; do
  echo "== $wf =="
  rg -n '^\s*uses:\s*[^@\s]+@[^\s#]+' "$wf" \
    | rg -n '@(v[0-9]+(\.[0-9]+)?|main|master)$' || true
done

Repository: iii-hq/workers

Length of output: 432


🏁 Script executed:

# First, let's verify the specific file and line numbers
cat -n .github/workflows/database-e2e.yml | sed -n '30,45p'

Repository: iii-hq/workers

Length of output: 726


🏁 Script executed:

# Run the corrected command to find all tag/branch-pinned actions in workflows
fd . '.github/workflows' -e yml -e yaml | while read -r wf; do
  echo "== $wf =="
  rg -n '^\s*uses:\s*[^@\s]+@[^\s#]+' "$wf" \
    | rg '@(v[0-9]+(\.[0-9]+)?|main|master)$' || true
done

Repository: iii-hq/workers

Length of output: 1409


Pin GitHub Action refs to commit SHAs.

Line 37 uses actions/setup-node@v4 (mutable version tag), which violates strict workflow hardening practices and can fail unpinned-uses policy checks. Convert to a pinned commit SHA (e.g., actions/setup-node@1e21ff14b671a668ad81a1eac58d86bc5915f5a6).

Note: This pattern appears across multiple workflows—consider auditing all workflow files to pin GitHub Actions consistently.

🧰 Tools
🪛 zizmor (1.25.2)

[error] 37-37: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/database-e2e.yml around lines 36 - 41, Replace the mutable
action ref "uses: actions/setup-node@v4" with a pinned commit SHA ref (e.g.,
"uses: actions/setup-node@<commit-SHA>") to satisfy the unpinned-uses policy;
update the line that contains actions/setup-node@v4 in the workflow to the
chosen commit SHA and repeat the same pinning for other workflow steps that use
mutable tags to ensure all GitHub Action refs are pinned.


# GHA `services:` blocks can't pass `-c wal_level=logical` to postgres,
# which the row-change tests require. Reuse the same docker-compose
Expand All @@ -53,7 +53,7 @@ jobs:
run: iii --version

- name: Run harness
working-directory: iii-database/tests/e2e
working-directory: database/tests/e2e
# --with-cargo-test runs `cargo test --all-features` against the
# already-running postgres + mysql so the gated driver/pool tests
# actually exercise their target DBs (otherwise they early-return).
Expand All @@ -63,7 +63,7 @@ jobs:
if: failure()
uses: actions/upload-artifact@v4
with:
name: iii-database-e2e-report
name: database-e2e-report
path: |
iii-database/tests/e2e/reports/
database/tests/e2e/reports/
retention-days: 7
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
tags:
- 'acp/v*'
- 'console/v*'
- 'iii-database/v*'
- 'database/v*'
- 'iii-directory/v*'
- 'iii-lsp/v*'
- 'image-resize/v*'
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ node_modules
package-lock.json
pnpm-lock.yaml
yarn.lock
!iii-database/tests/e2e/workers/harness/package-lock.json
!database/tests/e2e/workers/harness/package-lock.json
!shell/tests/e2e/workers/harness/package-lock.json
!console/web/pnpm-lock.yaml

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ asset for the host from the workers registry API.
|---|---|---|
| [`acp`](acp/) | Rust | Agent Client Protocol surface — stdio JSON-RPC, exposes iii agents as ACP sessions. |
| [`harness-node`](harness-node/) | Node | TS port of the iii harness stack — bundles `harness`, `turn-orchestrator`, `approval-gate`, `session`, `hook-fanout`, `auth-credentials`, `models-catalog`, `provider-anthropic`, `provider-openai`, `llm-budget`, and `context-compaction` as one pnpm monorepo. See [`harness-node/README.md`](harness-node/README.md). |
| [`iii-database`](iii-database/) | Rust | PostgreSQL, MySQL, and SQLite client — query, execute, transactions, prepared statements, and change feeds. |
| [`database`](database/) | Rust | PostgreSQL, MySQL, and SQLite client — query, execute, transactions, prepared statements, and change feeds. |
| [`iii-directory`](iii-directory/) | Rust | Engine introspection (functions / triggers / workers), workers-registry proxy, and filesystem-backed skill + prompt reader. |
| [`iii-lsp`](iii-lsp/) | Rust | Language Server for iii function ids, trigger configs, and worker discovery. Autocomplete / hover across JS/TS, Python, Rust. |
| [`iii-lsp-vscode`](iii-lsp-vscode/) | Node | VS Code extension that embeds `iii-lsp`. |
Expand Down
File renamed without changes.
80 changes: 40 additions & 40 deletions iii-database/Cargo.lock → database/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions iii-database/Cargo.toml → database/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[workspace]

[package]
name = "iii-database"
name = "database"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
version = "0.0.4"
edition = "2021"
publish = false

[[bin]]
name = "iii-database"
name = "database"
path = "src/main.rs"

[lib]
Expand Down
38 changes: 19 additions & 19 deletions iii-database/README.md → database/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# iii-database
# database

> Connect to PostgreSQL, MySQL, and SQLite. Run queries, prepared statements, transactions, and subscribe to row-level change feeds.

Expand All @@ -12,7 +12,7 @@
## Install

```sh
iii worker add iii-[email protected]
iii worker add [email protected]
```

## Configure
Expand All @@ -21,7 +21,7 @@ Add a single `databases` block to your `config.yaml`. SQLite is the recommended

```yaml
workers:
- name: iii-database
- name: database
config:
databases:
primary:
Expand Down Expand Up @@ -95,18 +95,18 @@ SQLite ignores the `tls` block (local-file driver).
```ts
import { call } from 'iii-sdk'

await call('iii-database::execute', {
await call('database::execute', {
db: 'primary',
sql: 'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, email TEXT)'
})

await call('iii-database::execute', {
await call('database::execute', {
db: 'primary',
sql: 'INSERT INTO users (email) VALUES (?), (?)',
params: ['a@x', 'b@x']
})

const { rows } = await call('iii-database::query', {
const { rows } = await call('database::query', {
db: 'primary',
sql: 'SELECT id, email FROM users ORDER BY id'
})
Expand All @@ -116,27 +116,27 @@ const { rows } = await call('iii-database::query', {

| Function | Purpose |
|---|---|
| `iii-database::query` | Read SQL. Returns `{ rows, row_count, columns }`. |
| `iii-database::execute` | Write SQL. Returns `{ affected_rows, last_insert_id, returned_rows }`.<br>**`last_insert_id` semantics:** SQLite/MySQL surface the engine's `last_insert_rowid()` / `LAST_INSERT_ID()` (only populated for INSERT). Postgres has no equivalent — `last_insert_id` is set from the **first column of the first RETURNING row**, so put your PK first: `RETURNING id, name`, not `RETURNING name, id`. |
| `iii-database::prepareStatement` | Pin a connection and return `{ handle: { id, expires_at } }`. |
| `iii-database::runStatement` | Run a previously-prepared handle. (No `timeout_ms` — uses the pinned connection's session lifetime; configure via `ttl_seconds` on `prepareStatement`.) |
| `iii-database::transaction` | Atomic batch sequence; rolls back on first failure. One-shot — pass all statements together. |
| `iii-database::beginTransaction` | Open an interactive transaction. Returns `{ transaction: { id, expires_at } }`. Configurable `timeout_ms` (default 30 000, max 300 000) auto-rolls back if the deadline elapses. |
| `iii-database::transactionQuery` | Read SQL inside an interactive transaction. Same envelope as `query`. |
| `iii-database::transactionExecute` | Write SQL inside an interactive transaction. Same envelope as `execute`. Rejects bare `BEGIN`/`COMMIT`/`ROLLBACK`/`SAVEPOINT`/`SET TRANSACTION` with `INVALID_PARAM` — finalize via the dedicated handlers below. |
| `iii-database::commitTransaction` | Commit and finalize an interactive transaction. Subsequent calls against the same id return `TRANSACTION_NOT_FOUND`. |
| `iii-database::rollbackTransaction` | Rollback and finalize an interactive transaction. Subsequent calls against the same id return `TRANSACTION_NOT_FOUND`. |
| `database::query` | Read SQL. Returns `{ rows, row_count, columns }`. |
| `database::execute` | Write SQL. Returns `{ affected_rows, last_insert_id, returned_rows }`.<br>**`last_insert_id` semantics:** SQLite/MySQL surface the engine's `last_insert_rowid()` / `LAST_INSERT_ID()` (only populated for INSERT). Postgres has no equivalent — `last_insert_id` is set from the **first column of the first RETURNING row**, so put your PK first: `RETURNING id, name`, not `RETURNING name, id`. |
| `database::prepareStatement` | Pin a connection and return `{ handle: { id, expires_at } }`. |
| `database::runStatement` | Run a previously-prepared handle. (No `timeout_ms` — uses the pinned connection's session lifetime; configure via `ttl_seconds` on `prepareStatement`.) |
| `database::transaction` | Atomic batch sequence; rolls back on first failure. One-shot — pass all statements together. |
| `database::beginTransaction` | Open an interactive transaction. Returns `{ transaction: { id, expires_at } }`. Configurable `timeout_ms` (default 30 000, max 300 000) auto-rolls back if the deadline elapses. |
| `database::transactionQuery` | Read SQL inside an interactive transaction. Same envelope as `query`. |
| `database::transactionExecute` | Write SQL inside an interactive transaction. Same envelope as `execute`. Rejects bare `BEGIN`/`COMMIT`/`ROLLBACK`/`SAVEPOINT`/`SET TRANSACTION` with `INVALID_PARAM` — finalize via the dedicated handlers below. |
| `database::commitTransaction` | Commit and finalize an interactive transaction. Subsequent calls against the same id return `TRANSACTION_NOT_FOUND`. |
| `database::rollbackTransaction` | Rollback and finalize an interactive transaction. Subsequent calls against the same id return `TRANSACTION_NOT_FOUND`. |

## Triggers

### `iii-database::row-change`
### `database::row-change`
Postgres only. Streams row-level changes via logical replication (`pgoutput`).

> **NOTE (v1.0.0):** Event dispatch is not yet functional. The publication and replication slot are created at startup, but the streaming decode loop is stubbed pending an upstream `tokio-postgres` replication API release. Operators can pre-provision slots and publications now; events will start flowing in a later release.

```yaml
triggers:
- type: iii-database::row-change
- type: database::row-change
config:
db: primary
schema: public
Expand Down Expand Up @@ -171,7 +171,7 @@ A few operations are no-ops on certain drivers. They emit a `tracing::warn!` rat
| `execute` with `returning: [...]` | ✓ | ✓ | warn-once + ignore |
| `transaction` `isolation: read_committed` / `repeatable_read` | warn + use serializable | ✓ | ✓ |
| `transaction` `isolation: serializable` | ✓ (`BEGIN IMMEDIATE`) | ✓ | ✓ |
| `iii-database::row-change` trigger | — | setup-only in v1.0.0 (see above) | — |
| `database::row-change` trigger | — | setup-only in v1.0.0 (see above) | — |


## Troubleshooting
Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions iii-database/iii.worker.yaml → database/iii.worker.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
iii: v1
name: iii-database
name: database
language: rust
deploy: binary
manifest: Cargo.toml
bin: iii-database
bin: database
description: Talk to PostgreSQL, MySQL, and SQLite from iii — query, execute, transactions, prepared statements, and change feeds.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
type: how-to
function_id: iii-database::execute
function_id: database::execute
title: Run a write statement and return affected rows
---

# When to use

Call `iii-database::execute` for any write-side SQL — `INSERT`, `UPDATE`,
Call `database::execute` for any write-side SQL — `INSERT`, `UPDATE`,
`DELETE`, or DDL (`CREATE TABLE`, `ALTER TABLE`, `DROP INDEX`, ...). The
response carries `affected_rows`, an optional `last_insert_id`, and a
`returned_rows` array populated when the caller asks for `RETURNING`-style
Expand All @@ -21,12 +21,12 @@ Reach for it when:
(e.g. server-defaulted `id` + `created_at`) — set `returning` on
Postgres or SQLite.

Use [`iii-database::query`](iii://iii-database/query) instead when the
Use [`database::query`](iii://database/query) instead when the
statement reads — `execute` does run a `SELECT` if you give it one but
discards the rows and reports `affected_rows: 0`, which is rarely what a
SELECT caller wants.

Use [`iii-database::transaction`](iii://iii-database/transaction) instead
Use [`database::transaction`](iii://database/transaction) instead
when you need several writes to commit atomically — `execute` runs each
call as its own implicit transaction.

Expand All @@ -43,7 +43,7 @@ call as its own implicit transaction.

`db` and `sql` are required. Empty/whitespace-only `sql` is rejected
uniformly with `DRIVER_ERROR` carrying `message: "empty SQL"` (matches
[`iii-database::query`](iii://iii-database/query)'s contract).
[`database::query`](iii://database/query)'s contract).

`params` accepts JSON primitives, arrays, and objects exactly like
`query` — same per-driver placeholder syntax (`?` for sqlite/mysql,
Expand Down Expand Up @@ -85,7 +85,7 @@ directly in `sql` for sqlite/postgres rather than passing
`RETURNING id, name` works; `RETURNING name, id` returns `name` as
`last_insert_id`. With no `RETURNING` clause, the field is `null`.
- `returned_rows` mirrors the row-of-objects shape from
[`iii-database::query`](iii://iii-database/query). Empty `[]` when the
[`database::query`](iii://database/query). Empty `[]` when the
statement omits `RETURNING` or runs on MySQL.

# Worked example
Expand Down Expand Up @@ -131,6 +131,6 @@ Returns `{ "affected_rows": 17, "last_insert_id": null, "returned_rows": [] }`.

# Related

- `iii-database::query` — for read SQL; returns materialized rows + column metadata instead of `affected_rows`.
- `iii-database::transaction` — group several writes into one atomic batch with rollback on first failure.
- `iii-database::prepareStatement` + `iii-database::runStatement` — re-run the same parameterized write many times without re-parsing on each call.
- `database::query` — for read SQL; returns materialized rows + column metadata instead of `affected_rows`.
- `database::transaction` — group several writes into one atomic batch with rollback on first failure.
- `database::prepareStatement` + `database::runStatement` — re-run the same parameterized write many times without re-parsing on each call.
Loading
Loading