fcode-factorial-actions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Factorial Code — Factorial Actions

Factorial Code — Factorial Actions

A Factorial Action is a process that the customers who installed an App reach from inside Factorial, without knowing Factorial Code exists. One process is one action, and the same action can be exposed through several entry points at once — a button in the Factorial UI, a form, a Factorial One tool, a backend job. Whatever the entry point, Factorial sends the process its parameters and, when asked to, waits for the result; the process does not know which entry point started it, so one implementation serves all of them.
Everything an action needs lives in one settings block,
factorial
: the Trigger from Factorial section of the process page in the console, or the
factorial
key of
processes/<slug>/metadata.json
(round-tripped by
fcode pull
/
fcode push
, field reference in
fcode-cli
).
Factorial Action 是安装 App 的客户可在 Factorial 内部使用的一种流程,而客户甚至无需知道 Factorial Code 的存在。一个流程对应一个 Action,同一个 Action 也可以同时通过多个 入口点 对外暴露,例如 Factorial UI 中的按钮、表单、Factorial One 工具或后端任务。无论从哪个入口点进入,Factorial 都会向流程发送参数,并按要求等待结果;流程并不知道是哪个入口点启动了它,因此同一套实现可服务所有入口点。
Action 所需的所有配置都位于一个设置块
factorial
中:控制台流程页面的 从 Factorial 触发 区域,或
processes/<slug>/metadata.json
的
factorial
键(
fcode pull
/
fcode push
可对其进行往返同步,字段参考见
fcode-cli
)。

What is live today and what is not

当前支持的功能与尚未支持的功能

Available nowStill to come
The
factorial
block in the console, in
metadata.json
, and as
settings.factorial
in the API
Factorial One tools: the assistant reading the
agentTool
contract and calling the process
The Actions API Factorial invokes through: list a company's actions, read an action's form schema, invoke it, issue upload grants, and list an app's declared actions before it is installedEnforcing
form.public
; retiring what is left of
form.appRole
requiredPolicies
enforced
on every invocation, before anything runs
uiTrigger
: the button lists and runs over this contract, addressed by an opaque
action_id
— no process slug reaches the browser
form
: a form opened from Factorial submits through Factorial's backend, which carries the user's identity and injects the trusted context
File parameters (
x-fcode-file
) uploaded straight to Factorial Code storage with a grant, the submission carrying the
fcode.storage://
path
uiTrigger.label
localized with
fcode.i18n("key")
tokens on the
?locale=
listing
lifecycleRole
derived from the reserved slugs and tagged in the console
So today
uiTrigger
,
form
,
requiredPolicies
and
awaitResult
all do something.
agentTool
and
backend
are stored and reachable, but no assistant reads a tool contract yet — fill them in now so the action is ready when it does.
当前可用即将推出
控制台、
metadata.json
以及 API 中的
factorial
块(
settings.factorial
)
Factorial One 工具:助手读取
agentTool
契约并调用流程
Factorial 调用的 Actions API:列出公司的 Action、读取 Action 的表单 schema、调用 Action、签发上传授权,以及在 App 安装前列出该 App 声明的 Action强制执行
form.public
;淘汰
form.appRole
的剩余部分
每次调用时,在任何内容运行之前强制执行
requiredPolicies
uiTrigger
:按钮依据此契约展示并运行,以不透明的
action_id
寻址——浏览器不会收到 process slug
form
:从 Factorial 打开的表单通过 Factorial 的后端提交,后端会携带用户身份并注入可信上下文
文件参数(
x-fcode-file
)凭上传授权直接上传到 Factorial Code 存储,提交时携带
fcode.storage://
路径
uiTrigger.label
在带
?locale=
的列表中通过
fcode.i18n("key")
token 进行本地化
lifecycleRole
根据保留的 slug 派生,并在控制台中显示相应标签
因此,目前
uiTrigger
、
form
、
requiredPolicies
和
awaitResult
都会实际生效。
agentTool
和
backend
会被存储且可以访问,但目前还没有助手读取工具契约——现在先填写好,等助手支持时即可直接使用。

Gotchas

常见陷阱

  • enabled
    is a master switch.
    While it is
    false
    nothing below is exposed, whatever each entry point's own
    enabled
    says. Turning it back on restores the entry points exactly as they were.
  • awaitResult
    defaults to
    true
    (synchronous).
    The legacy
    uiTrigger
    defaulted to fire-and-forget. The mirror copies
    factorial.awaitResult
    into
    uiTrigger.awaitResult
    , so a button moved to the
    factorial
    block becomes synchronous unless you write
    "awaitResult": false
    . Keep synchronous actions under the ~50 s budget (
    fcode-ui-triggers
    ).
  • requiredPolicies
    are enforced, but the keys are free text.
    Factorial checks them before it runs anything. It does not check that a key exists: one it does not recognise is simply never granted, so the whole AND-group containing it fails — a typo can only close an action, never open one. Take keys from Factorial's policy catalogue; prefer several small alternatives over one long group.
  • No policies means open. An empty list, or no list at all, lets anyone who can reach the entry point run the action — the entry point's own gate (a button's location permissions, an installed app) is all that remains. An empty group inside a non-empty list is dropped rather than treated as "allow all", so
    [["a"], []]
    still requires
    a
    .
  • Only
    uiTrigger.label
    is translatable.
    It is the one text users see, so it takes
    fcode.i18n("key")
    tokens (
    fcode-i18n
    ). The
    agentTool
    texts are for the model — plain text, one language, never shown to users. The process's own
    name
    /
    description
    serve humans and the marketplace card; the block has no title of its own.
  • An unset
    effect
    is treated as
    DESTRUCTIVE
    .
    Every tool fails closed (approval + "cannot be undone" warning) until you say
    READ
    or
    WRITE
    .
  • The four lifecycle slugs are reserved.
    install
    ,
    settings
    ,
    uninstall
    ,
    sync
    mean something to Factorial regardless of the block. Do not give an ordinary action one of them.
  • Omit the defaults.
    fcode pull
    leaves out
    awaitResult
    when
    true
    ,
    requiredPolicies
    when empty,
    form.public
    and
    form.appTool
    when
    false
    , unset
    agentTool
    texts and empty lists, and any sub-block whose
    enabled
    is
    false
    . A process not exposed to Factorial has no
    factorial
    key at all. A partial update leaves the unnamed fields unchanged.
  • Legacy
    form
    /
    uiTrigger
    keys still round-trip.
    When a file carries both,
    fcode push
    sends
    factorial
    and lets the mirror update the rest. Prefer writing
    factorial
    (below).
  • enabled
    是总开关。
    当它为
    false
    时,无论下方各入口点自己的
    enabled
    设置为何,都不会暴露任何内容。重新启用后,这些入口点会按原配置完全恢复。
  • awaitResult
    默认为
    true
    (同步)。
    旧版
    uiTrigger
    默认采用触发后不等待结果的方式。镜像机制会把
    factorial.awaitResult
    复制到
    uiTrigger.awaitResult
    ,因此迁移到
    factorial
    块的按钮除非显式写入
    "awaitResult": false
    ,否则都会变为同步。同步 Action 的执行时间应控制在约 50 秒的预算内(
    fcode-ui-triggers
    )。
  • requiredPolicies
    会强制执行,但键名可以自由填写。
    Factorial 会在运行任何内容之前检查它们。它不会检查某个键是否存在:无法识别的键永远不会被授予,因此包含该键的整个 AND 组都会失败——一个拼写错误只会导致 Action 被拒绝,绝不会导致其被放行。键名应取自 Factorial 的策略目录;相较于一个冗长的组,更应优先使用多个较小的备选组。
  • 没有策略意味着开放。 空列表或完全没有列表时,任何能访问该入口点的人都可以运行 Action——入口点自身的限制(例如按钮所在位置的权限、App 的安装状态)是唯一剩余门槛。非空列表中的空组会被丢弃,而不会被视为“允许所有”,所以
    [["a"], []]
    仍然要求
    a
    。
  • 只有
    uiTrigger.label
    可以翻译。
    它是用户唯一会看到的文本,因此可使用
    fcode.i18n("key")
    token(
    fcode-i18n
    )。
    agentTool
    中的文本面向模型——使用纯文本、只采用一种语言,绝不会展示给用户。流程自身的
    name
    /
    description
    供用户阅读并用于 marketplace 卡片;此块本身没有标题。
  • 未设置
    effect
    时会按
    DESTRUCTIVE
    处理。
    在明确指定
    READ
    或
    WRITE
    之前,所有工具都会要求审批并显示“无法撤销”警告,也就是默认采取保守拒绝策略。
  • 四个生命周期 slug 是保留的。 无论配置块内容如何,
    install
    、
    settings
    、
    uninstall
    、
    sync
    对 Factorial 都有特定含义。不要为普通 Action 使用其中任何一个。
  • 省略默认值。 当
    awaitResult
    为
    true
    、
    requiredPolicies
    为空、
    form.public
    和
    form.appTool
    为
    false
    、
    agentTool
    文本未设置或列表为空,以及任意子块的
    enabled
    为
    false
    时,
    fcode pull
    都不会输出这些字段。未向 Factorial 暴露的流程完全没有
    factorial
    键。局部更新会保持未指定字段不变。
  • 旧版
    form
    /
    uiTrigger
    键仍可往返读写。
    当一个文件同时包含两者时,
    fcode push
    会发送
    factorial
    ,并让镜像机制更新其余部分。请优先编写
    factorial
    (见下文)。

The
factorial
block, field by field

factorial
块:逐字段说明

json
{
  "factorial": {
    "enabled": true,
    "awaitResult": false,
    "requiredPolicies": [["company.manage_timeoff"], ["company.admin"]],
    "uiTrigger": { "enabled": true, "locationId": "calendar.header.admin", "label": "fcode.i18n(\"acme.approve.button\")", "icon": "Bell" },
    "form": { "enabled": true, "public": false, "appTool": true },
    "agentTool": { "enabled": true, "description": "…", "effect": "WRITE" },
    "backend": { "enabled": true }
  }
}
FieldType / defaultMeaning
enabled
boolean,
false
Master switch for every entry point below
awaitResult
boolean,
true
true
: Factorial waits and receives the process's return value unchanged (
fcode-ui-triggers
).
false
: Factorial only learns the execution started; the process reports back itself (FactorialClient, notification). Use
false
for imports and bulk work
requiredPolicies
string[][]
,
[]
OR of AND-groups of Factorial policy keys the user must hold:
[["a","b"],["c"]]
reads (a and b) or c. Empty = anyone who reaches the entry point. Console: one alternative per line, commas between the keys of a group
uiTrigger.enabled
booleanRender the action as a button inside Factorial
uiTrigger.locationId
stringFactorial UI location the button renders at (e.g.
calendar.header.admin
). Required while enabled, ≤ 200 chars, given by the Factorial team owning the page — see
fcode-ui-triggers
for the location contract
uiTrigger.label
stringButton text. The only i18n field: plain text or
fcode.i18n("key")
tokens
uiTrigger.icon
stringAllowlisted icon name (
fcode-ui-triggers
); omit for a text-only button
form.enabled
booleanExpose the parameters as a form (
fcode-forms
). Same switch and same quota as the Forms flag
form.appTool
boolean,
false
Offer the form to the company's users on the App's own page in Factorial, among the things the App lets them run — a "Report a sync issue" or "Sync now" form. Leave it
false
for a form the App opens itself. This is the block's name for the legacy
form.appRole: USER_FACING_FORM
, which it is kept mirrored with
form.public
boolean,
false
Deprecated. Declares the form may also be embedded anonymously outside Factorial, as the legacy embed does. Stored, not enforced yet; set it only on forms that genuinely need anonymous access
agentTool.enabled
booleanLet the Factorial One assistant call the process as a tool. The rest of the sub-block is the tool contract (next section)
backend.enabled
booleanLet Factorial backend jobs run the process with no user in front of it
json
{
  "factorial": {
    "enabled": true,
    "awaitResult": false,
    "requiredPolicies": [["company.manage_timeoff"], ["company.admin"]],
    "uiTrigger": { "enabled": true, "locationId": "calendar.header.admin", "label": "fcode.i18n(\"acme.approve.button\")", "icon": "Bell" },
    "form": { "enabled": true, "public": false, "appTool": true },
    "agentTool": { "enabled": true, "description": "…", "effect": "WRITE" },
    "backend": { "enabled": true }
  }
}
字段类型 / 默认值含义
enabled
boolean,
false
下方所有入口点的总开关
awaitResult
boolean,
true
true
:Factorial 等待并原样接收流程的返回值(
fcode-ui-triggers
)。
false
:Factorial 只获知执行已启动;流程自行回传结果(FactorialClient、notification)。导入和批量工作应使用
false
requiredPolicies
string[][]
,
[]
用户必须持有的 Factorial 策略键构成 AND 组的 OR 组合:
[["a","b"],["c"]]
表示 (a 且 b) 或 c。空 = 任何能访问入口点的人。控制台:每行一个备选组,组内各键之间用逗号分隔
uiTrigger.enabled
boolean在 Factorial 内将 Action 渲染为按钮
uiTrigger.locationId
string按钮渲染所在的 Factorial UI 位置(例如
calendar.header.admin
)。启用时必填,≤ 200 个字符,由负责该页面的 Factorial 团队提供——位置契约参见
fcode-ui-triggers
uiTrigger.label
string按钮文本。唯一支持 i18n 的字段:纯文本或
fcode.i18n("key")
token
uiTrigger.icon
string允许列表中的图标名称(
fcode-ui-triggers
);仅文本按钮应省略
form.enabled
boolean将参数公开为表单(
fcode-forms
)。与 Forms 标志使用同一个开关和同一配额
form.appTool
boolean,
false
在 Factorial 中 App 自己的页面上向公司的用户提供该表单,并将其列在 App 允许用户运行的内容中,例如“报告同步问题”或“立即同步”表单。对于由 App 自行打开的表单,应保持
false
。这是旧版
form.appRole: USER_FACING_FORM
在此块中的对应项,并会与其保持镜像同步
form.public
boolean,
false
已弃用。 声明该表单还可以像旧版嵌入方式那样,在 Factorial 外部以匿名方式嵌入。该值会被存储,但目前尚未强制执行;仅当表单确实需要匿名访问时才设置
agentTool.enabled
boolean允许 Factorial One 助手将流程作为工具调用。该子块的其余字段构成工具契约(见下一节)
backend.enabled
boolean允许 Factorial 后端任务在没有前台用户的情况下运行流程

The Factorial One tool contract (
agentTool
)

Factorial One 工具契约(
agentTool
)

The parameters schema tells the assistant what the tool takes; this block tells it what the tool is for. Plain text, no i18n.
FieldTypeWhat it buys the model
description
stringWhat the tool does, one or two sentences: name the object it acts on and the outcome. This is what user requests are matched against
effect
READ
|
WRITE
|
DESTRUCTIVE
READ
runs without asking;
WRITE
asks for approval;
DESTRUCTIVE
asks and warns it cannot be undone. Unset ⇒
DESTRUCTIVE
whenToUse
stringThe situation the tool is meant for, phrased as a user would ask
whenNotToUse
string[]Look-alike situations that are not a match, one per entry — name the neighbour tool to use instead
preconditions
string[]What must already be true before calling, one per entry; the assistant checks or asks instead of calling and failing
doesNotDo
string[]Side effects a user might assume and the tool does not have, one per entry
degradation
stringWhat happens on partial success: what is done, what is skipped, how the result says so
simulatesFor
stringSlug of the process this one previews (a dry run of it). Free text, not validated yet
参数 schema 告诉助手工具接收什么参数;此块则告诉助手工具的用途。使用纯文本,不使用 i18n。
字段类型为模型提供的信息
description
string工具的作用,用一两句话说明:指出其操作的对象及产生的结果。用户请求会据此进行匹配
effect
READ
|
WRITE
|
DESTRUCTIVE
READ
无需询问即可运行;
WRITE
会请求审批;
DESTRUCTIVE
会请求审批并警告操作无法撤销。未设置 ⇒
DESTRUCTIVE
whenToUse
string该工具适用的场景,表述方式应贴近用户提出请求时的说法
whenNotToUse
string[]看似相似但不适用的场景,每项一个——并指出应改用的相邻工具
preconditions
string[]调用前必须已经成立的条件,每项一个;助手应检查或询问,而不是调用后失败
doesNotDo
string[]用户可能认为会发生、但工具实际不会产生的副作用,每项一个
degradation
string部分成功时会发生什么:哪些已完成、哪些被跳过,以及结果如何体现
simulatesFor
string此工具所预览的流程 slug(即对其执行 dry run)。目前是自由文本,尚未校验

Writing good contract texts

编写高质量契约文本

  • Plain language, no i18n, one language. Write for a model reading English, not for a screen. Never put
    fcode.i18n(...)
    tokens here.
  • description
    : object + outcome.
    "Approves every pending time-off request of a team" beats "Time-off tool".
  • One situation in
    whenToUse
    .
    If you need "or", you probably have two tools. Phrase it the way the user would ask.
  • Name neighbour tools in
    whenNotToUse
    .
    "The user wants a single request approved: use
    approve-timeoff-request
    ." Each entry is one situation.
  • preconditions
    are checkable facts
    ("the team has at least one pending request"), not advice.
  • doesNotDo
    lists what a user would assume.
    Notifications, side writes, undo — say what is not happening so the assistant can offer the missing step.
  • degradation
    describes the partial outcome
    , so the assistant can explain it instead of reporting a failure.
  • A dry-run process points at its real one with
    simulatesFor
    and is itself
    READ
    ; the real one is
    WRITE
    or
    DESTRUCTIVE
    .
  • 使用平实语言,不使用 i18n,只采用一种语言。 应面向阅读英文的模型来写,而不是面向界面。绝不要在这里使用
    fcode.i18n(...)
    token。
  • description
    :对象 + 结果。
    “批准团队中所有待处理的请假申请”优于“请假工具”。
  • whenToUse
    只描述一种场景。
    如果需要“或”,大概说明应该拆成两个工具。表述方式应贴近用户提出请求时的说法。
  • 在
    whenNotToUse
    中指明相邻工具。
    “用户只想批准单项申请:请使用
    approve-timeoff-request
    。”每项对应一种场景。
  • preconditions
    是可核查的事实
    (“团队至少有一个待处理申请”),而不是建议。
  • doesNotDo
    列出用户可能认为会发生的操作。
    通知、附带写入、撤销等——应明确说明这些操作不会发生,以便助手提供缺失步骤。
  • degradation
    描述部分执行结果
    ,让助手可以解释该结果,而不是将其报告为失败。
  • Dry run 流程通过
    simulatesFor
    指向其正式流程
    ,且自身应为
    READ
    ;正式流程则为
    WRITE
    或
    DESTRUCTIVE
    。

Reserved lifecycle slugs

保留的生命周期 slug

SlugRoleWhat the platform does
install
The form Factorial shows when a company installs the AppDerives
lifecycleRole: INSTALL
settings
The form an installed company uses to configure the App
SETTINGS
uninstall
Runs when the company removes the App
UNINSTALL
sync
The sync process of an integrations-framework App
SYNC
lifecycleRole
is read-only and derived from the slug: the console shows a tinted Install / Settings / Uninstall / Sync tag in the processes list and the process header, the CLI and API report it, nothing ever writes it (it is not in
metadata.json
and not in the content hash).
install
,
settings
and
uninstall
are the marketplace lifecycle forms; they still take
factorial.form.enabled
like any form. The pre-render pattern for re-opened install/settings forms is in
fcode-forms
.
Slug角色平台执行的操作
install
公司安装 App 时 Factorial 显示的表单派生
lifecycleRole: INSTALL
settings
已安装该 App 的公司用来配置 App 的表单
SETTINGS
uninstall
公司移除 App 时运行
UNINSTALL
sync
integrations-framework App 的同步流程
SYNC
lifecycleRole
是只读字段,由 slug 派生:控制台会在流程列表和流程标题中显示带底色的 Install / Settings / Uninstall / Sync 标签,CLI 和 API 也会报告它;任何流程都不会写入该字段(它既不在
metadata.json
中,也不计入内容哈希)。
install
、
settings
和
uninstall
是 marketplace 生命周期表单;它们仍需像其他表单一样设置
factorial.form.enabled
。重新打开 install/settings 表单时所用的预渲染模式见
fcode-forms
。

Parameters: the contract with Factorial

参数:与 Factorial 之间的契约

Whatever the entry point, what Factorial sends is described by the process's
parametersSchema.json
(
fcode-json-schema
). The form renders it, the assistant reads it to know what the tool takes, Factorial validates against it. Give every parameter a
title
and a
description
, list the mandatory ones in
required
, and treat any schema change as a change to what Factorial may call you with. Trusted keys a UI trigger injects (
company_id
,
triggered_from_location
,
access_id
) are described in
fcode-ui-triggers
.
无论使用哪个入口点,Factorial 发送的内容都由流程的
parametersSchema.json
(
fcode-json-schema
)描述。表单根据它渲染,助手读取它以了解工具接受哪些参数,Factorial 则依据它进行校验。应为每个参数提供
title
和
description
,在
required
中列出必填项,并将任何 schema 变更都视为 Factorial 调用流程时可以传入的参数范围发生变更。UI trigger 注入的可信键(
company_id
、
triggered_from_location
、
access_id
)在
fcode-ui-triggers
中有说明。

The transition from
form
and
uiTrigger

从
form
和
uiTrigger
迁移

The
factorial
block supersedes the Forms flag (
form
) and the UI trigger button (
uiTrigger
). Both stay for now and the platform keeps them mirrored: whichever side you write, the other is updated, last write wins.
Writing
factorial
…
…updates the legacy setting
enabled && uiTrigger.enabled
uiTrigger.enabled
uiTrigger.locationId
,
uiTrigger.icon
,
uiTrigger.label
the same keys of
uiTrigger
awaitResult
uiTrigger.awaitResult
enabled && form.enabled
form.enabled
form.appTool
form.appRole
=
USER_FACING_FORM
Writing the legacy blocks mirrors the same fields back, and
factorial.enabled
turns on as soon as any entry point is enabled.
form.appRole
is the one legacy field the block partly owns. Of its four values,
INSTALL
/
SETTINGS
/
UNINSTALL
come from the reserved slugs and are left untouched, while
USER_FACING_FORM
is
form.appTool
— turning the flag on claims that value, turning it off releases it only if it was held, and a write that says nothing about
appTool
leaves the role alone. So a file written before the flag existed keeps its role on push: the CLI reads
appRole
and sends the flag to match.
Prefer
factorial
: write it in new processes, and when touching an old
metadata.json
, move the settings into it rather than editing
uiTrigger
.
factorial
块取代了 Forms 标志(
form
)和 UI trigger 按钮(
uiTrigger
)。目前两者仍会保留,平台会让它们保持镜像同步:无论写入哪一侧,另一侧都会更新,以最后一次写入为准。
写入
factorial
……
会更新旧版设置
enabled && uiTrigger.enabled
uiTrigger.enabled
uiTrigger.locationId
、
uiTrigger.icon
、
uiTrigger.label
uiTrigger
中对应的同名键
awaitResult
uiTrigger.awaitResult
enabled && form.enabled
form.enabled
form.appTool
form.appRole
=
USER_FACING_FORM
写入旧版块时,同样的字段会镜像回
factorial
;而只要任一入口点已启用,
factorial.enabled
就会开启。
form.appRole
是旧版字段中唯一有一部分由此块管理的字段。在它的四个取值中,
INSTALL
/
SETTINGS
/
UNINSTALL
来自保留的 slug,不会被修改,而
USER_FACING_FORM
对应
form.appTool
——开启该标志会取得这个角色值,关闭时只有在此前确实由
form.appTool
持有该角色的情况下才会将其释放;如果一次写入未涉及
appTool
,角色则保持不变。因此,在该标志出现之前编写的文件在 push 后仍会保留其角色:CLI 会读取
appRole
,并发送相应标志以保持一致。
优先使用
factorial
:新流程应直接使用它;修改旧
metadata.json
时,也应将设置迁移到该块,而不是编辑
uiTrigger
。

Worked example — SILTRA FIE import (dry run + apply)

实战示例——SILTRA FIE 导入(dry run + apply)

Adapted from the
siltra-integration-app
:
fie-import
reads the
.msj
file a company downloads from SILTRA and reports which sick leaves it would create, link or close in Factorial, writing nothing;
fie-import-apply
does the writing, in the background.
processes/fie-import/metadata.json
:
json
{
  "name": "FIE Import",
  "description": "Reads the .msj file the company downloads from SILTRA and shows what importing it would change in Factorial. Writes nothing — the import only happens after confirmation.",
  "tags": ["inbound", "siltra", "fie"],
  "factorial": {
    "enabled": true,
    "requiredPolicies": [["company.manage_timeoff"], ["company.admin"]],
    "uiTrigger": {
      "enabled": true,
      "locationId": "calendar.header.admin",
      "label": "fcode.i18n(\"calendar.import.fie.button\")",
      "icon": "Upload"
    },
    "form": { "enabled": true },
    "agentTool": {
      "enabled": true,
      "description": "Reads a SILTRA FIE transmission (.msj) and reports which sick leaves it would create, link or close in Factorial, and which events it would leave out and why. It writes nothing.",
      "effect": "READ",
      "whenToUse": "The user has downloaded a FIE file from SILTRA and wants to know what importing it would change before anything is written.",
      "whenNotToUse": [
        "The user has already reviewed the report and wants the leaves written: use fie-import-apply.",
        "The file is not a SILTRA .msj transmission."
      ],
      "preconditions": ["The .msj file has been uploaded and its storage reference is passed as msj_file."],
      "doesNotDo": ["It does not create, change or close any leave.", "It does not notify employees or managers."],
      "degradation": "If Factorial cannot be consulted, the report still lists every event in the file, marked as not compared against Factorial.",
      "simulatesFor": "fie-import-apply"
    }
  }
}
processes/fie-import-apply/metadata.json
— no button, no form, asynchronous, and the write side of the pair:
json
{
  "name": "FIE Import — apply",
  "tags": ["inbound", "siltra", "fie"],
  "factorial": {
    "enabled": true,
    "awaitResult": false,
    "requiredPolicies": [["company.manage_timeoff"], ["company.admin"]],
    "agentTool": {
      "enabled": true,
      "description": "Writes the sick leaves a SILTRA FIE transmission describes into Factorial: creates, links or closes them.",
      "effect": "WRITE",
      "whenToUse": "The user has seen the fie-import report for a file and asks to apply it.",
      "whenNotToUse": ["The user only wants to know what the file would change: use fie-import."],
      "preconditions": ["fie-import has reported on this same file and the user confirmed the result."],
      "doesNotDo": ["It does not send SILTRA anything back.", "It does not notify employees."],
      "degradation": "Leaves that fail to write are skipped and listed in the result; the rest are applied."
    },
    "backend": { "enabled": true }
  }
}
The policy keys are illustrative — take the real ones from Factorial's policy catalogue. Read the parameters as usual (
fcode.context.parameters.msj_file
, an
fcode.storage://…
reference —
fcode-forms
).
改编自
siltra-integration-app
:
fie-import
读取公司从 SILTRA 下载的
.msj
文件,并报告它将在 Factorial 中创建、关联或关闭哪些病假记录,且不会写入任何数据;
fie-import-apply
则负责在后台执行写入。
processes/fie-import/metadata.json
:
json
{
  "name": "FIE Import",
  "description": "Reads the .msj file the company downloads from SILTRA and shows what importing it would change in Factorial. Writes nothing — the import only happens after confirmation.",
  "tags": ["inbound", "siltra", "fie"],
  "factorial": {
    "enabled": true,
    "requiredPolicies": [["company.manage_timeoff"], ["company.admin"]],
    "uiTrigger": {
      "enabled": true,
      "locationId": "calendar.header.admin",
      "label": "fcode.i18n(\"calendar.import.fie.button\")",
      "icon": "Upload"
    },
    "form": { "enabled": true },
    "agentTool": {
      "enabled": true,
      "description": "Reads a SILTRA FIE transmission (.msj) and reports which sick leaves it would create, link or close in Factorial, and which events it would leave out and why. It writes nothing.",
      "effect": "READ",
      "whenToUse": "The user has downloaded a FIE file from SILTRA and wants to know what importing it would change before anything is written.",
      "whenNotToUse": [
        "The user has already reviewed the report and wants the leaves written: use fie-import-apply.",
        "The file is not a SILTRA .msj transmission."
      ],
      "preconditions": ["The .msj file has been uploaded and its storage reference is passed as msj_file."],
      "doesNotDo": ["It does not create, change or close any leave.", "It does not notify employees or managers."],
      "degradation": "If Factorial cannot be consulted, the report still lists every event in the file, marked as not compared against Factorial.",
      "simulatesFor": "fie-import-apply"
    }
  }
}
processes/fie-import-apply/metadata.json
——无按钮、无表单、异步执行,并且是该组合中的写入端:
json
{
  "name": "FIE Import — apply",
  "tags": ["inbound", "siltra", "fie"],
  "factorial": {
    "enabled": true,
    "awaitResult": false,
    "requiredPolicies": [["company.manage_timeoff"], ["company.admin"]],
    "agentTool": {
      "enabled": true,
      "description": "Writes the sick leaves a SILTRA FIE transmission describes into Factorial: creates, links or closes them.",
      "effect": "WRITE",
      "whenToUse": "The user has seen the fie-import report for a file and asks to apply it.",
      "whenNotToUse": ["The user only wants to know what the file would change: use fie-import."],
      "preconditions": ["fie-import has reported on this same file and the user confirmed the result."],
      "doesNotDo": ["It does not send SILTRA anything back.", "It does not notify employees."],
      "degradation": "Leaves that fail to write are skipped and listed in the result; the rest are applied."
    },
    "backend": { "enabled": true }
  }
}
策略键仅作示例——应从 Factorial 的策略目录获取真实键值。照常读取参数(
fcode.context.parameters.msj_file
,即
fcode.storage://…
引用——
fcode-forms
)。

Checklist before shipping

发布前检查清单

  1. factorial.enabled
    on, and only the entry points you mean to expose enabled.
  2. awaitResult
    decided explicitly:
    false
    for anything slow; a synchronous action returns a shape the entry point it runs under can read.
  3. requiredPolicies
    keys copied from Factorial's catalogue, not guessed — or deliberately empty, which leaves the action open to anyone who reaches it.
  4. agentTool
    :
    effect
    set,
    whenNotToUse
    names the neighbour tools, dry runs point at their real process with
    simulatesFor
    .
  5. No ordinary action uses
    install
    ,
    settings
    ,
    uninstall
    or
    sync
    as slug; the schema has titles, descriptions and
    required
    .
  1. factorial.enabled
    已开启,并且只启用确实要公开的入口点。
  2. 明确决定
    awaitResult
    :耗时操作使用
    false
    ;同步 Action 返回的结构必须能被其运行入口点读取。
  3. requiredPolicies
    键值从 Factorial 的策略目录复制而来,而不是自行猜测——或者有意留空,使任何能访问该入口点的人都可以运行 Action。
  4. agentTool
    :设置
    effect
    ;
    whenNotToUse
    指明相邻工具;dry run 通过
    simulatesFor
    指向其正式流程。
  5. 普通 Action 不使用
    install
    、
    settings
    、
    uninstall
    或
    sync
    作为 slug;schema 中配有
    title
    、
    description
    和
    required
    。