docassemble-simulator
Run a docassemble interview locally without a server. The simulator uses the real docassemble compiler, assembly/seek machinery, validation behavior, and DOCX/Jinja renderer while providing small commands suitable for humans and agents.
Install
Install the simulator from PyPI into the target package's interpreter; it must
run in the same environment as the interview package, so a global installation
(uv tool install, pipx) is not a substitute. Python 3.10 or newer is
required.
For a uv-managed interview package:
# Run these in the interview package's project environment.
uv add --dev docassemble-simulator
uv run sim check
For a non-uv project, install it into the target package interpreter:
/path/to/package/.venv/bin/python -m pip install docassemble-simulator
/path/to/package/.venv/bin/sim check
sim is the short alias for the canonical docassemble-simulator command;
both are installed and run the same CLI. Use the long name in scripts or
environments where sim might collide with another tool.
To track unreleased changes, install from the public GitHub repository instead:
uv add --dev "git+https://github.com/jpagh/docassemble-simulator"
Pin a version when reproducibility matters, for example
docassemble-simulator==26.10.9 or
git+https://github.com/jpagh/docassemble-simulator@v26.10.8.
why VARIABLE lists variable-resolution candidates in the same order the real
runtime will try them, including explicit ordering directives, applicable
language fallback, catalog provenance, and authored guards. With a saved session,
guards are evaluated after read-only execution preparation, so rehydrated helpers
and docassemble objects behave normally; the operation never saves the prepared
state. Without a saved session it reports guard text without fabricating guard
values. It marks the runtime winner when it can and reports resolution
conflicts; --strict makes a conflict non-zero. Saved-state reports label each
candidate live, inactive, or unknown. Unknown guard errors are handled
conservatively: overlapping unknown/live candidates conflict without claiming a
winner, while definitely inactive or provably disjoint candidates do not. The
original guard error remains in the candidate report for diagnosis. A terminal
unresolved-variable error includes the responsible block provenance, the
expression that ultimately referenced the sought variable, and that variable;
attribution comes from the ordered runtime seek history rather than a stale
previously displayed screen.
Expression matching includes the complete attribute, key, or index path; when
that precision is not retained, the expression is reported as unknown rather
than inferred from a shared root.
Tolerated lazy references remain diagnostics without a culprit. With seek
capture disabled, the simulator does not fabricate attribution when the target
runtime does not retain the ordered history.
If docassemble-base or docassemble-webapp is absent, the CLI acquires the
missing fixed package names with uv pip (or the interpreter's pip) without
upgrading installed packages. Use --offline or [simulator].offline = true
to disable acquisition. An installed package that fails because of a native
library is reported as an import failure, not silently reinstalled.
The simulator tests two runtime families: legacy docassemble 1.9.x (thread-local
server interface) and modern 1.10.x (hook interface). Supported
AssemblyLine-backed Interviews are exercised as a tested matrix of
docassemble-base/docassemble-webapp/docassemble.AssemblyLine/
docassemble.ALToolbox versions; see
docs/runtime-compatibility.md. The simulator
reproduces the docassemble webapp's startup module preload so installed custom
datatypes (for example ALToolbox BirthDate) register before parsing, then
compiles a minimal AssemblyLine birthdate-metadata probe before the target
Interview. An unsupported pair fails as runtime-compatibility with the
installed versions, failing capability, and recovery direction instead of a raw
parser error; the probe never installs, upgrades, or edits packages. The target
package interpreter is authoritative: a global jda or unrelated Python
installation is not a substitute. On macOS, docassemble's native dependencies
(such as zbar, commonly installed with Homebrew) must also be available to
that interpreter; native-library failures are reported rather than hidden.
Command model
Run from a directory containing docassemble/<package>/, or pass --root and
optionally --interview. --version prints the installed simulator version
without touching the runtime, and info --json reports the same value as
simulator_version beside the detected docassemble runtime.
docassemble-simulator info
docassemble-simulator check
docassemble-simulator questions
docassemble-simulator index --var M.family
docassemble-simulator why M.family --strict
docassemble-simulator start
docassemble-simulator answer M.name=Alice
docassemble-simulator answer --defaults
docassemble-simulator seek M.family --auto
docassemble-simulator status
docassemble-simulator refresh
docassemble-simulator reset
docassemble-simulator eval 'M.name'
docassemble-simulator vars
docassemble-simulator exec 'M.items.append_object()'
docassemble-simulator seek M.family --activate
startcreates fresh state and assembles to a screen, completion, or flow error.answersubmits the active saved screen like a browser form and commits the result as one transaction: submitted values are applied, visible fields with defaults take the default, visible optional fields left blank are defined with the value the browser would post (an empty string for text and dates,0/0.0for numeric fields,Nonefor radio/maybe/object selections, the unchecked value for checkbox-styleyesno/noyes, and an all-false dictionary for checkbox groups), so the samesets:question is not re-asked for each variable. A submittedcheckboxes/multiselectdictionary is completed the same way: a browser posts every rendered choice, so an omitted choice is definedFalserather than left undefined. A review's continue button is described as an answerableTruesubmission, and--defaultspresses it. Visible required fields left blank, or explicitly emptied (field=orfield=None), reject the submission, except that a nonblankNoneornulltoken explicitly selecting an offered null-valued radio choice is answered and storesNone. A required checkbox group needs at least one true selection. When native resolved choices suppress an input, authored requiredness remains in the screen description but does not demand an impossible answer (rendered_input: false). An undefined absentobject/object_radioselection becomesNone; an existing value is preserved. Browser constraints do not apply to an absent input, but authored validation still runs and may reject it (for example, “You must select at least one Trust”). No--partialor relaxed strictness is needed. Missing normalized choices alone do not establish absence; comboboxes and datalists still offer inputs. Hiddenshow iffields are neither assigned nor required, and signature fields becomeDAEmpty()so documents still render. Fields that are not on the active screen are rejected; useexecfor deliberate state surgery.--defaultssubmits the active screen without explicit assignments, using visible defaults and browser-blank values; required fields without defaults are reported instead of being guessed.--partialrestores per-field submission (missing required fields become warnings and lazy re-seeking is allowed), and--no-validateskips only the interview's validation code, not the required gate. Assignment or validation failure discards every submitted value.statusonly reads the saved outcome.refreshexplicitly rehydrates and reassembles saved state.resetdiscards every saved session for the selected interview, including any session stored under a different effective configuration. It needs no importable interview, never assembles, and is the one-shot escape when a saved session cannot be loaded under the current configuration.seekstarts from saved state by default.--freshis isolated and--activatepersists the sought screen as the active screen.--autosubmits each screen that can be completed from defaults/blanks as its own browser-shaped transaction, reports the ordered accepted screens, and stops immediately when the sought variable resolves, the interview completes, or real input/validation is needed. A rejected auto-submission rolls back its defaults, synthesized blanks, and validation mutations; earlier accepted screens remain committed in the operation-local walk. Non-activated and fresh seeks do not save the walk. Automatic journeys use the same submission acceptance asanswer: an absent required object-selection input receivesNoneonly when undefined, including resolved generic-object targets. Authored validation decides whether to advance or stop with its own error, not a required-missing diagnosis. Do not use--partialto get past an absent control; that opts out of whole-screen browser behavior rather than fixing it.- Lazy variable seeking is reported as ordered
variable-seekdiagnostics, not as errors. A seek fails only when docassemble exhausts its definitions; that produces anunresolved-variableerror containing the sought variable. evalandvarsrehydrate the namespace but never save it.execassembles and saves by default.--no-assembleperforms a state-only commit after successful Python execution.
Table/review screens expose row_actions, an array of rendered controls such as
{"kind": "add", "target": "people"} and
{"kind": "delete", "target": "people", "row": 0} (zero-based rows).
kind is an open string vocabulary; add, delete, and edit are supported.
Press them with row-action add people, row-action delete people --row 0,
or row-action edit people --row 0. Edit presents the row's authored screen.
Nested targets such as children[0].grandchildren use the same controls.
The operation accepts only controls on the saved screen, replays docassemble's
rendered action dispatch, and saves the resulting screen only on success. The worker accepts
{"operation": "row-action", "kind": "delete", "target": "people", "row": 0};
Python callers use runner.row_action("delete", "people", row=0).
These additive screen facts are debug-only in screen traces, not field-fact
comparison inputs; older traces remain readable.
Native screen actions
Screens also expose actions: links captured from rendered question/subquestion
text and visible note/HTML fields, including inline and reference-style Markdown
links interpreted by docassemble's configured Markdown engine. Each control has an opaque id, label,
source, and either its native payload or an unsupported reason.
docassemble-simulator status --json # inspect result.actions
docassemble-simulator action ACTION_ID --record run.trace.jsonl
The worker accepts {"operation":"action","action_id":"ACTION_ID"};
Python callers use runner.action(action_id). Choose an identifier from the
current saved screen, not a URL or an action name. Identifiers are deterministic
for the same rendered screen and distinguish creation parameters and source
locations; they are not permanent interview-wide identifiers. Old saved screens
without captured note content need refresh before those links can be pressed.
Likewise, refresh screens saved by earlier builds to regenerate their action
metadata and identifiers; status only reads the previously saved outcome.
action dispatches the complete native payload through docassemble, including
url_ask()'s _da_force_ask wrapper and ordered parameter-setting/add-item
sequences. It does not submit unanswered fields or run form validation. A
successful resulting screen is saved atomically; errors leave the saved session
unchanged (external side effects of authored code are not rolled back). Missing
identifiers, JavaScript, external/cross-interview links, extra URL navigation
parameters, and malformed URLs are rejected explicitly. Only local native action
links are supported, not arbitrary DOM/JavaScript interaction or CSS visibility.
row-action remains available for its existing list controls.
Do not evaluate a context-dependent button helper with eval to reconstruct a
link: evaluation has no active question-render context. Press the link already
rendered on the saved screen instead; no artificial current_variable is needed.
answer parses JSON, then Python literals, then plain text. Its JSON result
reports each assignment as applied, unknown field, or parse error, and
rejections include the active fields, closest-name suggestions, and missing
required fields. Unknown-field and parse-error rejections provide that same
missing-input guidance from valid assignments and rendered defaults without
running interview validation; all rejected values are discarded. The active
screen describes every field with a copy-pasteable
example (including checkbox mappings, object choices/groups, blanks, and
ISO dates); answer --help documents the same grammar. Checkbox and object
choice fields use the active field metadata. An exact YYYY-MM-DD submitted to
a date field becomes docassemble's timezone-aware DADateTime; malformed and
impossible dates are rejected. The same text submitted to a text field remains
a string. answer --code and exec retain Python semantics and bypass browser
coercion; --code is still scoped to the active screen's fields.
State is versioned and stored separately for each canonical interview and
effective configuration under .simulator/sessions/. The effective
configuration is fingerprinted over its resolved values, so two runs share a
session exactly when their discovered files, --config overrides, and
command-line policy flags resolve to the same content; changing any of them
starts a separate session instead of silently rehydrating the old one.
config --json reports this as config_fingerprint and, for every effective
top-level key, the contributing file/override layers and shadowed values.
Loading state saved under a different configuration reports the stored
fingerprint, config files/override, an exact resume command, and an exact
reset command for discarding it; the previous session stays on disk for the
configuration that produced it. This diagnostic
is shared by status, vars, answer, and seek. Files are trusted-local
pickle payloads. Unsupported or stale state fails with an instruction to run
start again. Mutations hold a per-interview advisory lock and commit with
atomic replacement.
check also reports duplicate explicit question ids with every block's
provenance. They are warnings by default; check --strict makes accidental
duplicates fail. A valid override/supersedes declaration is reported
separately with its participating blocks, but never blanket-exempts a duplicate
id group; unrelated or missing targets do not suppress the collision, and mixed
groups retain an accidental-collision report.
Persistent journeys and the standard runner
A multi-step journey should use one persistent worker instead of launching one process per operation:
printf '%s\\n' \\
'{"id":"1","operation":"start"}' \\
'{"id":"2","operation":"answer","assignments":{"user_name":"Alice"}}' \\
'{"id":"3","operation":"status"}' \\
'{"id":"4","operation":"shutdown"}' \\
| sim --root ./my-project --interview docassemble.my_project:data/questions/main.yml run \\
--timeout 30 --timings
run and worker accept JSON-lines on stdin or --journey FILE (a JSON array
is also accepted). Each response contains the request id, ok, the normal
simulator result or error, and the active project/interview/session scope.
reset discards every saved session for the worker's interview, including
sessions stored under another effective configuration; shutdown exits
cleanly.
--session-root PATH makes the session resumable by another worker. Without it,
the command uses a temporary isolated session. Interview/bootstrap output is
redirected to stderr so stdout remains protocol-only. Timings are optional and
remain in the protocol response.
While a worker is running, editing the interview's source takes effect on the
next operation. The worker checks the timestamp and size of every workspace
YAML source it watches — every *.yml/*.yaml under the workspace's
docassemble/*/data/questions trees, plus each file a compiled question came
from, so an abbreviated pkg:file include and a relative include nested under
another package are covered at the exact file docassemble read — together with
the declared modules:/imports: helper files, once per operation and, when
they changed, drops the compiled program, evicts the recompiled interview's
declared helpers from sys.modules, invalidates their cached bytecode, and
recompiles — so renaming a screen, editing an included YAML (including an
include-only routing file or a features-only document that contributes no
question), or editing a helper (with or without an interview-source edit) is
served without restarting the worker. Watching the whole questions tree
over-includes: an unrelated YAML edit in the same workspace merely causes a
reload, which is preferred to missing a source the interview could read. A
declared helper is watched when it resolves to a file or package under the
workspace source tree; helper edits to a declared package directory are watched
recursively, so an edited or newly added submodule counts. Only workspace
declared helpers are evicted, so an installed module named in imports: keeps
its identity. A declared module outside the workspace (stdlib or an installed
docassemble package) is not watched, and neither is a workspace module the
interview does not declare. Watching, eviction, and bytecode invalidation all
resolve real paths first, so a workspace symlink that escapes the resolved root
is not watched, evicted, or unlinked, a __pycache__ symlink pointing outside
the root does not have its external cache deleted, and a YAML or include that
resolves outside the workspace root is not watched. An unchanged interview
stays
warm and is not recompiled. A reload never touches the Saved session; reset
remains the explicit way to clear it, and a reload that makes the interview
invalid reports the usual typed compile error with the session intact.
Configuration is resolved once at worker startup and is not reloaded, so a
configuration change still requires a restart. Templates are read at render
time, so a template edit is visible to the next render without a reload. See
ADR-0016. One-shot commands and any cache_interview=False caller compile
fresh on every operation and are unchanged.
worker (and its run/journey aliases) also accepts --record PATH --phase NAME to append every execution response's screen outcome to one trace
sidecar, using the same adapter and format as the one-shot --record commands.
A request may override either field with its own "record" and "phase"
keys, so one journey can tag entries per step; an absent or null field uses
the worker's --record/--phase default, and a non-string value is an input
error. Recorded operations use the canonical one-shot spelling (start,
status, refresh, answer, action, seek, eval, exec, vars, seed), so an
alias request such as "operation":"evaluate" records what eval records.
Unlike the one-shot commands, which accept --record only on start,
refresh, answer, action, seek, and exec, the worker accepts a record for any
execution request, so a journey sidecar can also carry status responses and
eval/vars/seed failures recorded against the unchanged active screen.
A status record replays as a read-only screen observation; the rest, like a
one-shot exec record, are compare-only: trace replay reports the first
compare-only step as the typed input error trace step N records compare-only operation '<operation>'.
Protocol-only requests (ping,
reset, shutdown, and malformed frames) are never recorded. A recording
failure is a typed input response (and is logged to stderr), never a silent
ok, so a truncated trace cannot pass unnoticed; when the operation itself
already failed, its response keeps that error and gains a typed
recording-failure diagnostic instead, so the recording failure never erases
what the operation did. A scenario whose
journey reaches only the one-shot-recordable commands is equivalent to
recording it one process per operation, including an automatic seek's expanded
auto steps, its trailing post-walk marker, and a failed answer recorded
against the unchanged active screen.
printf '%s\n' \
'{"operation":"start","record":"run.trace.jsonl","phase":"intake"}' \
'{"operation":"answer","assignments":{"user_name":"Alice"},"record":"run.trace.jsonl","phase":"intake"}' \
'{"operation":"shutdown"}' \
| sim --root ./my-project run --record run.trace.jsonl
docassemble-simulator trace compare golden.trace.jsonl run.trace.jsonl
A worker also serves render, so a warm journey can produce documents without
re-launching the interpreter:
printf '%s\n' \
'{"operation":"render","template":"form.docx","source":"saved","no_assemble":true,"output":"artifact.docx"}' \
'{"operation":"render","template":"form.docx","source":"fresh","no_assemble":true}' \
'{"operation":"shutdown"}' \
| sim --root ./my-project --interview docassemble.my_project:data/questions/main.yml run
The render request mirrors sim render: template (required), source
(saved by default, or fresh, fixture + path, snapshot + path),
no_assemble, output, save_snapshot, expect_missing, and bindings (an
object of NAME: EXPRESSION, or an array of NAME=EXPRESSION strings). The
per-request timeout applies as it does to every operation, and a fixture
source never assembles, exactly as the --fixture flag does. For the same
project and inputs, the response result is the sim render --json result
(same artifact path and shape), with diagnostics and attachments
forwarded. A malformed request or a render failure arrives as a typed
input/render error, never a raw exception or protocol noise. Rendering
reads the worker's saved session and never mutates it beyond what the
sim render source policy already does.
A worker also serves the read-only catalog and project inspection commands, so a warm journey can list, check, and explain its interview without a fresh process:
printf '%s\n' \
'{"operation":"check"}' \
'{"operation":"questions","var":"name"}' \
'{"operation":"index"}' \
'{"operation":"why","variable":"user_name"}' \
'{"operation":"info"}' \
'{"operation":"config"}' \
'{"operation":"shutdown"}' \
| sim --root ./my-project --interview docassemble.my_project:data/questions/main.yml run
questions, index, check, why, info, and config mirror the one-shot
commands: the response result is the sim <command> --json result for the
same project, and a failure is the same typed error kind. The listing and
check requests are scoped to the worker's interview (like --interview),
take the listing filter as var, and accept check's --strict flag as
strict; why requires variable and also accepts strict. None of these
requests creates or mutates a Saved session: check/questions/index only
compile, info/config never touch session state, and why reads the
worker's saved namespace read-only through the existing explanation operation
— without a session it returns the same static not saved report as
sim why. A malformed request, a compile failure through check, or a
missing why variable arrives as the typed input/compile error the
one-shot command reports.
A worker also serves trace compare and trace replay, so an agent can hold
one warm worker for a record → change → replay loop:
printf '%s\n' \
'{"operation":"trace-compare","expected":"golden.trace.jsonl","actual":"run.trace.jsonl"}' \
'{"operation":"trace-compare","expected":"golden.trace.jsonl","actual":"run.trace.jsonl","order":"phased","phases":["intake","close"],"exceptions":"exceptions.toml"}' \
'{"operation":"trace-replay","golden":"golden.trace.jsonl"}' \
'{"operation":"trace-replay","golden":"golden.trace.jsonl","overrides":["2.user_name=Bob"]}' \
'{"operation":"shutdown"}' \
| sim --root ./my-project run
The trace-compare request mirrors sim trace compare: expected and
actual (required sidecar paths), order (ordered by default, or
unordered/phased), missing/extra (strict by default, or allow),
full_text, phases (an array or comma-separated string, only with
order: phased — as with --phases, declaring phases for another order is the
typed input error sim reports), and an optional exceptions TOML path. It
is read-only: --update is the CLI's one golden write path, so the worker has
no --update path, a request carrying update is the typed input error
trace compare update is not a worker capability, and the worker never writes
a golden. For the same sidecars and policy the response result
is the sim trace compare --json result, and a mismatch is the typed
trace-mismatch error whose details are that same report.
The trace-replay request mirrors sim trace replay: golden (required),
overrides (a STEP.FIELD=VALUE array, or a single override string),
artifact_dir (an optional retained attachment output directory), and an
optional interview (it defaults to the worker's own interview, so a golden
for another interview is rejected); update is rejected here too, because the
worker never writes a golden. The replay runs against its own temporary
session directory, so it never reads or writes the worker's Saved session and
never writes the golden; a divergent activated walk leaves no durable session
behind. The response result is the sim trace replay --json result
(clean, steps, first_divergence, counts), and a divergence is the typed
trace-mismatch error whose details are that same report. With artifact_dir,
that report also includes attachments; file lifetime and export failures are
described below.
Replaying through a warm worker reuses the interview the journey already
compiled, because docassemble's compiled-interview cache is process-global and
the replay's short-lived execution reads it rather than recompiling. Before
each replay, and before every questions/index/check request, the worker
runs the same lazy source check its execution operations use (#47), so an edit
between two requests is recompiled rather than served stale; an unchanged
source stays warm. A request whose source no longer compiles pays that compile
once: the refresh's failure is reused by the request's own path, so the worker
reports exactly the envelope the one-shot command reports (a compile error for
questions/index, check's per-interview rows, or a replay's divergence
report). Development measurements on the da110 lane (a scratch
measurement of one recorded default/auto/validation scenario, not a repository
benchmark and not part of any gate) put a warm replay around 15 ms against
about 3 s for a fresh sim trace replay process whose cost is dominated by
interpreter startup and bootstrap, with the one-time compile for a worker that
has not compiled the interview yet around 0.1 s inside that first request.
Replay is still one execution per request: it does not reuse a
previous replay's working state.
The Python client uses the same protocol and lifecycle:
from docassemble_simulator import SimulatorRunner
with SimulatorRunner("./my-project", interview="data/questions/main.yml") as journey:
started = journey.start()
answered = journey.answer({"user_name": "Alice"})
current = journey.status()
Each Python-runner request gets a generated decimal-string identifier ("1",
"2", …) unless its journey/request mapping supplies an identifier. Supplied
identifiers may be strings or integers and are sent unchanged; the response
must echo the same value and JSON type. Other values, including null,
booleans, fractional numbers, arrays, and objects, are invalid and raise
ValueError before the request is written. Every valid request advances the
generated sequence, including supplied-ID requests. A mismatched or malformed
response invalidates the worker; the runner never retries because the
operation's commit status may be uncertain.
The runner discovers the project root when root is omitted, accepts a config
path or mapping, and defaults to a copied source workspace plus a temporary
session root. Configuration is resolved against the original project root before
copying or symlinking, so ancestor layers and relative explicit paths are not
reinterpreted from the temporary directory. Its deadline covers writing the
request and reading one complete newline-delimited response, including partial
or stalled frames. A timeout,
crash, malformed frame, or unexpected response id closes the runner. Timeout responses
are typed as timeout and include only operation context. Use
source_policy="symlink" or isolate=False only when shared source behavior is
intentional. journey.seed("...", assemble=False) is the explicit state-seeding
escape hatch for unsupported browser-only dynamic fields; such screens report
dynamic_fields: true and field_source: code instead of fabricated fields.
The one-shot commands remain available.
Screen traces
Execution commands can record the screens a run passes through and compare a later run against that recording. The trace is an append-only JSONL sidecar; saved sessions are untouched.
docassemble-simulator start --record run.trace.jsonl --phase intake
docassemble-simulator answer user_name=Alice --record run.trace.jsonl --phase intake
docassemble-simulator trace compare golden.trace.jsonl run.trace.jsonl
docassemble-simulator trace compare golden.trace.jsonl run.trace.jsonl \
--order phased --phases intake,documents,download
docassemble-simulator trace compare golden.trace.jsonl run.trace.jsonl --update
--record PATH is accepted by start, refresh, answer, action, seek, and
exec, and takes an optional --phase NAME used by phased comparison. The
persistent worker/run/journey commands accept the same flags (or
per-request record/phase fields) and record an equivalent sidecar. In a
journey, an absent or null record/phase field means "use the worker's
--record/--phase default", and any other non-string value is an input
error; the one-shot commands have no default phase, so there --phase only
ever applies to its own invocation. A recording failure (an uncreatable
destination, a trace from another run) is a typed input response, exactly as
it is an exit-1 input failure for the one-shot commands, so a truncated
journey trace never reports success; a failed operation keeps its own error and
reports the recording failure as a recording-failure diagnostic. Each
record includes sequence, operation, explicit intent, phase, submitted
assignments, outcome, screen outcome, and canonical screen identity with the
rule that produced it. Intent distinguishes ordinary answers (answer),
browser-shaped default submissions (defaults), expanded automatic submissions
(auto), native actions (action, with the control identifier in target),
and seeks (including their target, fresh, and activation context).
The first line is metadata (interview definition, config fingerprint,
docassemble/AssemblyLine/simulator versions, and the trace schema version);
appending to a trace whose metadata disagrees with the current run is an input
error. An automatic seek records one auto entry for every attempted screen,
including a rejected default submission, followed by the post-walk seek result.
A failed answer is recorded against the unchanged active screen with the
rejected assignments, so validation re-asks appear as repeated occurrences of
one identity. A failure with no active screen is recorded as a failure instead:
the entry keeps its error payload and carries an error: identity
(error:unresolved:<variable> for an undefined reference), never a fabricated
screen.
Identity ignores generated Question_<n> names, resolved occurrence paths,
random instance names, and rendered text. Non-generic explicit ids remain
id:<id>; generic-object and indexed-list instances are anchored as
id:<id>@<anchor> so distinct instances do not collapse. It falls back from
explicit block ids through targeted variables, generic-object anchors, and indexed list
targets to a field-variable tuple or a kind/category. The rule used is stored
with every key.
trace compare reports coverage counts (expected, actual, matched, missing,
extra, duplicates) in every mode, so a tolerant policy cannot hide collapsing
coverage. --order unordered treats the run as a multiset; --order phased
requires declared phases in order while tolerating order within each phase.
The declaration comes from --phases when given, else from the golden's
recorded phase_order, else from the order the golden's own entries were
recorded in — so a golden recorded with --phase compares in phased mode
without repeating the list.
--missing allow and --extra allow relax the default strict policies. Field
facts are compared separately from identity; --full-text adds normalized
question and subquestion text. Known differences belong in a reviewed
exceptions TOML passed with --exceptions (each entry names the interview,
phase, identity, category, and reason; order violations and simulator faults
cannot be excused). --update rewrites the expected golden from the actual
trace and is the only write path. A mismatch exits 2 with the full report in
the trace-mismatch error details.
trace replay GOLDEN starts a fresh isolated session, applies each recorded
walk intent (start, status, refresh, answer and its browser-defaults variant,
action, seek, and expanded automatic submissions), and stops at the
first identity, field-fact, flow, or rejected-assignment divergence. Automatic
steps initialize their recorded seek target in one operation-local namespace,
compare each pre-submission screen, and then apply the recorded transaction;
a failed automatic submission is therefore not silently replaced by a seek.
Absent object-selection controls follow the same empty-field handling on replay,
without a partial-submission workaround. An authored rejection is recorded as a
failed automatic attempt and replay must reproduce that rejection, not bypass
validation or continue the walk. A clean replay of such a recording means the
expected stop matched, not that the rejected screen was accepted.
The recorded automatic group is also a hard submission bound: once the recorded
attempts are exhausted, an unexpected screen is observed and compared with the
recorded post-walk outcome instead of being submitted, so a changed interview
cannot authorize extra work or run an unrecorded screen's validation code. The
recorded activation policy is preserved: a non-activated walk does not replace
the replay session's Saved screen, while an activated walk commits its result
once, and only after every recorded attempt and the trailing post-walk outcome
matched. Replay checks identity and field facts before each submission and
checks flow/error results before continuing; a walk that ends before every
recorded attempt ran reports the earliest missing expected step. Any mismatch
stops the operation without saving, leaving the previous namespace, Saved
outcome, and Active seek unchanged. Use
--override STEP.FIELD=VALUE to vary one answer without changing the golden.
Replay validates the interview and configuration
fingerprint metadata, never writes the golden or the user's Saved session, and
exits non-zero on divergence. A replayable golden is a walk recording (start,
status, refresh, answer and its browser-defaults variant, action, seek, and expanded
automatic submissions); a status record is a read-only observation of the
saved screen. A sidecar can also carry records replay cannot reconstruct — the
worker accepts a record for any execution request, and exec is
one-shot-recordable — and those eval/vars/seed/exec records are
compare-only: replay reports the first such step as the typed input error
trace step N records compare-only operation '<operation>' instead of
silently skipping it.
The identity rules and the evidence behind them are documented in docs/screen-trace-identity-study.md.
Retaining replay attachments
sim trace replay golden.trace.jsonl --artifact-dir ./artifacts --json
# Equivalent worker request:
# {"operation":"trace-replay","golden":"golden.trace.jsonl","artifact_dir":"./artifacts"}
Export is opt-in. Replay copies only files exposed as published downloads during
its executed operations, not all generated files, intermediate renderings, or
Saved session data. result.attachments is a manifest using the ordinary
attachment metadata (filename, extension, mimetype, diagnostics,
structure), with path and uri pointing at the retained copy. Repeated
publication of the same source path is exported once; distinct published files
with the same download filename remain distinct entries. Each export creates
a unique replay-* subdirectory beneath the requested directory, with numbered
basenames to prevent collisions. Relative directories resolve from the process
working directory; parent directories are created as needed.
The isolated replay workspace is still removed before the response returns. Manifest files remain readable until you remove them; repeated replays do not overwrite earlier exports. Without an output directory, replay retains no files and adds no manifest. Neither mode changes screen identities, field-fact comparison, match counts, divergence policy, the golden, or your Saved session. A matching trace is not an assertion about document inventory or content: use ordinary client assertions over the manifest and open its DOCX/ZIP files.
On divergence, exported files from the executed prefix are retained and the
manifest is in error.details.attachments, alongside the unchanged replay
report (trace-mismatch, CLI exit 2). Directory, copy, or manifest failures
return an explicit export_error in that report. With no divergence, an export
failure is an input error (CLI exit 1), even if the report's clean is true:
clean describes trace matching only. With both failures, trace-mismatch
keeps precedence and export_error records the additional export problem.
A failed export's manifest lists only completed copies; these remain retained,
but the inventory may be incomplete. Early input errors (for example, invalid
golden metadata) do not run replay or export files. Human output also reports
retained attachments and export errors.
The existing PDF-skip policy remains: a DOCX-backed PDF request can publish real DOCX output, including DOCX members of ZIPs, not a fabricated PDF. This does not establish PDF, browser-JavaScript, payment, Celery, or deployment fidelity.
JSON and exit codes
Every command accepts --json and returns one envelope. By default,
variable-seek diagnostics are bounded to a count, per-kind counts, and
head/tail events; pass --seek-diagnostics capture or
--full-seek-diagnostics for the complete ordered event list. --seek-diagnostics off remains quiet. The human check command ends with checked/ok/failures,
and JSON check results include the same totals. Generated download results
may add a top-level attachments manifest:
{"ok": true, "command": "answer", "result": {}}
{
"ok": false,
"command": "answer",
"error": {"kind": "validation", "message": "...", "details": {}}
}
Exit codes are 0 for success (including interview completion and resolved
lazy seeks), 1 for usage, workspace, configuration, or missing-state
failures, 2 for validation, unresolved-variable, execution, seek, compile,
runtime-compatibility, render, or worker-timeout failures, and 3 for
unexpected simulator faults. Argument-parsing
failures follow the same envelope when --json is present and exit 1; normal
--help output remains a successful exit.
Rendering
docassemble-simulator render form.docx
docassemble-simulator render form.docx --fresh
docassemble-simulator render form.docx --snapshot state.pkl
docassemble-simulator render form.docx --fixture fixture.py
docassemble-simulator render form.docx --no-assemble
docassemble-simulator render form.docx --save-snapshot state.pkl
docassemble-simulator render form.docx --output artifacts/form.docx
docassemble-simulator render form.docx --bind client_name=clients[0].name
A render request has orthogonal concerns:
- source: saved session (default),
--fresh,--snapshot PATH, or--fixture PATH; choose exactly one; - assembly: enabled for saved, fresh, and snapshot sources unless
--no-assemble; fixtures never assemble; - snapshot effect:
--save-snapshot PATHwrites prepared state atomically before template evaluation; - artifact effect:
--output PATHnames the exact DOCX target and is written atomically only after successful evaluation and expectations; - expectation:
--expect-missing VARIABLEmakes that strict undefined error the expected result; - bindings:
--bind NAME=EXPRESSION(repeatable) evaluates an expression in the ephemeral render namespace. Configured defaults come from[simulator.render_bindings], with[simulator.render_bindings."form.docx"]overriding them for one template; command-line bindings win over both. The legacy top-level[render-bindings]spelling supplies the whole table instead of[simulator.render_bindings], so the two do not merge. Bindings are never saved.
Rendering never writes, replaces, or deletes a saved session. Fresh rendering is
ephemeral. Snapshot payloads are versioned and interview-specific. Snapshot and
artifact destinations cannot point into saved-session storage or authored
data/templates trees, alias a render input, or alias one another in the same
request. Writes use unique sibling temporary files and per-destination locks, so
concurrent writers install complete files with final-writer-wins behavior.
Render and include passes run while execution keeps the docassemble thread context active. Structural DOCX failures report the requested template, error type, and available paragraph/line; an included filename is reported only when the runtime preserves it reliably. Source and included templates are never rewritten or repaired.
Authoring a fixture
A fixture is a Python file exec'd into a fresh render namespace; it is not a
module and is not imported. A fixture source never assembles, so a
template-only check needs no start, session, or saved state.
# .config/simulator/fixture.py
from pathlib import Path
from docassemble.base.util import DALocalFile
import docassemble.my_project
PACKAGE = Path(docassemble.my_project.__file__).parent
def build_namespace():
return {
"client_name": "Ada Lovelace",
"fee": currency(1250),
"month": format_date("2026-10-01", "MMMM yyyy"),
"letterhead": DALocalFile(PACKAGE / "data/static/letterhead.docx"),
}
for key, value in build_namespace().items():
globals()[key] = value
docassemble-simulator render fee_agreement.docx \
--fixture .config/simulator/fixture.py --output out.docx
- The namespace starts from
parse.INITIAL_DICT(essentially empty) and is the same namespace the prepared interview supplies at render time. Helpers such ascurrency,format_date, andordinal_numberresolve in it; do not stub them, because stubbing hides the real formatting behavior a template must be tested against. - Names must be module-level globals. The
build_namespace()-returns-a-dict shape assigned item by item intoglobals()is the convention that keeps every value visible to the template. __file__is not defined. Anchor repository-relative paths withPath(docassemble.<package>.__file__)(as above) or an absolute path.- File inputs (an uploaded letterhead, a static PDF) need
DALocalFile(path)soinclude_docx_templatetakes its.path()branch instead of a package-template lookup. - A failing fixture reports its path, error type, and full Python traceback in
the error's
detailsasfixture,error_type, andtraceback; the traceback's failing line is attributed to the fixture file.
DOCX output is supported. Generated interview attachments are stored under
.simulator/files/; completion links use real local file:// URIs and JSON
includes only files actually published as download results. Intermediate
numbered files are not counted as published attachments. Published DOCX files
are checked for nested WordprocessingML paragraphs and any strict-structure
finding appears on the attachment as a non-mutating diagnostic.
Generated PDF conversion remains intentionally stubbed: the simulator does not
invoke an external converter or fabricate a PDF. When an AssemblyLine document
or bundle asks for a generated PDF and has a DOCX rendering, the simulator
skips the PDF conversion, returns the real DOCX artifact (including merged DOCX
and DOCX ZIP bundles), and records a pdf-skip diagnostic. PDF-only
attachments still raise PDFConversionUnavailable.
Workspace configuration and defaults
Runtime files are entirely ignored under .simulator/ (sessions, generated
YAML, and render output). Authored files live under .config/simulator/:
.config/simulator/config.toml # committed declarative settings
.config/simulator/config.local.toml # optional, ignored override
.config/simulator/config.py # optional authored seed code
.config/simulator/fixture.py # optional fixture for explicit --fixture
.simulator/config-effective.yml # generated TOML-to-YAML handoff
.simulator/sessions/ # trusted-local pickle state
A fixture is used only when selected explicitly with --fixture. A
zero-config run uses an in-process fake Redis, simulator-local file storage,
debug=true, localhost, en_US, US, the local timezone (falling back to
America/New_York), and foreground background actions. No server database is
substituted: the simulator's own state lives in .simulator/sessions/, and
database-backed AssemblyLine session features are disabled by default rather
than pointed at a nonexistent PostgreSQL. These are local substitutes, not
PostgreSQL, Redis, Celery, or server storage. DOCX rendering is supported;
generated PDFs are skipped in favor of the DOCX artifact and no external
converter is invoked.
| Default | Behavior | Category |
|---|---|---|
| server database | not substituted; .simulator/sessions/ holds CLI state |
capability boundary |
| AssemblyLine session metadata | update session metadata: false; PostgreSQL-backed upsert off |
pass-through default |
| Redis | in-process FakeRedis |
stub |
| File storage | files under .simulator/files/ |
local filesystem |
debug, host, locale, country |
true, localhost, en_US, US |
pass-through defaults |
| timezone | local timezone, then America/New_York |
pass-through default |
| background actions | foreground, no Celery worker | stub |
| seek diagnostics | capture (default), structured variable-seek trace |
local capture |
| DOCX/PDF | DOCX supported; generated PDFs skipped in favor of DOCX, no converter invoked | capability boundary |
Configuration is merged from lowest to highest precedence: the global
$DOCASSEMBLE_SIMULATOR_CONFIG (or
$XDG_CONFIG_HOME/docassemble-simulator/config.toml), then every matching
project file while walking from the filesystem root to the package root. At a
level, local files win. Supported project candidates are:
simulator.local.toml .simulator.local.toml simulator.toml .simulator.toml
simulator/config.toml .simulator/config.toml .config/simulator.toml
.config/simulator/config.local.toml .config/simulator/config.toml
The effective file is generated at .simulator/config-effective.yml; TOML
jinja-data is normalized to docassemble's jinja data. --config PATH adds
a final YAML (or TOML) override. Sessions are isolated per effective
configuration, so runs under different overrides never share state; the
config command's --json output includes the config_fingerprint that
scopes them. The config command prints redacted values and supports
--json; credentials are never printed or committed.
Simulator-owned settings belong under [simulator], including
missing_runtime = "install"|"disabled", background_actions = "foreground"|"disabled", seek_diagnostics = "capture"|"off", offline,
and render_bindings (the legacy top-level [render-bindings] table is also
accepted). Capture is on by default; seek_diagnostics = "off" (or
--seek-diagnostics off on any command) suppresses the structured seeking
trace and skips forcing the interview's debug trace for runs whose only goal
is to verify the interview completes end to end. Lazy-seek log noise stays off
stdout/stderr in both modes. Docassemble pass-through
settings such as timezone, jinja data, and supported database
or Redis values are visible to interview code but do not change simulator
policy. A package's config.py remains optional seed code and is not needed
for the standard local substitutes.
Example:
# .config/simulator/config.toml
timezone = "America/Chicago"
[jinja-data]
my_label = "Family"
[simulator]
background_actions = "foreground"
seek_diagnostics = "capture"
[simulator.render_bindings]
x = "clients[0]"
# Alternatively, the legacy top-level spelling is [render-bindings].
# Opt back in to AssemblyLine's PostgreSQL-backed session metadata when this
# project configures a real database. See docs/runtime-compatibility.md.
["assembly line"]
"update session metadata" = true
Use render form.docx --bind x=clients[1] to override a configured binding.
A template-specific table such as [simulator.render_bindings."poa.docx"]
overrides the default binding map for that template. Command-line bindings win
over template-specific config, which wins over the default binding map. The
legacy top-level [render-bindings] spelling is an alternative to
[simulator.render_bindings] that supplies the whole table; the two spellings
do not merge. Bindings are simple names evaluated in an ephemeral render
namespace and never saved.
Optional config.py executes after namespace rehydration and before authored
flow, evaluation, or render work. Use it for deterministic seed data and
server-scoped objects, not to emulate external services.
The simulator generically calls the compiled interview's non-pickleable
population mechanism on every prepared namespace. This restores
docassemble.base.util exports and declared modules:/imports: entries for
saved, fresh, snapshot, fixture, and no-assemble paths. Standard helpers such as
currency, redact, and date/number/text helpers do not belong in a package
seed. Functions outside standard utility exports must still be declared by the
interview.
Top-level docassemble object roots are registered for reference resolution after
rehydration and seed execution. Packages should still give server-scoped seeded
objects stable instanceName values and seed all data their templates consume.
Fidelity and deliberate limits
The simulator preserves interview compilation, mandatory assembly, seeking,
screen descriptions, answer coercion, validation order, marking, seed timing,
docassemble context, template evaluation, include passes, and intentional PDF
policy. Supported background_action() events run immediately in the current
foreground context by default; this does not simulate worker isolation, queue
latency, retries, or process failures. background_actions = "disabled" retains
a waiting/stub task for diagnosis. The simulator substitutes no SQL database at
all: AssemblyLine's PostgreSQL-backed session metadata, saved answer sets, and
interview list are disabled by default and remain deployment-only unless the
project configures a real database and opts back in. Browser HTML, uploads,
external services, PostgreSQL, real Redis/Celery behavior, and PDF/download
verification remain deployment or staging responsibilities.
Development
Use the default command for everyday development:
mise run test
It selects only fast tests, excluding real-runtime and external corpus tests
regardless of which runtimes or fixtures are installed locally. mise run test:fast remains supported with the same selection, and the commit hook
continues to use that fast lane. Neither command is a complete release gate.
Run slow validation explicitly:
mise run test:runtime # Two-family runtime compatibility gate (provisions both lanes)
mise run test:corpus # External corpus canary gate (provisions the modern lane, validates fixtures)
mise run test:all-da # Aggregate lane: whole suite with both runtime families wired
mise run test:runtime is the runtime compatibility gate. It provisions the
isolated .venv-da110 (docassemble 1.10.x) and .venv-da19 (1.9.x)
environments, points DASIMULATOR_REAL_PYTHON / DASIMULATOR_REAL_PYTHON_19
at their interpreters, and runs the real-runtime contract tests, including the
screen-trace acceptance scenario, with the external corpus excluded. By
default it uses min(4, CPU count) pytest-xdist workers, falling back to two
when no CPU probe answers; set DASIMULATOR_RUNTIME_WORKERS=1 for a serial
comparison/debug run, or set it to any count in the accepted range. The count
is bounded to 1–8 rather than using every detected CPU, and every worker
inherits both explicit family interpreter paths. Each runtime test uses its
own pytest temporary workspace for sessions, files, traces, and packages, so
parallel workers do not share writable interview state. The gate prints
per-family pass/fail/skip counts in either mode. A lane that is missing, cannot
import the runtime, or belongs to the wrong family fails before pytest runs,
and a family that contributes no passing coverage fails the session, so the
gate can never pass by skipping its intended coverage. mise run sync:da19 /
mise run sync:da110 (re)provision one family without running the gate.
Both gate scripts use the shared scripts/runtime-lane-preflight for lane
executability, importability, family validation, and version reporting; their
selection, fixture, worker, and false-green policies remain separate.
mise run test:corpus is the external corpus canary gate. It provisions the
modern .venv-da110 lane (sync:da110), validates that interpreter and the
external docassemble-yaml fixture
checkout before pytest, then runs the corpus-marked contract tests against
that lane: the demo corpus runner canary and the demo package compile check.
The checkout defaults to the sibling ../docassemble-yaml/lsp/tests/fixtures;
set DASIMULATOR_DEMO_FIXTURES to use another location. A missing lane, a
fixture checkout that is absent or incomplete, an empty selection, or a suite
in which every corpus test skipped fails the command with recovery
instructions, so it cannot pass by skipping the canary it exists to run.
Prepared NLTK data is reused from a stable, version-aware cache; set
DASIMULATOR_NLTK_CACHE_DIR to choose its root (default
.simulator/nltk-cache), and a new generation is published only when the
lane's Python or runtime identity changes. The direct runner's
--refresh-nltk-cache flag remains the explicit refresh path. Corpus case
concurrency is bounded and configurable through DASIMULATOR_CORPUS_JOBS
(default 4); the runner's own default is serial. This task is deliberately the
canary over the selected contract, not a run of the whole external corpus: run
scripts/test-demo-corpus directly (with --match, --shard, or no filter)
to exercise every example.
To run every test in the current environment, use uv run pytest; runtime and
corpus checks skip when their prerequisites are absent. The gates themselves
fail instead of skipping, so use the provisioned cross-family gate and the
corpus gate for complete slow validation.
Real-docassemble fidelity is covered by a separate lane that runs the CLI in a
target package's interpreter (which supplies docassemble and python-docx):
scripts/test-real-runtime /path/to/target/package/.venv/bin/python
Pass a second interpreter to also run the cross-family contract against docassemble 1.9.x:
scripts/test-real-runtime /path/to/1.10/package/.venv/bin/python \
/path/to/1.9/package/.venv/bin/python
The repo can provision both target interpreters itself from the da19 /
da110 dependency groups in pyproject.toml, which also pin the tested
docassemble.AssemblyLine / docassemble.ALToolbox pair for each family
(see docs/runtime-compatibility.md):
mise run test:all-da
This syncs isolated .venv-da19 (docassemble 1.9.x) and .venv-da110
(1.10.x) environments, including the AssemblyLine compatibility fixture and
target smoke path, and runs the whole suite once with both lanes wired up
(DASIMULATOR_REAL_PYTHON* pointing at those interpreters), so every
cross-family contract test exercises the modern and legacy runtimes. It is the
aggregate lane: fast tests plus both families, plus the corpus tests when their
fixtures are available. The compatibility gate proper is mise run test:runtime, which runs the same two lanes against the runtime contract only.
Use mise run sync:da19 / mise run sync:da110 to (re)provision one family
without running the suite.
There is no CI runner for the 1.9.x lane yet: mise run test:runtime is the
manual regression gate that must pass before changes to the runtime
compatibility layer merge (issue #1, story 23).
The real-runtime tests use the current pytest interpreter when the runtime
is installed; set DASIMULATOR_REAL_PYTHON to use a separate target
environment and DASIMULATOR_REAL_PYTHON_19 for the 1.9.x lane. Without
either runtime, they skip; a configured target that
cannot run the tests fails clearly. The fast suite's stubbed runtime tests
never require docassemble. Generated-PDF download screens are satisfied with
DOCX artifacts locally; real PDF validation remains a deployment concern, and
the simulator gate is DOCX artifact rendering and content inspection.
The sibling docassemble-yaml example corpus has an isolated compile/start lane:
scripts/test-demo-corpus \
--fixtures /path/to/docassemble-yaml/lsp/tests/fixtures \
--python /path/to/package/.venv/bin/python \
--compile --start --output .simulator/demo-corpus-results
mise run test:corpus runs the canary subset of this runner behind the lane
and fixture checks described above; invoke scripts/test-demo-corpus directly
for a focused or full run.
The runner stages docassemble.base and docassemble.demo, overlays all 966
examples/*.yml fixtures, runs each phase in a subprocess with a timeout and a
network-denial sandbox, and writes results.jsonl, summary.json, and
summary.md. Reviewed canonical provenance is checked from
tests/demo_corpus_provenance.toml; regenerate it with
--write-provenance-manifest PATH when the fixture/runtime pair is intentionally
updated. Missing optional NLTK corpora remain a dependency boundary by default;
use --prepare-runtime-data explicitly when provisioning them is intended.
Prepared data is reused in a versioned cache; use --nltk-cache-dir PATH to
choose its root or --refresh-nltk-cache to publish a new generation. Use
--jobs N for bounded parallel case execution (serial by default),
--match, --shard INDEX/COUNT, and
--expectations tests/demo_corpus_expectations.toml for focused or reviewed
runs. Set DASIMULATOR_DEMO_FIXTURES for the default fixture root and
DASIMULATOR_DEMO_PYTHON for the interpreter used by the script itself.
Releases are cut with mise run release. A release first visibly runs the
slow two-family runtime compatibility gate (mise run test:runtime, which
also runs the CPython 3.10 fast lane, excluding the external corpus), then
bumps the patch digit, pushes the commit and annotated tag, and publishes the
GitHub release with notes generated from the commits since the previous tag.
Publishing the GitHub release triggers
.github/workflows/python-publish.yml, which verifies the release tag matches
project.version, builds the wheel and sdist, and uploads them to PyPI with
trusted publishing. A failed or interrupted gate leaves version files and
release Git state unchanged. Run mise run test:corpus separately when the
external corpus gate is needed; it is not part of release validation.
mise run bump still bumps and pushes without creating a release, and
scripts/release --dry-run previews the next bump without provisioning
interpreters or running either slow gate.
License
MIT. See LICENSE.
Metadata
Release files for docassemble-simulator 26.10.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| docassemble_simulator-26.10.9.tar.gz | 202.1 kB | Without provenance | Details for docassemble_simulator-26.10.9.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| docassemble_simulator-26.10.9-py3-none-any.whl | Python 3 | none | any | Details for docassemble_simulator-26.10.9-py3-none-any.whl |
Total release size: 394.1 kB
Release files / docassemble_simulator-26.10.9.tar.gz
| Download URL | docassemble_simulator-26.10.9.tar.gz |
|---|---|
| Size | 202.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
367268febabdc0fd9da214a843c780475b47e0cc7b689228721a5160f18623ad
|
|
BLAKE2b-256 checksum How to use checksums |
06d58e70b391a49c1f8acb45b5f10f08a50cf9292da7ce2a6cef9de9656c31bf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / docassemble_simulator-26.10.9-py3-none-any.whl
| Download URL | docassemble_simulator-26.10.9-py3-none-any.whl |
|---|---|
| Size | 192.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ce01834e88ec7c83d2dcf344c705a933605cd8ab32fdfb0b1a7531d6489e87f0
|
|
BLAKE2b-256 checksum How to use checksums |
3c178f4bf34ecaebe0db00a2671ebd41dfd41c79d1f0f077979a1d34af691be9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for docassemble-simulator 26.10.9
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.