Stdlib Applier Over Pip Dependency
Problem¶
A toolkit feature needs to apply a JSON Patch (RFC 6902) on top of a base settings file. The reference library is jsonpatch on PyPI. Adding it would let SPEC-130's apply_profile_patch.py be a 30-line wrapper.
But the toolkit otherwise has zero runtime third-party Python deps — end users don't run pip install for any other feature. Adding one for --profile=power-user would:
- Force every opt-in user to manage a Python environment (venv, system pip, brew python, etc.) where they previously needed none.
- Re-open a supply-chain attack surface (jsonpatch + transitive deps) that SPEC-114 specifically minimized.
- Make
setup.sh --verifychecks depend on an external package being installed in a path the script can find.
Solution: From-Scratch Stdlib Applier¶
Write a minimal pure-stdlib implementation that handles exactly the slice of the standard the toolkit actually uses. SPEC-130's apply_profile_patch.py:
- ~200 LoC total, single file, no imports outside
argparse/json/os/sys/tempfile - Handles only
add/remove/replaceops (the spec scope explicitly forbidscopy/move/test) - Handles JSON Pointer per RFC 6901 (escape sequences
~0/~1, array index,-for append) - Atomic write via
tempfile.mkstemp+os.replacewith cleanup on failure - Rejects out-of-scope ops (forbidden ops, root-pointer ops) with a clear error and a non-zero exit
The applier ships as a regular Python file under scripts/lib/, invoked by setup.sh via python3 scripts/lib/apply_profile_patch.py --base ... --patch ... --out .... End users need only python3 (already a toolkit prereq) — no pip step.
When to Apply¶
This pattern fits when all of these hold:
- The standard is well-defined and stable (RFC, IETF draft, ISO, ECMA, etc.).
- The toolkit's usage covers a small, knowable slice — not "all of YAML 1.2", but "key-value pairs under one root key".
- The reference library would be the only dep of its kind (no synergy with existing deps).
- The end-user-facing toolkit promise is "Python 3 is the only Python requirement".
It does not fit when:
- The standard is large and the toolkit uses a wide slice (then library wins on correctness).
- The reference library is already a transitive dep of something else the toolkit installs.
- The slice the toolkit uses is at the edge of the spec (where corner cases bite — e.g., full YAML 1.2 anchor / merge-key support, or full JSONPath wildcard semantics).
Trade-off Discipline¶
The from-scratch implementation must be strictly scoped: forbid the parts of the standard the spec doesn't need, and reject them explicitly rather than silently ignore. SPEC-130's applier:
- Rejects
copy/move/testwithforbidden op ... — only add, remove, replace are allowed (SPEC-130 AC-3.4). - Rejects root-pointer ops (path
"") without of scope for SPEC-130.
The scope boundary is the safety boundary. A "harmless" extra branch (SPEC-130 SIMP-1 originally allowed root-replace "but not used") is debt: it widens the test surface, hides intent, and tempts future contributors to lean on it without the spec.
Anti-Pattern¶
# Anti-pattern: import the library, lose the toolkit's no-pip promise
import jsonpatch
patched = jsonpatch.apply_patch(base, patch_ops)
# end user now needs `pip install jsonpatch` plus venv management
Pattern¶
# Pattern: stdlib slice that does exactly what the spec needs
from __future__ import annotations
import argparse, json, os, sys, tempfile
ALLOWED_OPS = ("add", "remove", "replace")
def _apply_op(doc, op):
if op.get("op") not in ALLOWED_OPS:
raise ValueError(f"forbidden op {op.get('op')!r}")
# ... narrow, explicit handling of the spec slice
Why It Compounds¶
Future SPECs that need to consume small slices of a standard (e.g., a --profile=ci patch, a JSONPath-filtered config view, a YAML-front-matter rewriter) can reach for this pattern instead of re-debating the dependency question every time. The toolkit's "Python 3 is enough" promise stays intact.
See Also¶
- defensive-toolkit-install.md — the broader pattern of treating end-user systems as untrusted constraints.
- out-of-band-shell-ipc-delimiter.md — another minimalism-via-stdlib pattern.