このページでは、browser-flowのすべての名詞を定義する正規Zodスキーマ、つまりcontract of record(M0)をまとめます。各データ型はZodスキーマであり、そのTypeScript型はz.inferで導出されるため、Fooz.infer<typeof FooSchema>を表します。すべてのスキーマは.strict()です(Riderは認識しないフィールドを黙って無視しません)。また、すべてのトップレベルドメインスキーマは、寛容なパースではなく明示的なバージョンネゴシエーションのためにschema_versionリテラル("1.0")を持ちます。 以下のスキーマは、@browserflow/coreに実装されているZodスキーマです。フィールドの意味が重要な箇所では、周囲の説明と、実行例であるdaily-price-watch Flow(ブラウザTool search-price@1.2.0 → transform → シークレットwebhookへの条件付きhttp.post)によって具体化します。

なぜ.strict()schema_versionを使うのか

前方互換性は、寛容なパースではなく明示的なバージョンネゴシエーションによって扱います。すべてのスキーマが.strict()であるため、Riderはサポートしているより高いminorが刻印されたFlowSpecを拒否します。強制変換せず、はっきりと失敗します(原則T7)。追加的な変更ではminorを上げます(1.0 → 1.1)。破壊的変更ではmajorを上げ、migrate codemodを同梱します。移行モデル全体については、FlowSpec & IRを参照してください。

共有スキーマ語彙

4つの小さなスキーマが全体で参照されます。これらは一度だけ定義されます。ドメインモデル §0を参照してください。

JsonSchema

小さなJSON Schema(Draft 2020-12)サブセットです。ToolまたはFlowのI/Oスロットが取り得る型で、スカラー、配列、オブジェクトを表します。
import { z } from "zod";

/**
 * A small JSON Schema (Draft 2020-12) subset: the type a tool/flow I/O slot may
 * take. Scalars, arrays, and objects — so an input is no longer limited to a bare
 * scalar (a tool can declare an object body, an array, etc.).
 */
export type JsonSchema =
  | { type: "string" | "number" | "boolean" | "null" }
  | { type: "array"; items: JsonSchema }
  | {
      type: "object";
      properties: Record<string, JsonSchema>;
      required?: string[];
      additionalProperties?: boolean;
    };

export const JsonSchemaSchema: z.ZodType<JsonSchema> = z.lazy(() =>
  z.union([
    z.object({ type: z.enum(["string", "number", "boolean", "null"]) }).strict(),
    z.object({ type: z.literal("array"), items: JsonSchemaSchema }).strict(),
    z
      .object({
        type: z.literal("object"),
        properties: z.record(z.string(), JsonSchemaSchema),
        required: z.array(z.string()).optional(),
        additionalProperties: z.boolean().optional(),
      })
      .strict(),
  ]),
);
使用箇所: InputDecl.schemaToolContract.output

Expr

式です。${{ … }}の内側に現れるソーステキスト(またはリテラル)を表します。コンパイラが生の文字列と、パース済みで検証済みの式を区別できるようにbrand付けされています。
/**
 * An expression: the source text that appears inside `${{ … }}` (or a literal).
 * Branded so the compiler distinguishes a raw string from a parsed, validated
 * expression. Grammar: 02 — Authoring (expression language).
 */
export const ExprSchema = z.string().brand<"Expr">();
export type Expr = z.infer<typeof ExprSchema>;
使用箇所: Step.withStep.whenStep.foreachFlowSpec.output。文法はexpression languageにあります。

RetryPolicy

手順ごとのリトライポリシーです。デフォルトはToolのkindから導出されます(ネットワークToolはリトライし、pure transformはリトライしません)。
/** Per-step retry policy. Defaults are derived from a tool's `kind`. */
export const RetryPolicySchema = z
  .object({
    max: z.number().int().nonnegative(),
    backoff: z.enum(["exp", "fixed"]),
    base_ms: z.number().int().positive(),
    jitter: z.boolean(),
  })
  .strict();
export type RetryPolicy = z.infer<typeof RetryPolicySchema>;
使用箇所: Step.retryExecution §4を参照してください。

Require

固定されたTool依存です。正確なname@version refと解決済みcontract digestの両方を持つため、Flowのidentityは単なるバージョンラベルではなくToolのbehaviorまで閉じ込めます。
/**
 * A pinned tool dependency: an exact `name@version` ref AND the resolved
 * contract digest, so a flow's identity closes over tool *behavior*, not just a
 * version label.
 */
export const RequireSchema = z
  .object({ ref: z.string().min(1), contract_digest: z.string().min(1) })
  .strict();
export type Require = z.infer<typeof RequireSchema>;
使用箇所: FlowSpec.requires。Flow digestを固定すると依存クロージャ全体が固定されます。詳しくはThe digestを参照してください。

Tool contractスキーマ

ドメインモデル §2を参照してください。

InputDecl

Tool上で宣言される1つの入力スロットです。型付きで、任意でシークレットにできます。secret: trueのスロットはRun境界の外に出ません。
/**
 * One declared input slot on a tool. The contract between the tool's caller
 * and its body: the tool reads its inputs by name and `schema`, and
 * `secret: true` slots never leave the run boundary. `schema` is the
 * JSON-Schema-subset type of the slot — scalars **and** objects/arrays, so a
 * tool can declare e.g. an object request body. The YAML surface accepts a
 * `type: string|number|boolean` shorthand that expands to `{ "type": … }`
 * during canonicalization.
 */
export const InputDeclSchema = z
  .object({
    name: z.string().min(1),
    schema: JsonSchemaSchema,
    secret: z.boolean(),
  })
  .strict();

export type InputDecl = z.infer<typeof InputDeclSchema>;
使用箇所: ToolContract.inputsFlowSpec.inputs

ToolContract

Toolのinterface contractです。型付きの入力と出力、宣言されたcapabilities、provenanceを持ちます。capabilitiesはToolが使用できるeffectを列挙します(T2: effects by permission)。Riderはそれらだけを注入し、それ以上は注入しません。
/**
 * A tool's interface contract. `inputs`/`output` are typed so that wiring is
 * checked at author time and validated at run time. `capabilities` enumerate
 * the effects the tool is permitted to use (T2: effects by permission); the
 * Rider injects exactly those and no more. `provenance.source_digest` is a
 * content-addressed fingerprint of whatever authored the tool body (e.g. a
 * recorded browser session), so a tool is pinnable and auditable.
 */
export const ToolContractSchema = z
  .object({
    schema_version: z.literal("1.0"),
    name: z.string().min(1),
    version: z.string().min(1), // semver
    kind: z.enum(["browser", "http", "shell", "transform", "code", "model", "composite"]),
    description: z.string().min(1),
    inputs: z.array(InputDeclSchema),
    output: JsonSchemaSchema, // a small JSON Schema (Draft 2020-12) subset
    capabilities: z.array(z.enum(["net", "fs", "browser", "clock", "random", "env", "spawn", "secrets"])),
    provenance: z
      .object({ source_digest: z.string().optional(), pack: z.string().optional() })
      .strict(),
  })
  .strict();

export type ToolContract = z.infer<typeof ToolContractSchema>;
daily-price-watchでは、search-price@1.2.0browser capabilityを宣言するbrowser Toolであり、webhook手順はnetsecretsを宣言するhttp Toolを使います。Tool kindはstandard packsに含まれています。

Stepスキーマ

StepはToolをFlow内の位置に結び付けます。呼び出しにidを与え、withを通じてFlowデータをToolのinputsにマップし、依存関係を宣言し、任意でretry、timeout、conditional policyを付けます。ドメインモデル §3を参照してください。
export const StepSchema = z
  .object({
    id: z.string().min(1), // unique within the flow; stable across edits
    use: z.string().min(1), // "browser:search-price@1.2.0" | "http.post" | "flow:sub@2"
    with: z.record(z.string(), ExprSchema).optional(), // input bindings (may be expressions)
    needs: z.array(z.string()).optional(), // explicit deps; else inferred from refs
    when: ExprSchema.optional(), // run only if this evaluates truthy
    retry: RetryPolicySchema.optional(),
    timeout_ms: z.number().int().positive().optional(),
    foreach: ExprSchema.optional(), // fan out over an array (map)
    output_as: z.string().optional(), // alias for downstream references
  })
  .strict();

export type Step = z.infer<typeof StepSchema>;
コンパイラはwithwhenforeach内のsteps.<id>参照を読み取り、依存エッジを構築します。そのため、needsは通常、書かれるのではなく推論されます。control flowhow references become a graphを参照してください。

FlowSpecスキーマ

FlowSpecは単一の正規IRです。両方のオーサリングsurfaceがこれを生成し、YAMLへround-tripできます。これはcanonicalizeされ、sha256 digestへhashされます。つまり、キャッシュ、pin、replicationに使われるFlowのidentityです。ドメインモデル §5FlowSpec & IR §3を参照してください。
export const FlowSpecSchema = z
  .object({
    schema_version: z.literal("1.0"),
    name: z.string().min(1),
    description: z.string().optional(),
    inputs: z.array(InputDeclSchema),
    steps: z.array(StepSchema), // topologically independent; order is for humans
    output: ExprSchema.optional(),
    /** Every tool this flow invokes — exact version + resolved contract digest
     * (`RequireSchema`). Derived, not authored; folded into the flow digest. */
    requires: z.array(RequireSchema),
    /** sha256 of the canonical form. Absent only mid-compile. */
    digest: z.string().optional(),
  })
  .strict();

export type FlowSpec = z.infer<typeof FlowSpecSchema>;
FlowSpecはプレーンなJSONです。関数もクラスもないため、シリアライズ、diff、移動が簡単です。digestフィールドは自身のhashから除外されます。requiresは書かれたものではなく、書き換えられたuse: refから導出されます。

Runスキーマ

Runは、Riderが具体的な入力に対してFlowSpecを実行しているものです。これはJournalを生成します。Journalは、手順ライフサイクルイベントに、解決済み入力と最終出力を加えた、append-onlyで順序付きのログです。ドメインモデル §6を参照してください。
export const RunSchema = z
  .object({
    id: z.string().min(1), // uuidv4
    flow_digest: z.string().min(1), // exactly which FlowSpec ran
    status: z.enum(["pending", "running", "succeeded", "failed", "canceled"]),
    started_at: z.string(), // ISO-8601
    finished_at: z.string().optional(),
    journal: z.array(JournalEntrySchema),
    output: z.unknown().optional(),
  })
  .strict();

export type Run = z.infer<typeof RunSchema>;
flow_digestは、どのFlowSpecが実行されたかを正確に記録します(原則T3)。JournalによってRunは再開可能かつリプレイ可能になります。実行を参照してください。

Journalとエラースキーマ

Execution §3§8を参照してください。

JournalEntry

各手順は小さなステートマシンです。すべての遷移がJournal entryになります。Journalはappend-onlyかつcontent-addressedです(すべての出力とeffectはdigestで保存されます)。これにより、再開とリプレイが可能になります。
export const JournalEntrySchema = z.discriminatedUnion("event", [
  z.object({ event: z.literal("run_started"), at: z.string(), flow_digest: z.string(), inputs_digest: z.string() }).strict(),
  z.object({ event: z.literal("step_started"), at: z.string(), step: z.string(), attempt: z.number().int() }).strict(),
  z.object({ event: z.literal("step_succeeded"), at: z.string(), step: z.string(), output_digest: z.string() }).strict(),
  z.object({ event: z.literal("step_failed"), at: z.string(), step: z.string(), error: DomainErrorSchema }).strict(),
  z.object({ event: z.literal("step_skipped"), at: z.string(), step: z.string(), reason: z.string() }).strict(),
  z.object({ event: z.literal("effect_recorded"), at: z.string(), step: z.string(), key: z.string(), result_digest: z.string() }).strict(),
  z.object({ event: z.literal("run_finished"), at: z.string(), status: z.enum(["succeeded", "failed", "canceled"]) }).strict(),
]);
daily-price-watchでは、webhookのeffect_recorded entryが、冪等性キーをkeyとしてrequestをredacted shape({secret:WEBHOOK_URL})としてjournalに記録します。シークレット値は決して記録されません。recording effects without leaking secretsを参照してください。

DomainError

エラーはDomainError lineageに従います。それぞれが安定したcode、問題が発生したstep、リトライ可能かどうかを示すflagを持つため、failureはactionableであり、シークレットを漏らしません。
export const DomainErrorSchema = z
  .object({
    code: z.string().min(1), // e.g. "tool.http.timeout", "tool.browser.locator_missing"
    message: z.string().min(1),
    step: z.string().optional(),
    retryable: z.boolean(),
    cause_digest: z.string().optional(),
  })
  .strict();
使用箇所: JournalEntrystep_failed.error)。コンパイル時およびruntime codeのカタログについては、diagnostics referenceを参照してください。

PackManifestスキーマ

Packは、ToolsとFlowsのversionedでcontent-addressedなbundleです。deployment、versioning、replicationの単位です。manifestにはtools(それぞれのcontract digest付き)とflows(それぞれのFlowSpec digest付き)が列挙されるため、Packは全体として再現可能です。ドメインモデル §8を参照してください。
export const PackManifestSchema = z
  .object({
    schema_version: z.literal("1.0"),
    name: z.string().min(1), // "@acme/pricing"
    version: z.string().min(1), // semver
    tools: z.array(z.object({ ref: z.string(), contract_digest: z.string() }).strict()),
    flows: z.array(z.object({ name: z.string(), flow_digest: z.string() }).strict()),
    dependencies: z.array(z.string()), // other packs, pinned
    digest: z.string(), // sha256 of the canonical manifest
  })
  .strict();

export type PackManifest = z.infer<typeof PackManifestSchema>;
PacksとRegistryの詳細はTools & Packsにあります。

スキーマ一覧

Schema目的使用箇所
JsonSchemaSchema型付きI/OスロットのためのJSON SchemaサブセットInputDeclToolContract
ExprSchemabrand付けされた式ソーステキストStepFlowSpec
RetryPolicySchema手順ごとのリトライポリシーStep
RequireSchema固定されたTool ref + contract digestFlowSpec
InputDeclSchema型付きで任意でシークレットにできる1つの入力スロットToolContractFlowSpec
ToolContractSchemaToolのinterface contractPacks、compiler
StepSchema1つのTool呼び出しとwiringFlowSpec
FlowSpecSchema正規でcontent-addressedなIRRider、Packs
RunSchemaJournalを持つ1回の実行Store
JournalEntrySchemaappend-onlyなRunライフサイクルイベント1件Run
DomainErrorSchema安定した、シークレット安全なerrorJournalEntry
PackManifestSchemaTools + Flowsのversioned bundleRegistry

ドメインモデル

このページのすべてのスキーマの背後にある7つの名詞と説明。

FlowSpec & IR

Canonicalization、digestアルゴリズム、reference-to-graph導出。

実行

RiderがFlowSpecをRun、Journal、リプレイに変換する方法。

診断

これらのスキーマが表面化するコンパイル時およびruntime codeのカタログ。