Skip to content
Ironflow is pre-1.0: APIs can change between releases. Pin a version. What is ready →

Reserved Names

Ironflow owns a few name roots. This page lists each root and the rule for it. A rejected write fails with an error. Ironflow never ignores it silently.

Names in an Ironflow-owned namespace have meanings that Ironflow defines. A minor release may add names to these namespaces and may restrict who can write them. Using an owned namespace for your own data is unsupported.

Loosening a rule is never a breaking change. Tightening a rule inside an owned root is allowed in a minor release. See ADR 0128.

The rule applies when you create, register or emit a name. Two surfaces also check reads: an entity stream ID (stream reads, snapshots and info) and a KV bucket name (get, list and delete). Other reads and subscriptions are not restricted.

SurfaceRootRule
event-nameironflow.Rejected, except the writable defined name ironflow.invoke.<fn> on Emit, Trigger, TriggerSync and TriggerBatch, and only with functions:invoke. NATS ingest rejects it.
event-name$Rejected. Entity tombstones use this root.
webhook-event-prefixironflow.Rejected when the prefix plus a dot starts with the root, so ironflow alone is rejected too.
webhook-event-prefix$Rejected when the prefix starts with the root.
entity-idironflow:Rejected. The match ignores case.
kv-bucketSYS_Create returns 400. Other routes return 404. Delete returns 403.
kv-bucketENV_Create returns 400. Other routes return 404.
kv-bucketconfig_env_Create returns 400. Other routes return 404.
kv-bucketMOD_Create returns 400. Other routes return 404.
projection-nameironflow_Rejected before the definition is stored.
pubsub-topicevents:Publish rejected.
pubsub-topicsystem.Publish rejected.
pubsub-topicentity:Publish rejected.
pubsub-topicpublic.Publish rejected.
pubsub-topictopic:Publish rejected.
rego-packagedata.ironflowRejected, except ironflow.authz and ironflow.authz_test.
environment-namecurrentRejected on create and rename. The rule matches the whole name, not a prefix.

Event roots, MOD_ and ironflow_ match exact case, because event names, NATS subjects and KV bucket names are case-sensitive. Entity IDs match without regard to case.

Event schema registration rejects every event name that a user cannot emit. A schema never applies to a platform event, so Ironflow rejects it instead of accepting it silently. The writable defined name ironflow.invoke.<fn> still takes a schema, because Emit enforces schemas on it.

  • Event names, webhook prefixes, event schemas, projection names, pub/sub topics: ConnectRPC invalid_argument.
  • Entity IDs: ConnectRPC permission_denied.
  • KV buckets: HTTP 400, 404 or 403, as the table shows.
  • Rego packages: error code policy_set_reserved_namespace.
  • Environment names: HTTP 400, or invalid_argument on ConnectRPC.

Two JSON keys inside a payload are Ironflow-owned too (ADR 0133).

SurfaceRootRule
payload-marker$encAn object whose only member is $enc, at any depth. Reserved by contract. No door rejects it in 1.0.
payload-marker$redactedA top-level member. Rejected on every payload write that a user or worker makes.

Ironflow writes $redacted when it redacts a payload (RedactEvent, RedactStep, RedactRun). The check reads only the top level, so a nested $redacted passes. These writes reject a payload with a top-level $redacted member, whatever its value:

  • The event doors: Trigger, Emit, TriggerSync, TriggerBatch, PubSubService.Emit, EntityStreamService.AppendEvent, webhook ingest and NATS ingest.
  • InvokeFunction, InvokeFunctionSync and the input of a child invoke step.
  • PatchStep and InjectStepOutput.
  • A step output, run result or waitForEvent payload that a worker sends, in push and pull mode.
  • A step error that a REST pull worker sends. It fails the run with RESERVED_PAYLOAD_MARKER; a checkpoint or yield that carries one also answers HTTP 400.

The RPCs answer ConnectRPC invalid_argument. Webhook ingest answers HTTP 400. NATS ingest terminates the message. A worker result fails the run with the code RESERVED_PAYLOAD_MARKER, and the run is not retried. A child invoke input fails the invoke step. For invoke, the parent run fails too. For invokeAsync, the parent run continues. A waitForEvent payload is rejected as an invalid yield, and the run fails with YIELD_REJECTED in push and pull mode.

Redacted data cannot come back as output. A function whose input was redacted and that then returns a top-level $redacted member, for example by spreading event.data into its result after a resume, fails with RESERVED_PAYLOAD_MARKER.

A later release gives $enc a meaning. Data that uses it is unsupported.

Every event name that starts with ironflow. is one of three kinds.

Writable defined name. Your code can emit this name through Emit, Trigger, TriggerSync and TriggerBatch. A non-empty suffix must follow the prefix. The suffix can contain dots. The caller needs functions:invoke on function <fn>, as for InvokeFunction; without it the emit returns 403. The event starts only function <fn>. A trigger on any other function does not match it, a wildcard such as > included. A NATS publisher to events.ingest.> cannot emit this name, and a webhook cannot either.

  • ironflow.invoke.<fn>: the event that ironflow invoke emits for function <fn>.

Platform-only names. Only Ironflow writes these. A client emit is rejected.

  • ironflow.file.* and ironflow.bucket.*: file and bucket events.
  • ironflow.cron.<fn>: a cron schedule fired for function <fn>.
  • ironflow.function.invoked: a function was invoked.

Every other name. No rule defines it, so a client emit is rejected. A later release can define it.

Webhook ingest and entity stream appends get no exceptions. They reject every owned name, the writable defined name included. An entity append wakes waiting runs too, so entities:append alone cannot start an invoke.

  • A function trigger can match an owned name, for example ironflow.file.* or ironflow.invoke.<fn>.
  • You can subscribe to and read owned events.
  • Reading an entity stream with an ironflow: ID is still rejected.

Ironflow runs these checks only on user calls: create, register, emit, and the entity stream and KV bucket reads. Server start and rebuild never run them. A stored name under a new root cannot block startup. Ironflow does not migrate old names.

  • A stored projection named ironflow_* stays. Its next re-register fails.
  • A KV bucket named MOD_* stays. Every route except create returns 404, like the other reserved prefixes.
  • A webhook source whose event_prefix is under an owned root stays. A delivery fails with 400 when its event name starts under an owned root. For the prefix ironflow alone, that means a delivery that carries an event type, because a delivery with no type uses the bare name ironflow.
  • An environment named current stays. A PUT that keeps the name current fails until you rename the environment.
  • A stored payload with a top-level $redacted member stays. Only new writes are rejected.
  • Stored events keep their old names. The cron and invoke events moved from ironflow/cron.<fn> and ironflow/function.invoked to the dotted names above. The old names stay in history.