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
4 changes: 2 additions & 2 deletions .github/scripts/build_publish_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ def _baseline_worker_identities(baseline_workers_json: dict[str, Any] | None) ->

# Workers the engine itself hosts (enabled via the engine config, not
# installed from the registry). A target install can flip one on mid-boot
# (e.g. harness enables `iii-stream` for console streaming), which lands it in
# the workers-baseline diff even though its interface is not part of the
# (e.g. a worker that still lists the deprecated `iii-stream` builtin as a
# dependency), which lands it in the workers-baseline diff even though its interface is not part of the
# released worker's surface — and its schemas are not this repo's to fix.
ENGINE_BUILTIN_WORKERS = frozenset(
{
Expand Down
4 changes: 2 additions & 2 deletions .github/scripts/tests/test_build_publish_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ def test_skipped_interface_capture_allows_explicit_empty_surface(worker_name: st


def test_engine_builtins_are_not_deployment_targets() -> None:
"""A target install can enable an engine-hosted worker mid-boot (harness
turns on `iii-stream`), which lands it in the workers-baseline diff. Its
"""A target install can enable an engine-hosted worker mid-boot (e.g. a
worker that still depends on the deprecated `iii-stream`), which lands it in the workers-baseline diff. Its
interface is not part of the released worker's surface and must not reach
the typed-schema gate."""
from build_publish_payload import _resolve_target_worker_names
Expand Down
4 changes: 2 additions & 2 deletions DOCUMENTATION_GUIDELINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,13 @@ If you only need the new value inside the same function that wrote it, call

### How to bind

1. Register a handler: `registerFunction('stream::on-change', handler)`.
1. Register a handler: `registerFunction('consumer::on-my-worker-change', handler)`.
2. Register the trigger:

```typescript
iii.registerTrigger({
type: 'my-worker',
function_id: 'stream::on-change',
function_id: 'consumer::on-my-worker-change',
config: {
// optional filters — see get function info on the trigger type
},
Expand Down
21 changes: 20 additions & 1 deletion approval-gate/Cargo.lock

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

4 changes: 0 additions & 4 deletions approval-gate/src/testkit/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ pub async fn spawn_engine() -> Option<Engine> {
- name: iii-worker-manager
config:
port: {port}
- name: iii-pubsub
config:
adapter:
name: local
- name: configuration
config:
adapter:
Expand Down
5 changes: 3 additions & 2 deletions docs/architecture/iii-worker-yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ tags:

Example: [`harness/iii.worker.yaml`](../../harness/iii.worker.yaml).

Engine-owned dependencies such as `configuration`, `iii-stream`, and
`iii-observability` use npm-style major wildcards such as `0.x`. Published stable
Engine-owned dependencies such as `configuration` and `iii-observability` use
npm-style major wildcards such as `0.x`. `iii-stream` is deprecated: do not add
it as a dependency of a new worker. Published stable
worker dependencies use a caret range whose lower bound is the newest stable
release validated with the current SDK. For example, `state: "^0.22.2"` accepts
compatible patch releases without falling back to an older SDK build. A worker
Expand Down
2 changes: 1 addition & 1 deletion fp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ fp::pipe { through: [
agent policy hard-denies — session writes and `session::store::*`,
`approval::*`, `configuration::*`/`oauth::*` (credentials),
`router::*`/`provider::*` (model spend), `harness::*`/`run::*` (turn
control), `stream::*` and bus internals, `*::on-config-change` — because
control), `stream::*` (the deprecated iii-stream surface) and bus internals, `*::on-config-change` — because
steps run with worker authority and must not ride past those denies.
Session READS (`session::get`/`list`/`messages*`/`get-message`/
`get-attachment`/`list-attachments`) are allowed, like `database::query`:
Expand Down
2 changes: 2 additions & 0 deletions fp/src/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ fn forbidden_step(function_id: &str, approval_gate_running: bool) -> Option<&'st
worker authority, which would bypass the agent-level deny on them",
);
}
// `stream::*` is the deprecated iii-stream surface. Engines still host it,
// so keep refusing it here until the builtin is removed.
if function_id.starts_with("stream::")
|| function_id.starts_with("hook-fanout::")
|| function_id.starts_with("iii::")
Expand Down
6 changes: 3 additions & 3 deletions harness/architecture/reactive-triggers.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ and never changes delivery.
| `state` | `scope?`, `key?` — omit `key` and it fires for every key in the scope |
| `cron` | `expression` (6-field: sec min hour day month weekday) |
| `durable:subscriber` | `topic`, `queue_config?` — the queue. `queue` is the provider *package*, not a type |
| `subscribe` | `topic` — pubsub |
| `stream` | `stream_name?`, `group_id?`, `item_id?` |
| `stream:join` / `stream:leave` | `stream_name?` |
| `subscribe` (deprecated: pubsub) | `topic` — pubsub |
| `stream` (deprecated: iii-stream) | `stream_name?`, `group_id?`, `item_id?` |
| `stream:join` / `stream:leave` (deprecated: iii-stream) | `stream_name?` |
| `log` | `level?` |
| `trace` | `service_name?`, `status?` |
| `configuration` | `configuration_id?`, `event_types?` |
Expand Down
1 change: 0 additions & 1 deletion harness/tests/e2e/stack-config/engine.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ workers:
name: fs
config:
directory: ${HARNESS_E2E_RUN_DIR}/configuration
- name: iii-stream
- name: iii-observability
2 changes: 1 addition & 1 deletion harness/tests/integration/src/stack/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ workers:
directory: {config_dir}
# iii-state deliberately absent: the standalone `state` worker owns the
# `state` trigger type (its boot guard refuses to start beside the builtin).
- name: iii-stream
# iii-stream deliberately absent too: nothing in this stack uses streams.
"#,
config_dir = layout.configuration_dir().display(),
)
Expand Down
2 changes: 1 addition & 1 deletion harness/tests/integration/src/stack/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub(crate) fn stack_info(bins: &StackBins, layout: &RunLayout, port: u16) -> any
Ok(json!({
"profile": STACK_PROFILE,
"components": {
"engine_builtins": ["configuration", "state", "stream", "cron"],
"engine_builtins": ["configuration", "state", "cron"],
"external_workers": external_workers,
"controlled_services": ["scripted-router", "integration-probe"]
},
Expand Down
2 changes: 1 addition & 1 deletion harness/tests/integration/src/stack/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn engine_config_uses_rc_compatible_builtin_workers() {
let layout = RunLayout::allocate(artifacts.path(), "run-001").unwrap();
let yaml = config::render_engine_yaml(&layout, 3210);

assert!(yaml.contains("name: iii-stream"));
assert!(!yaml.contains("name: iii-stream"));
assert!(!yaml.contains("name: iii-cron"));
assert!(!yaml.contains("name: iii-observability"));
assert!(config::WORKER_START_ORDER.contains(&"cron"));
Expand Down
5 changes: 3 additions & 2 deletions harness/tests/register-trigger-use-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,8 +422,9 @@ event as its whole payload and:
| the call **errors** (unknown id, bad payload) | handler **skipped**, silently |

Supported on every builtin type except `log` and `trace`: `state`, `cron`,
`durable:subscriber` (the queue), `subscribe` (pubsub), `stream`,
`stream:join` / `stream:leave`, `http`, `configuration`. Note the queue trigger
`durable:subscriber` (the queue), `subscribe` (pubsub, deprecated), `stream`
and `stream:join` / `stream:leave` (iii-stream, deprecated), `http`,
`configuration`. Note the queue trigger
type string is `durable:subscriber` — `queue` is the *provider package* name,
not a trigger type. **Silently ignored** on worker-defined types
(`harness::turn-completed`, `storage::object-created`, …) — those are
Expand Down
1 change: 1 addition & 0 deletions iii-permissions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ rules:
- '!state::set'
- '!state::update'
- '!state::delete'
# Deprecated iii-stream write; keep denied while engines still host it.
- '!stream::set'
- '!iii::durable::publish'
- '!email::on-config-change'
Expand Down
2 changes: 1 addition & 1 deletion image-resize/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ cd examples/image-resize-demo

This will:
1. Install image-resize via the local registry
2. Start the III engine (RestApiModule on :3111, StreamModule on :3112, external image-resize)
2. Start the III engine (RestApiModule on :3111, external image-resize)
3. Start the Node.js worker (registers HTTP endpoints)
4. Send test images to `POST /thumbnail` and display results
5. Save thumbnails to `output/`
Expand Down
10 changes: 0 additions & 10 deletions image-resize/example/config.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
workers:
- name: iii-stream
config:
port: ${STREAM_PORT:3112}
host: 127.0.0.1
adapter:
name: kv
config:
store_method: file_based # Options: in_memory, file_based
file_path: ./data/stream_store

- name: iii-http
config:
port: 3111
Expand Down
2 changes: 1 addition & 1 deletion image-resize/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct Cli {
#[arg(long, default_value = "./config.yaml")]
config: String,

/// WebSocket URL of the III engine (port 49134 = engine main WS, not StreamModule 3112)
/// WebSocket URL of the III engine (port 49134 = engine main WS)
#[arg(long, env = "III_URL", default_value = "ws://127.0.0.1:49134")]
url: String,

Expand Down
2 changes: 1 addition & 1 deletion lsp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Other file types are passed through without analysis.

## Features

- **Completions** — function IDs, trigger types, payload properties, trigger config properties, and known values (stream names, topics, API paths). Triggered on `'`, `"`, `:`, `{`, ` `, and `=` (for Python keyword arguments).
- **Completions** — function IDs, trigger types, payload properties, trigger config properties, and known values (stream names, topics, API paths). Triggered on `'`, `"`, `:`, `{`, ` `, and `=` (for Python keyword arguments). The `stream`, `stream:join` and `stream:leave` trigger types (served by the deprecated iii-stream builtin) are tagged deprecated in completions and hovers.
- **Hover** — function description plus request and response JSON schemas, rendered inline.
- **Diagnostics** — validates function IDs, required payload fields, trigger types and config properties, cron expressions, and HTTP methods. Republished on every `did_open` / `did_change`.

Expand Down
68 changes: 61 additions & 7 deletions lsp/src/completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@ use crate::engine_introspection::{FunctionInfo, TriggerTypeInfo};
use std::sync::Arc;
use tower_lsp_server::ls_types::*;

/// Trigger types served by the deprecated iii-stream builtin. Engines still
/// register them, so they stay completable, but editors render them as
/// deprecated so new code does not adopt them.
const DEPRECATED_TRIGGER_TYPES: &[&str] = &["stream", "stream:join", "stream:leave"];

/// Short deprecation note shown in completion details and hovers.
pub const DEPRECATED_TRIGGER_NOTE: &str = "deprecated: iii-stream";

pub fn is_deprecated_trigger_type(id: &str) -> bool {
DEPRECATED_TRIGGER_TYPES.contains(&id)
}

fn trigger_type_item(tt: &TriggerTypeInfo) -> CompletionItem {
let deprecated = is_deprecated_trigger_type(&tt.id);
let detail = if deprecated {
format!("{} ({DEPRECATED_TRIGGER_NOTE})", tt.description)
} else {
tt.description.clone()
};
CompletionItem {
label: tt.id.clone(),
kind: Some(CompletionItemKind::ENUM),
detail: Some(detail),
tags: deprecated.then(|| vec![CompletionItemTag::DEPRECATED]),
insert_text: Some(tt.id.clone()),
..Default::default()
}
}

pub fn get_completions(
context: &CompletionContext,
current_text: &str,
Expand Down Expand Up @@ -41,13 +70,7 @@ pub fn get_completions(
let mut items = Vec::new();
for entry in engine.trigger_types.iter() {
let tt: &TriggerTypeInfo = entry.value();
items.push(CompletionItem {
label: tt.id.clone(),
kind: Some(CompletionItemKind::ENUM),
detail: Some(tt.description.clone()),
insert_text: Some(tt.id.clone()),
..Default::default()
});
items.push(trigger_type_item(tt));
}
items
}
Expand Down Expand Up @@ -201,6 +224,37 @@ mod tests {
assert_eq!(items[0], "http");
}

fn trigger_type(id: &str, description: &str) -> TriggerTypeInfo {
TriggerTypeInfo {
id: id.to_string(),
description: description.to_string(),
trigger_request_format: None,
call_request_format: None,
}
}

#[test]
fn stream_trigger_types_are_tagged_deprecated() {
for id in ["stream", "stream:join", "stream:leave"] {
let item = trigger_type_item(&trigger_type(id, "Stream trigger"));
assert_eq!(item.tags, Some(vec![CompletionItemTag::DEPRECATED]), "{id}");
assert_eq!(
item.detail.as_deref(),
Some("Stream trigger (deprecated: iii-stream)"),
"{id}"
);
}
}

#[test]
fn other_trigger_types_are_not_tagged() {
for id in ["http", "state", "stream::custom", "my-worker::stream"] {
let item = trigger_type_item(&trigger_type(id, "Some trigger"));
assert_eq!(item.tags, None, "{id}");
assert_eq!(item.detail.as_deref(), Some("Some trigger"), "{id}");
}
}

#[test]
fn none_context_returns_empty() {
let items: Vec<CompletionItem> = Vec::new();
Expand Down
Loading
Loading