Skip to content

v1.7.0

Latest

Choose a tag to compare

@github-actions github-actions released this 25 Oct 13:48
· 18 commits to master since this release
6110d8b

Upgrade if you point iad at a server you care about, open projects you did not write, give the
agent a service account, or run the MCP server over HTTP. In 1.7.0 every gate judges the server a
call runs on, a repository file cannot widen those limits until
you trust it, agent-supplied code runs as the account you chose or not at all, and passwords stay
out of places they have no reason to be. It also changes what several calls do, so read Breaking
changes before you upgrade. The terms below are defined in CONTEXT.md: operator file,
workspace file, launch settings, trusted file, effective policy, refusal, set-aside
item
, service account and connection account.

Breaking changes

Each item says what changed and what to do. iris-agentic-dev check_config lists what iad set
aside and why, so run it first if a call that worked before is refused now.

Configuration files and trust

  • A workspace file can no longer widen a limit until you trust it. A project's
    .iris-agentic-dev.toml, a file named by --config and a file named by OBJECTSCRIPT_WORKSPACE
    keep applying their connection, tool lists and anything that tightens. They stop applying
    write_tools_enabled = true, destructive_tools_enabled = true, a per-server writeTools,
    dataPolicy = "allow" or "redact" where your operator file is stricter, mcpTemplate = "dev"
    or "test", an allow list or role looser than yours, a kill allowlist, tls_verify = false,
    an ssh_host, a per-server service account and the compile-time gate switched off. Each one is
    set aside and listed in check_config. To apply them, read what the file loosens with
    iris-agentic-dev trust --show, then run iris-agentic-dev trust in a terminal and type
    trust. Or move the setting to ~/.iris-agentic-dev.toml or your launch settings.
  • Trust ends when the settings change. It covers the file's path, the loosening settings it
    held when you trusted it, and the host, port and namespace of the server each applies to. Editing
    other keys keeps trust. Changing a loosening setting, or the address it applies to, sets the
    setting aside until you run trust again. A mid-session edit takes the same route. A trust
    entry takes effect when iad next starts, and trust says to restart.
  • ~/.iris-agentic-dev.toml is always read. It is the operator file, never a workspace file,
    and its [policy.*] sections apply in projects that have a file of their own. A project that
    relied on its own file hiding your policies now gets the stricter of the two, part by part.
    The walk up from the working directory stops before your home directory.
    ~/.iris-dev.toml is no longer read; move anything in it to the operator file.
  • Some settings come only from the operator file or launch settings. Trust entries,
    exec_as_os_user, the HTTP transport token, local_skills and audit_payloads are set aside in
    any workspace file, trusted or not.
  • Loosening settings from your own file stay off a server a project chose. An operator-file
    or launch-settings setting that loosens a limit does not apply to a server an untrusted
    workspace file named, unless that server is on this machine or is the IRIS_HOST server.
  • A workspace [instance.<name>] that reuses a known server's name is ignored and reported.
    The name is taken by servers.json, Server Manager or your operator file.
  • An unreadable or unparsable .iris-agentic-dev.toml stops iad from connecting. Every IRIS
    tool answers CONFIG_INVALID with the file, line and column, and the gates stay closed.
    Before, iad ignored the file and ran without its gates.
  • A server with no mcpTemplate gets the one its system mode implies. Live and Failover
    servers refuse the Execute, Compile and SourceControl categories, Test servers get test,
    and Development servers get dev. A mode iad cannot read is treated as closed for writes, never
    as Development. Declare mcpTemplate = "dev" for the server in your operator file if you want
    the old behaviour.
  • Discovered servers have their system mode checked. Connections found through
    IRIS_CONTAINER, the localhost scan, the Docker scan and .vscode/settings.json used to skip
    the check. A remote one whose mode iad cannot read starts with writes off.
  • Policy values are read without regard to case, and a bad value fails closed. "Live",
    "Block" and "Subject" work. An unknown mcpTemplate means live, an unknown dataPolicy
    means block, an unknown role means subject, and one bad value narrows that field only. Before,
    an unknown template meant dev and a bad fleet entry dropped every policy.
  • Roles are workbench and subject. role = "workspace" still works through 1.7.x, with a
    deprecation note in check_config. role = "control-plane" is reported as unknown and treated
    as workbench.

Gates judge the server the call runs on

  • A call with server = "prod" is judged by prod's effective policy. That covers the
    template, allowed categories, blocklist, dataPolicy, role, write gates and identity.
    A server with no [policy.prod] section gets the strict defaults, not the default server's
    policy. If you leaned on the default server's policy covering the others, write a section for
    each.
  • Pool members start write-closed. A member is open for writes only when iad probes it as
    Development or Test, or when you declare [policy.<name>] writeTools = true.
    IRIS_WRITE_TOOLS_ENABLED=1 and the top-level write_tools_enabled open the default server and
    nothing else. A Server Manager profile whose user cannot read ^%SYS starts write-closed.
  • On the default server with an unknown system mode, the call's namespace decides.
    iris_execute(namespace = "PROD") is refused where it ran before.
  • allow lists apply to every IRIS tool. iris_admin, the iris_production* tools,
    iris_doc put and iris_global set used to slip past them. Categories follow the action.
  • live and test templates refuse iris_test, iris_coverage run, iris_doc writes and
    iris_generate_class.
  • The subject role covers every tool that can write. Fifteen or so tools gain a confirm
    parameter, and confirmed is accepted everywhere confirm is. Source-control writes on a
    subject server are a fixed rule.
  • iris_message_body's dataPolicy argument can only narrow the server's policy. An
    unknown value is INVALID_PARAMS. acknowledge_phi and data_policy are accepted for
    acknowledgePhi and dataPolicy.
  • The CLI runs the same gates as MCP. tool, exec, compile, query, doc and batch
    apply the write and destructive gates, your declarations, the unknown-parameter check and the
    tool list. The IRIS_ALLOW_PROD=1 message is gone, and compile with no files no longer skips
    everything. With writes off, these commands exit 1 with the gate's code.
  • The compile-on-save hook runs iris-agentic-dev compile. It uploads the local file
    before it compiles, and needs the binary on PATH. It no longer calls curl.
  • The benchmark runs four tools. It refuses any other tool, and refuses server,
    namespace, confirm, confirmed and force_writable arguments. The refusal goes back to the
    model as a tool error. The benchmark also reads IRIS_SCHEME and --scheme.
  • force lifts nothing. A forced call is judged by what it runs. iris_compile force_writable = true needs both the compile and admin categories.
  • A resumed elicitation names its tool and server. A resume made for a different tool or
    server is refused. A resume is gated as the write it performs, needs writes on, and uses the
    original compile flag. The agent still answers the yes or no, not a person.
  • A source-control answer iad cannot act on stops the write. iris_doc put, checkout and
    execute read the hook's UserAction answer from a labelled record, so chatter the hook prints
    first no longer decides it. A page or program the hook asks to open (codes 2 to 5) is
    SCM_NEEDS_UI, a code outside 0 to 7 is SCM_ACTION_UNKNOWN, an answer iad cannot read is
    SCM_ACTION_UNPARSEABLE, an alert (6) is SCM_REJECTED, an error status from the hook is
    SCM_ERROR, and a hook whose session cannot start is SCM_UNAVAILABLE. Before, each of these
    read as "no checkout needed" and the document was written.
  • Tools that compile a scratch class are marked in the gate table. Reads over HTTP still
    run through the IrisDevTmp scratch class, and readOnlyHint stays true on them. Use
    read_only = "strict" (below) on a server that must see no writes at all.
  • read_only = "strict" closes the write and destructive gates. On every server, whatever
    opens them: write_tools_enabled, destructive_tools_enabled, a [policy.<name>] writeTools,
    IRIS_WRITE_TOOLS_ENABLED or IRIS_ALLOW_PROD. iris_compile is a write, so strict refuses it
    too. A refused call answers READ_ONLY_STRICT with gate_source: "read_only_strict".

Refusals and check_config

  • Every refusal says its kind, and none tells the agent how to lift it. The response carries a
    refusal object with one of acknowledgement_required, operator_limit, untrusted_setting
    or fixed_rule. An operator limit or untrusted setting names the setting, and an untrusted
    setting points at its set-aside item. Neither carries the operator file's path or the edit that
    would lift it, because an agent that can write files makes any edit it reads. iad writes the remedy
    to stderr once per setting, and iris-agentic-dev trust --show lists every remedy. Existing
    error_code values stay. The role gate gains error_code
    (SUBJECT_CONFIRM_REQUIRED, SUBJECT_SCM_WRITE_BLOCKED), and its text moves to message with
    error kept. POLICY_GATE gains success: false. Compile-time gate refusals are error
    results (isError: true). A privilege error from IRIS is an IRIS error, never a refusal.
  • check_config gains config_sources, effective_policy, compile_time_gate, set_aside,
    identity, read_only and skills.
    credentials_withheld stays as a string alias through
    1.7.x. config_watch_path is never your home directory and can be null. write_tools_source
    can be operator_file.
  • An operator-file edit that loosens a limit waits for a restart. During a session, an edit
    to ~/.iris-agentic-dev.toml that tightens applies at once. One that loosens (a gate key,
    read_only, any policy part, a trust entry, tls_verify = false, an ssh_host, a service
    account, an operator-only setting) applies when iad next starts. iad writes one line to stderr
    per held setting, and check_config lists it under set_aside with the lift "restart iad".
    Deleting the operator file counts as loosening whatever it tightened. Before, every edit applied
    at once.
  • check_config names a config file as the connection source only when it names a target. A
    file with no host or container reports config_file_named_no_target, and
    config_file_warning says when a workspace file exists but is not in use.
  • iad writes one line to stderr at startup when it set something aside. The line goes to
    stderr only, so TSV and JSON output are unchanged.

Identity: where the agent's code runs

  • With a service account set, every path runs the code as that account or refuses. That is
    the HTTP generator, docker exec, NoPWS, SSH, the WebSocket terminal, a named server,
    global_kill, iris_test and iris_coverage. Code that worked on the docker paths because the
    container's OS user held %All now gets <PROTECT>. If the login guard cannot work on your
    instance, set IAD_EXEC_AS_OS_USER=1 or exec_as_os_user = true in the operator file. The
    response then says "source": "os_user_opt_in".
  • server = "qa" uses qa's own service account. A server with none is refused with
    SERVICE_ACCOUNT_UNCONFIGURED_FOR_SERVER when a service account is set. Give the server a
    service_username, in servers.json, iris_add_server or an [instance.<name>] block in a
    file you trust.
  • A named server never falls back to IRIS_CONTAINER. It runs in its own container, or the
    call fails with NO_DOCKER_ROUTE_FOR_SERVER.
  • A refused service account stops the call. A 401, 403 or <PROTECT> on the generator's
    upload or compile returns SERVICE_ACCOUNT_HTTP_DENIED. It no longer falls back to docker exec
    as %All.
  • iris_ws_open, global_kill, iris_test and iris_coverage run/start run as the
    service account.
    The WebSocket terminal needs the account to hold %Development:USE.
    iris_coverage run also needs %DB_IRISSYS:RW on the account, because the line monitor keeps
    its state in IRISSYS; without it the tool answers COVERAGE_PRIVILEGE_DENIED.
  • A WebSocket session ends when its identity changes. After the service account changes, the
    open session is refused with SESSION_IDENTITY_CHANGED.
  • A terminal that asks for a user name is reported. A container whose terminal service needs
    a password login answers DOCKER_TERMINAL_LOGIN_REQUIRED instead of garbled output.
  • A withheld service account is a tool error. SERVICE_ACCOUNT_WITHHELD replaces an
    invalid_request protocol error.
  • Responses gain identity {user, source, path, verified, server}. auth_user and
    service_account_env repeat part of it and go away in 1.8.0.
  • iad sends an IAD-ID: line first on the docker paths and strips it from the output. The
    first line iad writes to the terminal is no longer the agent's.
  • SSH checks host keys. iad uses BatchMode=yes and accept-new, never
    StrictHostKeyChecking=no. A host you have not connected to is recorded on first use. An
    ssh_host that comes only from a workspace file needs trust.

Compile-time gate and code-edit block

  • The compile-time gate refuses everything IRIS runs while compiling. That is #execute,
    ##expression, ##function, a #if whose operand calls code, braced parameter values,
    projections outside %Projection.*, CodeMode = objectgenerator and generator (quoted or
    not), in .cls, .mac and .inc documents, in class XML put under a .cls name, and in
    CREATE PROCEDURE, FUNCTION, METHOD and TRIGGER bodies sent through iris_query. A
    construct inside a comment or string literal is not refused. Embedded &html<>, &js<>,
    &sql(), Python bodies and XData no longer hide a generator that follows them.
  • iris_compile by name reads the stored source first. It checks the class, the .inc
    files it includes (not % system includes), and every member of a wildcard or package target.
    A source it cannot read, for any reason other than "does not exist", is refused. iris_doc
    put with compile=true checks the stored includes as well.
  • iris_compile accepts only the flags and qualifiers that compile what you named. Flag
    letters c, d, k, u and -, and the qualifiers checkuptodate, display,
    displayerror, displaylog, keepsource, quiet, silent and verbose, each with an
    optional no prefix. Anything else is INVALID_PARAMS. The b flag and the /subclasses and
    /expand qualifiers (or an abbreviation of them) compiled stored subclasses the gate never read,
    and their generators ran. e, p and h are refused too.
  • A compile that would also build an out-of-date superclass is refused. IRIS compiles a
    class's stale superclasses along with it, and their generators run, but the gate reads only the
    classes you named. A compile whose target has an out-of-date superclass outside %, Ens.,
    EnsLib., EnsPortal., HS., HSLIB. and HSMOD. answers COMPILE_SUPERCLASS_STALE and
    names it. Compile that superclass first; its own check runs then. A check that cannot run
    answers SUPERCLASS_CHECK_FAILED and compiles nothing.
  • iris_generate_class stores what the model wrote. It used to compile whatever class already
    had the name the model wrote, without saving the generated text and without the gate. It now
    stores the text and compiles it the way iris_doc put with compile does, so the gate, the
    include check and the superclass check apply and a refusal is the tool's answer. The response
    gains compile_errors, a retry gets IRIS's compile errors, and a text with no class name is
    INVALID_OUTPUT. It needs Atelier, so a docker-only connection answers
    NOPWS_ATELIER_REQUIRED.
  • CodeMode = expression and call are accepted. They run at call time. The gate refused
    them before.
  • Turning the gate off needs trust. compile_time_gate = false, [policy.<name>] compileTimeGate = false or IAD_COMPILE_TIME_GATE=0 work from the operator file, launch
    settings or a trusted file. IAD_COMPILE_TIME_GATE=1 always wins. Responses say
    compile_time_gate: "off" when it is.
  • The code-edit block refuses %Routine, $SYSTEM.OBJ.UnCompile, the routine-buffer commands
    (ZLOAD, ZINSERT, ZREMOVE, ZSAVE), ^rMAC, ^rINC and the $SYSTEM.OBJ routine entry
    points.
    It checks its own list of code-storage globals. Code that reads ^Ens.Config* or
    ^IRIS.Temp*, or names a code global in a comment, is no longer refused. The block is a lexical
    check that indirection can get past; its error text now says so and names the service account as
    the boundary.

Credentials, TLS and the HTTP transport

  • mcp --transport http on a non-loopback address will not start without a token. Set
    IAD_HTTP_TOKEN, pass --auth-token-file, or put [transport] http_token in the operator
    file. The token is at least 32 bytes, and clients send Authorization: Bearer <token>. The
    server prints why and exits with status 2 if none is set. A configured token is enforced on
    loopback too.
  • The HTTP transport checks Host and Origin on every bind. It serves localhost,
    127.0.0.1, [::1], the bound IP, and on 0.0.0.0 also host.docker.internal, IP literals and
    each --allowed-host. A request with an Origin passes only when it is same-origin or listed
    with --allowed-origin. Anything else gets 403. Command-line and SDK clients send no Origin
    and are unaffected.
  • IRIS_TLS_VERIFY and IRIS_INSECURE apply to the launch-settings connection only.
    servers.json entries and [instance.*] blocks take their own tls_verify, Server Manager
    profiles always validate, and a workspace file's tls_verify = false applies to that file's own
    connection once you trust it. Add tls_verify = false to the entry of a server with a
    self-signed certificate. The audit client and the iris_search client validate certificates
    too.
  • A configured scheme must be http or https. Any other value makes that entry
    INVALID_CONFIG. Environment credentials go to a workspace file's host only when scheme, host,
    port and prefix all match IRIS_HOST's.
  • Credentials are stored under iad's own keychain service, bound to the server's address.
    iris_add_server writes to the keychain on macOS and Windows, where 1.6.x wrote plaintext.
    servers.json is 0600, and holds a password only on a host with no keychain, tied to the
    address it was saved for. IAD_NO_KEYCHAIN=1 skips the keychain.
  • iris_add_server refuses a name that belongs to a Server Manager profile
    (NAME_CONFLICT), and iris_remove_server no longer deletes Server Manager keychain entries.
    Re-registering a server at a different address drops its stored password
    (password_cleared: true).
  • A servers.json entry reads a Server Manager password only if you imported it and its address
    still matches the profile.
    A .vscode/settings.json named server whose address differs from
    your user-level profile gets no keychain password.
  • A .vscode/settings.json server with no readable password is not used. A named server with
    no keychain entry, no keychain or no Server Manager profile, and a direct host with no
    password, used to connect with the password SYS. They now build no connection, and
    check_config lists <server>:password_missing under set_aside.
  • A config file's container, namespace and account no longer reach the environment. iad used
    to export IRIS_CONTAINER, IRIS_NAMESPACE, IRIS_USERNAME and IRIS_PASSWORD from the file
    into its own process, so every child it started inherited them. It now passes them to the
    connection directly.
  • Discovery sends no credentials until a listener identifies itself as IRIS. It looks for an
    unauthenticated Atelier answer, Portal session cookies or iris_* metrics first. An instance
    that hides all three is no longer found automatically; set IRIS_HOST.
  • Source-control hooks get an empty password, as they do from VS Code. A custom hook that logs
    into another system with it stops working.
  • The local audit log stores sizes, not payloads. code, query, args, content and
    value are recorded as {redacted, chars, lines}, credential-named keys are scrubbed at any
    depth, and the file is 0600. Set audit_payloads = true in the operator file, or
    IAD_AUDIT_PAYLOADS=1, to keep full payloads.
  • install replaces a symlinked lock file instead of writing through it, writes the lock file
    with proper escaping, and refuses dependency names with control characters.
  • skills/skills in the working directory is not served. Set local_skills = true in the
    operator file or IAD_LOCAL_SKILLS=1 to serve local copies of bundled skills. A served copy is
    labelled override and keeps the bundled tier.
  • objectscript.conn honours "https": true. Skill recipes use the tool CLI or
    curl -K - with ${IRIS_SCHEME} instead of a password on a command line.

SQL

  • Read-mode iris_query runs SELECT, WITH and EXPLAIN only. CALL, TUNE TABLE,
    BUILD INDEX, PURGE, SET OPTION, FREEZE PLANS, DML, DDL, GRANT, REVOKE and a second
    statement after ; are SQL_WRITE_BLOCKED. A stored procedure can write anything, and IRIS
    cannot tell iad whether one does, so CALL moves to mode="write". Run TUNE TABLE and the
    other maintenance statements through iris_execute with $SYSTEM.SQL.Execute.
  • iris_interop_query reads message bodies only where dataPolicy is allow. A call with
    body_class, body_where or body_select is DATA_POLICY_BLOCKED otherwise. body_where must
    be one filter: balanced parentheses, no ; or comment outside a quoted string, and no SELECT,
    INTO, UNION, INTERSECT, EXCEPT or write keyword. A quoted 'a;b -- c' is now accepted.

Host files and the VS Code extension

  • Tools read host files only inside the project directory. iris_compile with a file path,
    kb index and iris_symbols_local refuse a path outside it, a path with .., and a symlink
    that leaves it, with HOST_PATH_BLOCKED and a reason. iris_compile also takes only .cls,
    .mac, .inc and .int files. The project directory is OBJECTSCRIPT_WORKSPACE, then
    --workspace, then the directory holding the project's .iris-agentic-dev.toml, then the
    working directory; the filesystem root and your home directory never count. With none, every
    host path is refused. iris_symbols_local stops at 200,000 entries, 32 levels or 10 seconds and
    says so in truncated_reason; kb index reads at most 1,000 files.
  • iris_test and iris_coverage take class patterns only. A pattern with /, \ or ..,
    such as MyApp/Tests, is INVALID_PARAMS. Use MyApp.Tests or MyApp.*. iad no longer points
    ^UnitTestRoot at a shared /tmp/httest/. A root the instance already has is used as it is, so
    tests staged under it run as before; an instance with none (a fresh container, where IRIS would
    answer ERROR #5007 and run nothing) gets a root of its own for the run under <mgr>/Temp,
    removed afterwards.
  • The VS Code extension verifies the server it downloads. Each extension build pins the
    SHA-256 of the five release binaries, and a download or cached copy that does not match is
    deleted and reported. Redirects go only to GitHub's release hosts over HTTPS, at most five hops,
    and the body must match Content-Length and stay under 256 MB.
  • The extension does less in a workspace you have not trusted. iris-agentic-dev.serverPath
    is machine-scoped, so a repository's settings cannot choose the program it runs. In an untrusted
    workspace it reads no workspace settings and starts no server. A user-level
    intersystems.servers entry wins over a workspace entry of the same name, and a server defined
    only in the workspace never gets a password Server Manager stored.

Setup skills

  • The setup skills bind published ports to 127.0.0.1 and set one generated _SYSTEM
    password.
    iris-agentic-dev-setup, nopws-setup, iris-vscode-objectscript,
    irishealth-container, iris-cpf-merge and iris-objectscript-eval, plus
    docs/getting-started.md, the README and the mcp --help text, no longer un-expire every
    account on SYS. The password goes to the container on stdin. The skills ask before they change
    an existing container. If you rely on a container published on all interfaces, publish it
    yourself.

Smaller behaviour changes

  • iris-agentic-dev query TSV output escapes backslashes and control characters.
  • iris_test_server refuses a host that is not a bare hostname or IP address, and does not
    follow redirects.
  • iris_info what=sa_schema refuses names with dot segments.
  • journal_search withholds values of blocklisted globals, and values of sensitive-named
    globals need acknowledgePhi.
  • iris_lookup_transfer import needs the destructive tier, because it replaces the whole table.
  • iris_coverage runs %UnitTest.TestProduction classes the way iris_test does.
  • On a server set to redact, iris_message_body and stream_inspect return HL7 v2 redacted and
    refuse other content with BODY_NOT_REDACTABLE. journal_search stays refused under redact.
    stream_inspect now answers to dataPolicy like iris_message_body does, and a refused stream
    still reports its class, size and line count.
  • iris_source_control Disconnect and Reconnect need their own opt-in,
    IRIS_SCM_ALLOW_DISCONNECT=1, and answer SCM_ADMIN_BLOCKED without it.
    IRIS_SCM_ALLOW_CHECKIN=1 opens CheckIn only. check_config reports both under scm_opt_ins.
  • iris_source_control execute with Diff or %Diff counts as a read. Every other menu id is a
    write.
  • stream_inspect returns at most max_chars characters (default 20,000, at most 1,000,000),
    with the whole size and truncated. It no longer reports type, because character and binary
    streams share storage and the class it opened said nothing about the content. An id with no
    stream is STREAM_NOT_FOUND.
  • iris_coverage refuses with MONITOR_IN_USE when a line monitor is already running, instead
    of stopping it; mode=stop still stops it. A class with no executable lines reports
    pct: null, not 0.0%, and a class the monitor does not know is COVERAGE_ROUTINE_UNKNOWN.
  • Telemetry goes to ~/.iris-agentic-dev/telemetry/<session>.jsonl on every call. Writing it to
    ^IRISDEV("telemetry") as well is an operator opt-in, telemetry_sink = "iris" in the operator
    file or IAD_TELEMETRY_SINK=iris, and then happens only on the call's server, with writes on,
    outside strict read-only and not on a subject server. Before, every call on a writable default
    server compiled a scratch class in USER to record itself. telemetry_query reads both places,
    and check_config reports telemetry_sink.
  • iris_coverage mode=check and iris_reload_pool count as writes. check starts and stops the
    line monitor, and a pool reload brings new servers into use. iris_reload_pool is in the
    admin category, like iris_add_server.
  • Over the HTTP transport each MCP session has its own log store, pending dialogs and call
    history, so one client can no longer read another's results with iris_get_log or
    agent_history.
  • The MCP client's name and version reach the User-Agent and the IRIS audit event with only
    letters, digits and ._+- kept (others become _), capped at 64 and 32 characters, so a client
    name cannot forge fields there. Visual Studio Code shows as Visual_Studio_Code.
  • iris_generate_class sends ANTHROPIC_API_KEY only for a Claude model and OPENAI_API_KEY
    only for any other model; it no longer sends one provider's key to the other's URL.
  • A response from IRIS larger than IAD_MAX_RESPONSE_BYTES (default 128 MiB) is
    RESPONSE_TOO_LARGE.
  • journal_search walks the current journal from its newest record back, so records come newest
    first and a record before start ends the walk. From the oldest record, a search for anything
    recent on a busy file stopped at the 200,000-record cap before reaching it.
  • iris_global get returns the stored value exactly. It used to drop \r and trim trailing
    whitespace.

What's new

  • iris-agentic-dev trust. It shows a file's loosening settings and what each changes now,
    asks you to type trust at a terminal, and writes a trust entry to the operator file with your
    comments intact. With no terminal it exits 2 and writes nothing. --show prints the same
    without writing, --list shows each entry as trusted, lapsed or missing, and --revoke <path>
    removes one. A terminal check proves there is a terminal, not that a person sits at it. An agent
    that can start a pseudo-terminal can pass it, so the boundary that holds is your approval of the
    shell command.
    No MCP tool writes trust.
  • The operator file. ~/.iris-agentic-dev.toml holds your policies, your trust entries and
    the operator-only settings, and it applies wherever you work.
  • Effective policy. check_config shows each server's policy and which file set each part.
    The parts combine like this: mcpTemplate takes the stricter of dev, test and live; allow is
    the categories both files list; globalBlocklist is every pattern from both; dataPolicy takes
    the strictest of block, redact and allow; irisAudit is on if either turns it on. A loosening
    part from an untrusted file is dropped on its own, and the rest of that file's policy still
    applies. A trusted file's part replaces the operator file's.
  • Refusal kinds. acknowledgement_required is lifted by repeating the call with confirm or
    acknowledgePhi. operator_limit is lifted by changing a setting in the operator file, launch
    settings or a trusted file. untrusted_setting is lifted by trusting the file. fixed_rule
    is not lifted, such as the system blocklist.
  • The set-aside list. check_config returns set_aside, one item per loosening setting from
    an untrusted file, per operator-only setting found in a workspace file, per credential withheld
    from a server, and per workspace server hidden by a known one. Each carries a stable id, its
    source file, the reason and what lifts it. A refusal points at the id. A file whose trust has
    lapsed lists what changed.
  • Service-account identity. Every tool that runs the agent's code returns
    identity {user, source, path, verified, server}, where user is what IRIS reports as
    $USERNAME. check_config shows the account each path would use, and makes one
    GET /api/atelier/ login probe that sends no code. iris_add_server takes service_username,
    service_password and container. On the docker paths, iad's first line drops the session to
    the account with $SYSTEM.Security.Login and ends the process if it cannot, so no password
    reaches the terminal.
  • Strict read-only. read_only = "strict" in any configuration file, or IAD_READ_ONLY=strict
    in launch settings, hides and refuses every tool that uses a scratch class (37 tools), closes the
    write and destructive gates on every server, answers READ_ONLY_STRICT, and keeps telemetry
    local. It only tightens, so a project file may set it.
  • HTTP transport token. IAD_HTTP_TOKEN, --auth-token-file and [transport] http_token,
    with --allowed-host and --allowed-origin. See docs/connecting.md.
  • Keychain storage, bound to the address. iad's own passwords sit under the
    iris-agentic-dev keychain service, keyed by the server's endpoint, so a password saved for one
    address is never sent to another. Files iad writes for secrets are created owner-only and
    replaced atomically.
  • Per-connection TLS. One client builder serves every IRIS-bound request. check_config
    reports each server's tls_verify and where it came from.
  • Passwords and search terms stay out of stored class source. Source-control calls, the
    iris_doc probe, iris_production_diff, credential and user management, and journal_search
    pass them as call arguments instead of splicing them into the class iad stores on the server.
  • A bounded journal walk. journal_search reads records one at a time and stops at 200,000
    records or 20 seconds. The answer carries scanned, truncated and truncated_reason
    (max_records, scan_cap or time_cap). A search over a large journal returns what it found
    with truncated: true and a hint to narrow start, end or global_pattern.

Setting up a service account

The account needs a database layout that keeps application code read-only while leaving the
compiler somewhere to write. This is the layout that works on IRIS 2026.2:

  1. Keep application code, application data and a scratch area in three databases.
  2. Create the agent's namespace with the scratch database as its default routines database
    and the application data database as its globals database.
  3. Map the application's packages and routines into the namespace from the read-only code
    database (Config.MapPackages for the package, Config.MapRoutines for its routines).
  4. Set each database's resource with SYS.Database.ResourceName. A new database starts as
    %DB_%DEFAULT, and a role cannot tell the three apart until you change it.
  5. Create a role with %Development:USE (to reach /api/atelier and the terminal),
    %DB_<code>:R, %DB_<data>:RW (or R) and %DB_<scratch>:RW.
  6. Create the user with password authentication, that role and nothing else.

Saving a class in a mapped application package then fails (ERROR #5883 over Atelier, <PROTECT>
in a terminal), and a class in any other package lands in the scratch database, where it can do
no more than run as the service account. Two layouts fail, and iad reports them rather than
falling back. An account with only read access to the namespace's databases gets
SERVICE_ACCOUNT_HTTP_DENIED: Atelier refuses it before anything runs. A read-only code database
as the default routines database, with only IrisDevTmp mapped, uploads the scratch class and
then fails to compile it (ERROR #302). %Development:USE also opens the debugger and the rest
of Atelier to that account. iad never hands the agent its credentials, and the database
permission still blocks code edits. The login guard was measured on 2026.2 only; other versions
are untested.

Fixes

  • iris_global get returns what was stored. Newlines, \r and trailing whitespace come back
    exactly, for single values and subtrees. iris_global set keeps a value on one source line, so a
    newline in value is stored, where before it broke the call.
  • journal_search no longer floods messages.log. It used %SYS.Journal.File:Search, which
    logs one line per journal record it visits. On a busy instance one search wrote about 500,000
    lines, messages.log rolled over and the container's iris-main exited. The walk above logs
    nothing. A search over a very large current journal also reports a timeout with the file size
    and a narrower-window hint, not IRIS_UNREACHABLE.
  • Docker-only iris_compile compiles classes. It passed X.cls to $SYSTEM.OBJ.Compile and
    failed with ERROR #5351. It now strips the suffix, and passes documents to CompileList.
  • HL7 redaction holds across line endings and padding, and masks more. iris_message_body
    and stream_inspect redact whether segments end in \r, \n or \r\n, and when a segment
    name has leading whitespace or the MLLP bytes \x0b and \x1c before it, which used to let the
    segment through unmasked. The mask now covers the identifying, contact and account fields of PID,
    NK1, GT1, IN1 and IN2, and MSH-3; docs/tools.md lists them. An empty field stays empty. PV1,
    OBX, NTE and free text come back as stored, so redacted: true means the mask ran, not that the
    message is de-identified. A near-miss dataPolicy value such as Allow or "allow " never
    returns the raw body.
  • BPL parsing has a depth limit. A deeply nested definition returns BPL_TOO_DEEP and no
    longer overflows the stack.
  • iris_info what=sa_schema stays inside the Studio Assist path. A name with .. segments is
    refused.
  • iris_test_server does not take a URL as a host. A host with URL syntax is refused, and
    redirects are not followed.
  • iris-agentic-dev compile --flags and TSV output escape what they print. A flag outside
    the qualifier character set is refused before IRIS sees it, and every control byte, tab and
    backslash round-trips through TSV.
  • iris_coverage shares iris_test's dispatch. It runs the same gated builder, so it follows
    the same policy and finds %UnitTest.TestProduction classes.
  • iris_doc put with compile=true checks the stored includes. It did not.
  • The SQL checks follow IRIS's lexing and no longer panic on non-ASCII text. They used byte
    offsets on character text, so an accented letter could crash the check. One lexer now reads
    literals ('' doubling, a backslash is an ordinary character), delimited identifiers ("" doubling)
    and comments as IRIS does. The DELETE row pre-check reads the table after DELETE FROM, and a
    statement led by a comment or a newline no longer skips the max_rows_affected check.
  • iris_table_info splits a name on unquoted dots only.
  • iris_coverage counts lines that ran. It read the second element of a monitor row, which is
    time, as the execution count, so a method that ran three lines reported one. It now monitors
    every routine a class compiles to, not only Pkg.Cls.1, and stops the monitor on every exit
    path.
  • iris_doc delete reports a delete IRIS refused. A locked document or a source-control
    class that refuses in OnBeforeDelete answers HTTP 200 with the reason in the body. Single and
    batch delete now return DELETE_FAILED or an errors entry instead of success: true.
  • A scratch class IRIS would not delete is reported. The cleanup read only the HTTP status, so
    a refused delete left the class behind silently. The call's own result stands and carries a
    warnings entry naming the class.
  • Lookup tables read empty values and report absent keys. iris_lookup_manage get of a key
    whose value is empty returns the empty value, not KEY_NOT_FOUND, and delete of an absent key
    returns KEY_NOT_FOUND, not IRIS's raw error text.
  • iris_admin journal_search advertises acknowledgePhi. The schema left the parameter
    out, so a client that validated arguments against it could not pass it.
  • Discovery reports what it skipped. A listener or container it did not send credentials to
    appears under discovery.skipped.

v1.6.4...v1.7.0