browser-flowのcontract of recordを定義する正規Zodスキーマ、JsonSchema、Expr、RetryPolicy、Require、InputDecl、ToolContract、Step、FlowSpec、Run、JournalEntry、DomainError、PackManifestを1か所にまとめます。
このページでは、browser-flowのすべての名詞を定義する正規Zodスキーマ、つまりcontract of record(M0)をまとめます。各データ型はZodスキーマであり、そのTypeScript型はz.inferで導出されるため、Fooはz.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)によって具体化します。
/** * 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>;
/** * 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>;
/** * 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>;
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>;
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>;
FlowSpecは単一の正規IRです。両方のオーサリングsurfaceがこれを生成し、YAMLへround-tripできます。これはcanonicalizeされ、sha256 digestへhashされます。つまり、キャッシュ、pin、replicationに使われるFlowのidentityです。ドメインモデル §5とFlowSpec & 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>;