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 newepoch; published snapshots are never modified. - Providing
CurrentEpoch()andPin(epoch)for task activation; building aTaskFunctionViewper task that resolves both snapshot functions and the task’s own inline source into a source-freedigest. - Publishing upstream function table changes as new snapshots through a syncer (BlockDB Online table subscription, or Redis metadata hash polling).
- Fetching source by
digeston 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 theblockx_auditdaemon for static auditing (Go side:internal/functioncode/audit).
- Defining how the publishing side writes the function table; it also does not require BlockDB to store an
epochor a global version number. - Letting the Executor access BlockDB directly or query Function Code View directly.
- Guaranteeing that
epochvalues 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.
epochis a Worker-local opaque string (bootID:seq) that is meaningful only within the current process.- A task is pinned to the same
epochfor its whole lifetime; new code only affects new tasks. - Snapshots are append-only and never modified.
InstallSnapshotonly accepts a complete next view, and does not bumpepochwhen the logical content is unchanged. - A single Worker never publishes views out of order: if it sees function
achange and thenbchange, there is never a snapshot wherebis new butais still old. digestis SHA-256(source), always computed locally at a trusted Worker entry point (snapshot install / inline normalization); the upstream-declaredDigestnever 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);VerifiedDigestis taggedjson:"-"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.SupersededAtis 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 theFunctionCodeRefandentrySelectorwithout 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 onDeps{Store, ScanClient, SubscribeClient, RowReader, InstallAudit}.redis.Syncer/redis.RedisSource(internal/functioncode/adapters/redis/):Bootstrap/Runhave the same semantics as above, pollingLoadAlleveryPollInterval.audit.Pool(internal/functioncode/audit/auditor.go): implementsAuditor(Audit(ctx, source, entrySelector), source-first, used by snapshot reports and the sync-invoker) andDigestAuditor(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 byFunctionCode.Space; an emptySpacenever 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 onlyPinandCurrentEpochrespectively.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: firstSubscribe([system.function])and buffer events intosubscriptionPump, thenScanAllto obtain the base view, then replay the buffered events in order, and finallyInstallSnapshot. The subscription established during bootstrap is handed directly toRunfor continued consumption, so no events are lost in the handover window.Run: for everychangeEventreceived, read the current values of the affected rows byRowIDswithBatchGetRows, derive the next complete view copy-on-write from the previous view, thenInstallSnapshot.io.EOFand transient errors reconnect afterReconnectDelay(default 200ms), using theCreatedAtof the most recently applied event asStartAt.- Only the
idandcodecolumns are read (readColumns()); thespacecolumn is currently read opportunistically and is an empty string when missing, so it never matches the allowlist. - After every install,
pruneIfNeeded()andobs.RecordFunctionCodeSnapshotStoreare called.
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 2python -u -m blockx_auditdaemons in enforce / dark mode. - Snapshot install audit: the
InstallAudithook runsaudit.BuildReportasynchronously after every new epoch is published, auditing each function withfunctionIdas theentrySelectorand loggingfunction-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.Check→audit.CheckByDigest→Pool.AuditByDigest. On a cache miss, the singleflight leader callsloadSource, 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 returnsResult{Pass, SourceDigest, EntryName, Findings};Poolthen verifiesres.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 singlepassbit. It only doesast.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 withfunction_code_audit_rejected; under dark, only a warn log is emitted and the call is dispatched as usual with a gate-less payload; whenSpacematches the allowlist, the audit is skipped and the Executor loads the function in the unrestricted namespace.
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
epochgeneration:SnapshotStore.installSnapshotdoesseq++under the write lock and setsepoch = fmt.Sprintf("%s:%d", bootID, seq);bootIDis generated bynewFunctionCodeBootID()asboot-<pid>-<unixnano>.- Retention and pruning:
PruneSnapshotsBefore(cutoff)deletes only snapshots that are not current and whoseSupersededAtis 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 withnow - SnapshotRetentionafter every install; there is no separate timer. - A pinned
SnapshotViewis 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.gosetsrt.functionCodeViewtonilso the writer phase no longer holds a historical snapshot. - Startup:
setupFunctionCodeViewcompletessyncer.Bootstrapbefore the gRPC service starts; failure callsos.Exit(1)directly. Therefore the first snapshot is guaranteed to be published by the time the Worker becomes externally visible. - Idempotency:
InstallSnapshotreturns the current epoch withcreated=falsefor a logically identical view;TaskFunctionView.RegisterInlinedoes not re-hash the same*FunctionCodepointer; binding the samefunctionIDto 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 frominternal/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.SnapshotStoreand publishes complete views only throughInstallSnapshot; add a branch insetupFunctionCodeViewthat returnsfunctionCodeSetup{provider, resolver, start, close}. Never let the query path perform external IO. - Extending the audit allowlist: edit
python/blockx_audit/tables.py(pluskinds.pyandauditor.pywhen kinds are involved), and update the facade inpython/blockx_executor/restricted_runtime.pyandpython/tests/test_audit_*.pyin step. Seedocs/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.goand the Python sidepython/blockx_audit/daemon.pymust be updated together (frame format, hello frame,Resultfield names), along withpython/tests/test_audit_protocol.py. - Adding gate semantics or error classifications: edit
internal/functioncode/audit/gate.go(Check/Observe/RejectionError) andinternal/worker/adapters/audit_gate.go. - Registering test functions for local development:
newDevFunctionCodeStore()ininternal/worker/app/function_code.gocallsMemoryFunctionCodeStore.Register.
Testing
internal/functioncode/core/*_test.go: immutability, epoch never going backwards,Pinsharing the view, pinned views remaining usable after pruning, pruning bySupersededAt, rejection of inline / snapshot name collisions.internal/functioncode/adapters/blockdb/syncer_test.go:Bootstrap’s subscribe/scan/replay, event ordering, resubscribing fromlastAppliedon reconnect, duplicate events not bumping the epoch.internal/functioncode/adapters/redis/*_test.go: payload decoding, rate-limited refresh failure logs,InstallAudittrigger conditions;TestRedisSourceLiverequiresFUNCTION_CODE_REDIS_URLand is skipped otherwise.internal/functioncode/audit/*_test.go:auditor_test.gostarts a real daemon (depends onpython/.venv, skipped if missing);gate_test.go,space_allowlist_test.go,proc_robustness_test.gocover 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.pycovers 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_FAILtriggers activation failure),worker_process_localtestservice_test.go(passes through the gate underFUNCTION_CODE_AUDIT_MODE=enforce). See Testing for the overall test organization.
Related docs
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 withblockx_audit.docs/specs/2026-07-23-execute-call-function-code-reference.md: the protocol decision thatExecuteCallsends 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 betweenPrecheckand audit modes.
- Worker: task activation and the Orchestrator.
- Call execution subsystem: the dispatcher and executor adapter.
- Python Executor:
ModuleRegistryand the restricted runtime. - Sync Invoker:
Precheckand auditing for synchronous invocation. - Protocols and interfaces: UDS
ExecuteCall/CallWaiting/ResumeCall.