Skip to main content
Function Code View is a component inside the Worker process that maintains functionId -> source as a series of immutable snapshots, so that every task sees the same code for its whole lifetime. Function code audit is an admission gate attached to the same chain: before dispatch, a Python static auditor checks the source, and the call is rejected if it fails. Neither is deployed separately. See the Architecture overview for where they sit in the system.

Responsibilities and boundaries

Responsible for:
  • Maintaining the Worker-local SnapshotStore: every code update publishes a complete new snapshot with a new epoch; published snapshots are never modified.
  • Providing CurrentEpoch() and Pin(epoch) for task activation; building a TaskFunctionView per task that resolves both snapshot functions and the task’s own inline source into a source-free digest.
  • Publishing upstream function table changes as new snapshots through a syncer (BlockDB Online table subscription, or Redis metadata hash polling).
  • Fetching source by digest on an Executor compiled-cache miss and handing it back to the Executor.
  • Before dispatch, looking up the audit cache by (digest, entrySelector) and, on a miss, calling the blockx_audit daemon for static auditing (Go side: internal/functioncode/audit).
Not responsible for:
  • Defining how the publishing side writes the function table; it also does not require BlockDB to store an epoch or a global version number.
  • Letting the Executor access BlockDB directly or query Function Code View directly.
  • Guaranteeing that epoch values are consistent or comparable across Workers.
  • The audit rules themselves. All rules live in the Python package python/blockx_audit/; the Go side only provides the process pool, cache, and gate.
Core invariants:
  • epoch is a Worker-local opaque string (bootID:seq) that is meaningful only within the current process.
  • A task is pinned to the same epoch for its whole lifetime; new code only affects new tasks.
  • Snapshots are append-only and never modified. InstallSnapshot only accepts a complete next view, and does not bump epoch when the logical content is unchanged.
  • A single Worker never publishes views out of order: if it sees function a change and then b change, there is never a snapshot where b is new but a is still old.
  • digest is SHA-256(source), always computed locally at a trusted Worker entry point (snapshot install / inline normalization); the upstream-declared Digest never enters the execution path. The digest of empty source is SHA-256(""), not the empty string.
  • Audit enforce mode is fail-closed: both a violation and an unavailable auditor reject the dispatch.

Code location

Core types and interfaces

  • SnapshotStore (internal/functioncode/core/snapshot_store.go): the Worker-local snapshot repository. CurrentEpoch(), Pin(codeEpoch), Fetch(functionID, codeEpoch), InstallSnapshot(next, publishedAt), PruneSnapshotsBefore(cutoff).
  • SnapshotView (same file): the read-only handle a task can pin.
  • FunctionCode / FunctionCodeRef (same file): the former is the payload carrying source (SourceCode, Digest, VerifiedDigest, Space); VerifiedDigest is tagged json:"-" and never comes in from serialized input. The latter is the source-free trusted identity (SourceDigest, Space) and is the only form a snapshot publishes externally.
  • SnapshotMeta (same file): Epoch, PublishedAt, SupersededAt, FunctionCnt. SupersededAt is the starting point of the pruning clock.
  • TaskFunctionView (internal/functioncode/core/task_view.go): the task-level overlay. Resolve(functionID, code) is the source entry boundary; ResolveForDispatch(functionID, expectedDigest) returns the FunctionCodeRef and entrySelector without loading source; SourceByDigest(digest) is only called on an audit miss or an Executor miss.
  • SourceDigest(source) / PrepareFunctionCode(code) (internal/functioncode/core/digest.go): SHA-256 derivation.
  • blockdb.Syncer (internal/functioncode/adapters/blockdb/syncer.go): Bootstrap(ctx) (subscribe → scan → replay → first snapshot), Run(ctx) (steady-state subscribe/apply/reconnect loop). Depends on Deps{Store, ScanClient, SubscribeClient, RowReader, InstallAudit}.
  • redis.Syncer / redis.RedisSource (internal/functioncode/adapters/redis/): Bootstrap / Run have the same semantics as above, polling LoadAll every PollInterval.
  • audit.Pool (internal/functioncode/audit/auditor.go): implements Auditor (Audit(ctx, source, entrySelector), source-first, used by snapshot reports and the sync-invoker) and DigestAuditor (AuditByDigest(ctx, digest, entrySelector, loadSource), used by Worker dispatch).
  • audit.Result / audit.Finding / audit.RejectionError (result.go, gate.go): the wire struct returned by the daemon, a single violation, and the deterministic rejection error. audit.IsRejection(err) distinguishes a violation from an auditor failure.
  • audit.SpaceAllowlist (space_allowlist.go): an operational allowlist that bypasses the gate by FunctionCode.Space; an empty Space never matches.
  • adapters.FunctionAuditGate (internal/worker/adapters/audit_gate.go): Check(ctx, callID, digest, entrySelector, loadSource) (*uds.AuditGatePayload, error).
  • adapters.FunctionCodeViewProvider / adapters.EpochResolver (internal/worker/adapters/task_runtime.go): the Orchestrator’s two dependency interfaces on Function Code View, exposing only Pin and CurrentEpoch respectively.
  • uds.AuditGatePayload / uds.ExecuteCallPayload.FunctionCodeDigest (api/uds/types.go): the gate and digest sent down to the Executor.

Data flow / execution flow

Code update path

Key behaviors of the BlockDB syncer (internal/functioncode/adapters/blockdb/syncer.go):
  • Bootstrap: first Subscribe([system.function]) and buffer events into subscriptionPump, then ScanAll to obtain the base view, then replay the buffered events in order, and finally InstallSnapshot. The subscription established during bootstrap is handed directly to Run for continued consumption, so no events are lost in the handover window.
  • Run: for every changeEvent received, read the current values of the affected rows by RowIDs with BatchGetRows, derive the next complete view copy-on-write from the previous view, then InstallSnapshot. io.EOF and transient errors reconnect after ReconnectDelay (default 200ms), using the CreatedAt of the most recently applied event as StartAt.
  • Only the id and code columns are read (readColumns()); the space column is currently read opportunistically and is an empty string when missing, so it never matches the allowlist.
  • After every install, pruneIfNeeded() and obs.RecordFunctionCodeSnapshotStore are called.
The Redis syncer (internal/functioncode/adapters/redis/) goes through the same InstallSnapshot entry point; only the source differs, with a full HGetAll refresh every PollInterval. decodeFunctionValue accepts hash values that are either raw source or JSON (any of the code / sourceCode / source_code / source fields).
InstallSnapshot only compares FunctionCodeRef (digest + Space). The same source reuses the already-computed digest across adjacent snapshots (cloneFunctionRefs looks it up in the previous snapshot’s sourcesByDigest), so an update that changes a single function performs only one SHA-256.

Query path

1

Pin the epoch at task activation

Orchestrator.activateAndRun (internal/worker/adapters/orchestrator_phases.go) calls epochResolver.CurrentEpoch(), then functionCodeViewProvider.Pin(epoch) to obtain the shared read-only SnapshotView, and checks that snapshotView.Epoch() == epoch. It then builds a TaskFunctionView from it and stores it in taskRuntime.functionCodeView. Failure at any step goes to HandleTaskActivationFailed.
2

Resolve digests before entering the Dispatcher

After the builder produces the CallList, resolveCallDigests calls TaskFunctionView.Resolve(functionID, code) for each call: snapshot functions do one Lookup, and inline functions register their source once per *FunctionCode pointer. From then on the call carries only FunctionCodeDigest for the rest of its lifetime, and the builder’s Code pointer is cleared.
3

Fetch metadata and pass the audit gate at dispatch

When handling DispatchCallCmd (orchestrator_dispatch.go), resolveFunctionCodeForDispatch calls ResolveForDispatch(functionID, expectedDigest) to obtain only Space and entrySelector, without reading source. If a gate exists and Space is not in the allowlist, it calls FunctionAuditGate.Check; the loadSource callback is resolveFunctionCode(taskID, digest) and only runs on an audit cache miss. On rejection, evCallFailed is emitted with errorKind function_code_audit_rejected, which is not retryable.
4

Send the digest to the Executor and fetch source on a miss

ExecuteCallPayload sets only FunctionCodeDigest, EntrySelector, and the optional Audit; it carries no source. When the Executor’s ModuleRegistry.load_callable misses the digest, it sends CallWaiting(waitKind=function_code); on the Worker side, handleFunctionCodeRequest calls TaskFunctionView.SourceByDigest through the resolveFunctionCode registered via SetFunctionCodeResolver and replies with ResumeCall(resumeKind=function_code). This request does not enter the dispatcher’s WAITING state and is not counted in IO metrics. On receipt, the Executor recomputes the SHA-256 and compares it against the digest, then _verify_audit_gate re-verifies sourceDigest and pass.

Audit call chain

  • Both audit entry points share one audit.Pool: newFunctionCodeAudit (internal/worker/app/function_code.go) starts 2 python -u -m blockx_audit daemons in enforce / dark mode.
  • Snapshot install audit: the InstallAudit hook runs audit.BuildReport asynchronously after every new epoch is published, auditing each function with functionId as the entrySelector and logging function-code install audit. It does not affect snapshot content; its main purpose is to warm the (digest, entrySelector) cache so the first dispatch usually hits.
  • Dispatch audit: FunctionAuditGate.Checkaudit.CheckByDigestPool.AuditByDigest. On a cache miss, the singleflight leader calls loadSource, recomputes the digest and compares it against the trusted digest, then sends the source to the daemon as a JSON frame with a 4-byte length prefix (proc.go). The daemon returns Result{Pass, SourceDigest, EntryName, Findings}; Pool then verifies res.SourceDigest == digest.
  • On the Python side, blockx_audit.audit(source, function_id) combines a default-deny AST node allowlist (_ALLOWED_NODES), free-name / import / attribute allowlists (tables.py), kind tracking and capability boundaries (kinds.py), and a bytecode backstop (bytecode.py) that only runs when the walker has no findings, into a single pass bit. It only does ast.parse, never executes user code, and runs on the pure stdlib.
  • Effect on the task / call: under enforce, both RejectionError (violation) and ordinary errors (daemon timeout / crash) fail the call with function_code_audit_rejected; under dark, only a warn log is emitted and the call is dispatched as usual with a gate-less payload; when Space matches the allowlist, the audit is skipped and the Executor loads the function in the unrestricted namespace.
Sync Invoker’s Precheck reuses the same audit.Pool through the source-first audit.Check / audit.Observe interfaces, auditing without executing; see Sync Invoker for details.

State and lifecycle

  • epoch generation: SnapshotStore.installSnapshot does seq++ under the write lock and sets epoch = fmt.Sprintf("%s:%d", bootID, seq); bootID is generated by newFunctionCodeBootID() as boot-<pid>-<unixnano>.
  • Retention and pruning: PruneSnapshotsBefore(cutoff) deletes only snapshots that are not current and whose SupersededAt is earlier than cutoff. The clock starts when a snapshot is superseded, not when it is published, so a long task that pinned a just-superseded epoch still gets the full retention window. Both syncers call it with now - SnapshotRetention after every install; there is no separate timer.
  • A pinned SnapshotView is kept alive by the task’s Go reference; removing that epoch from the store does not affect already-activated tasks. When the calls phase ends, orchestrator_actor.go sets rt.functionCodeView to nil so the writer phase no longer holds a historical snapshot.
  • Startup: setupFunctionCodeView completes syncer.Bootstrap before the gRPC service starts; failure calls os.Exit(1) directly. Therefore the first snapshot is guaranteed to be published by the time the Worker becomes externally visible.
  • Idempotency: InstallSnapshot returns the current epoch with created=false for a logically identical view; TaskFunctionView.RegisterInline does not re-hash the same *FunctionCode pointer; binding the same functionID to different source, or colliding with a snapshot function name, “poisons” that ID so subsequent dispatches fail deterministically.
  • Audit modes (FunctionCodeAuditMode): off (default; no daemon, no gate), enforce (fail-closed; the Worker fails to start if the daemon pool fails to start), dark (audits and logs the same way but does not block; if the daemon pool fails to start, it degrades to off with a warn).

Configuration

The following fields come from internal/worker/app/config.go (WorkerFullConfig); see internal/worker/app/app.go for environment variable overrides. The remaining audit pool parameters are hard-coded in newFunctionCodeAudit: Size: 2, Module: "blockx_audit"; audit.Config.withDefaults fills in CallTimeout 10s and MaxCache 4096. The BlockDB syncer’s ReconnectDelay defaults to 200ms and is not exposed as Worker configuration.
The redis(key=... poll=...) description printed by setupRedisFunctionCodeView says 120s when PollIntervalMs is not configured, but the value actually in effect is the 60s from fcredis.SyncConfig.withDefaults.

Extension points

  • Adding a code source: implement a loader shaped like redis.Source, write a syncer that holds a *fccore.SnapshotStore and publishes complete views only through InstallSnapshot; add a branch in setupFunctionCodeView that returns functionCodeSetup{provider, resolver, start, close}. Never let the query path perform external IO.
  • Extending the audit allowlist: edit python/blockx_audit/tables.py (plus kinds.py and auditor.py when kinds are involved), and update the facade in python/blockx_executor/restricted_runtime.py and python/tests/test_audit_*.py in step. See docs/specs/function-code-python-whitelist.md §13 in the blockx repo for the maintenance tables and self-check list.
  • Changing the daemon protocol: the Go side internal/functioncode/audit/proc.go and the Python side python/blockx_audit/daemon.py must be updated together (frame format, hello frame, Result field names), along with python/tests/test_audit_protocol.py.
  • Adding gate semantics or error classifications: edit internal/functioncode/audit/gate.go (Check / Observe / RejectionError) and internal/worker/adapters/audit_gate.go.
  • Registering test functions for local development: newDevFunctionCodeStore() in internal/worker/app/function_code.go calls MemoryFunctionCodeStore.Register.

Testing

  • internal/functioncode/core/*_test.go: immutability, epoch never going backwards, Pin sharing the view, pinned views remaining usable after pruning, pruning by SupersededAt, rejection of inline / snapshot name collisions.
  • internal/functioncode/adapters/blockdb/syncer_test.go: Bootstrap’s subscribe/scan/replay, event ordering, resubscribing from lastApplied on reconnect, duplicate events not bumping the epoch.
  • internal/functioncode/adapters/redis/*_test.go: payload decoding, rate-limited refresh failure logs, InstallAudit trigger conditions; TestRedisSourceLive requires FUNCTION_CODE_REDIS_URL and is skipped otherwise.
  • internal/functioncode/audit/*_test.go: auditor_test.go starts a real daemon (depends on python/.venv, skipped if missing); gate_test.go, space_allowlist_test.go, proc_robustness_test.go cover gate semantics and process crash / timeout replacement.
  • internal/worker/app/function_code_test.go, function_code_audit_test.go: source selection, retention resolution, startup behavior of the three audit modes.
  • python/tests/test_audit_*.py: test_audit_features.py (subset rules), test_audit_tables.py / test_audit_kinds.py, test_audit_bytecode.py, test_audit_protocol.py (daemon frame protocol), test_audit_corpus.py (v12 function corpus). test_executor_audit_gate.py covers the Executor’s gate re-verification.
  • E2E: cmd/worker/worker_process_function_code_test.go (miniredis end to end), worker_process_activation_test.go (STUB_EPOCH_FAIL triggers activation failure), worker_process_localtestservice_test.go (passes through the gate under FUNCTION_CODE_AUDIT_MODE=enforce). See Testing for the overall test organization.
Specs in the blockx repo:
  • docs/specs/function-code-view.md: detailed design of snapshots, epochs, retention and pruning, and the query chain.
  • docs/specs/function-code-audit-design.md: the audit mechanism, the two gates, error codes, and the Executor restricted runtime.
  • docs/specs/function-code-python-whitelist.md: the complete allowlist, aligned table by table with blockx_audit.
  • docs/specs/2026-07-23-execute-call-function-code-reference.md: the protocol decision that ExecuteCall sends only the digest and fetches source on a miss.
  • docs/specs/architecture.md §4.2.4.4: where Function Code View sits in the overall architecture.
  • docs/specs/sync-invoker.md §3.1: the relationship between Precheck and audit modes.
Related pages on this site: