Configuration Review and Validation¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Functional Programming"]
section["Data First Apis Expression Style"]
page["Configuration Review and Validation"]
capstone["Capstone evidence"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
orient["Orient on the page map"] --> read["Read the main claim and examples"]
read --> inspect["Inspect the related code, proof, or capstone surface"]
inspect --> verify["Run or review the verification path"]
verify --> apply["Apply the idea back to the module and capstone"]
Configuration becomes useful domain data only after an untyped caller has been checked. A frozen dataclass prevents mutation after construction; it does not prove that a JSON mapping, command-line token, or rule name is valid.
Module 02 therefore has two distinct responsibilities:
boundary_rag_configvalidates untyped RAG policy and constructsRagConfig;boundary_app_configparses CLI concerns, delegates RAG policy to that shared validator, then adds paths and debug flags.
The important review question is not merely “is config immutable?” It is “can every entry route construct the same valid policy or return at the same boundary?”
Follow a valid value¶
The canonical parser lives in
capstone/module-reference-states/module-02/src/funcpipe_rag/api/config.py.
For this input:
the parser checks the raw fields, converts the rule sequence to a tuple, and only then constructs:
The result is frozen, structurally comparable policy. get_deps later interprets
the cleaning names by looking them up in RULES and binds them into a cleaner.
This split gives each value a precise role:
| Value | Meaning | Must not own |
|---|---|---|
| raw mapping | untrusted boundary representation | domain invariants |
CleanConfig |
ordered cleaning policy names | filesystem or CLI parsing |
RagEnv |
validated numeric run policy | coercion from arbitrary objects |
RagConfig |
complete pure-core policy | dependency execution |
RagCoreDeps |
selected callable implementations | validation of raw names |
Validate in dependency order¶
boundary_rag_config uses this order:
- retrieve
chunk_size, using the public default when absent; - require an actual integer and reject
boolexplicitly; - require a positive value before calling
RagEnv; - retrieve
clean_rules, using the default ordered tuple when absent; - require a list or tuple whose every element is a string;
- reject names absent from the
RULESregistry; - convert the accepted sequence to a tuple;
- construct frozen config and return
Ok.
Each earlier check protects a later operation. Range validation protects
RagEnv; registry validation protects make_cleaner.
Python's boolean trap is an API issue¶
Python reports:
This check is therefore too weak:
The boundary needs both conditions:
Otherwise True can silently mean a chunk size of one. Type annotations do not
protect a value that entered through Mapping[str, object].
Do not coerce by accident¶
The mapping boundary deliberately does not call int(value). These values carry
different information:
| Raw value | Mapping-boundary result | Reason |
|---|---|---|
4 |
Ok |
exact accepted runtime type and range |
True |
Err mentioning bool |
boolean policy is ambiguous |
0 or -1 |
Err requiring positive integer |
range fails before construction |
"4" |
Err requiring integer |
string coercion is not this API's contract |
| missing field | Ok with 512 |
an explicit default is part of the contract |
Silent coercion would make True, 3.8, and "4" look like variations of the
same request and would remove the information needed for a useful error.
Rule names require semantic validation¶
CleanConfig stores names in order so a learner can inspect and compare policy.
The dataclass alone cannot know whether a name has an implementation.
That value is structurally valid but cannot be interpreted by make_cleaner;
direct lookup in RULES would raise KeyError. The boundary must compare names
with the registry first and return an Err that includes the unknown names and
available choices.
| Raw cleaning policy | Outcome |
|---|---|
["strip", "upper"] |
Ok(CleanConfig(("strip", "upper"))) |
("upper", "lower") |
Ok; order is preserved |
"strip,upper" |
Err; the mapping API requires a sequence |
["strip", 1] |
Err; every item must be a string |
["strip", "unknown"] |
Err; dependency selection cannot succeed |
The CLI must share the policy parser¶
shells/rag_main.py owns command-line syntax. argparse converts
--chunk_size 4 to an integer and the shell splits comma-separated rule names.
The shell then calls:
Only after Ok does it use dataclasses.replace to add debug policy and wrap the
result with input and output paths in AppConfig.
This delegation prevents drift. A former direct-construction route could accept an
unknown name, return Ok(AppConfig), and fail later while get_deps selected the
cleaner. That was not a Result-bearing boundary; it was deferred failure.
argparse still owns malformed command-line syntax. For example,
--chunk_size nope produces its normal parser error rather than an Err value.
Once CLI tokens have been parsed, range and rule policy belong to the shared
validator.
Executable evidence¶
The focused data API proof covers valid construction, boolean rejection, and both nonpositive ranges. It also proves that two different cleaning orders produce different, deterministic results:
The concrete shell proof covers the cross-entry-point law: an unknown CLI cleaning
rule returns Err before reading input or selecting dependencies.
The exact tests are in the completed Module 02 reference state under
tests/learning/. The same range and CLI validation source corrections are carried
through every later reference state and the live capstone.
Review checklist¶
Before accepting a configuration change, answer:
- Is the new field boundary syntax, domain policy, debug policy, or dependency selection?
- Which constructor invariant or registry lookup requires validation first?
- Do all entry points delegate to one semantic parser?
- Is defaulting explicit, and is coercion either documented or rejected?
- Does a focused test observe both the returned result and whether execution started?
- Has the public law been carried into every cumulative later state?
Configuration-as-data is justified when these answers make variants easier to inspect and test. A plain parameter remains better when there is only one local, already typed value and no boundary policy to coordinate.
Continue with Callbacks to Combinators.