I made a small module for inserting changing titles and bodies into a fixed procedure. It compiles data without performing entry. The same function serves a Python entry point and a notebook, letting readers inspect missing-input rejection and preservation of Unicode, quotes and literal braces before connecting any real executor.

Keep the definition and current values in separate documents

The recipe document has a name, schema version, parameter names and steps. The input document holds the present title and body. Compilation returns a new plan: step kinds and field identifiers remain fixed while parameter positions in value strings receive current text.

This is not a copy of the full Northstar recipe schema. It permits two descriptive step kinds and performs neither action. There is no execution callback. It isolates the decision about fixed procedure and changing content for testing, rather than offering a browser-driver-compatible replacement for the original runtime.

Require every current input explicitly

Input keys must exactly equal the declared parameter names. Missing and unknown keys are rejected, and every value must be a bounded string. The example stores no historical title or body defaults to fill an omission silently.

An empty string can be a deliberate current value, so it is not automatically forbidden. Whether a particular business operation accepts an empty body needs an additional rule. Explicitly supplied structure and suitable content are different conclusions. The module therefore does not turn input-shape validation into a judgment about editorial quality or present business intent.

Substitute once without interpreting the inserted text again

Only declared double-brace positions in the definition are replaced. Braces inside the supplied input remain literal data rather than a second template. Quotes, backslashes, newlines and Korean remain string content; compilation neither concatenates executable JSON code nor evaluates the supplied text.

Malformed template positions and undeclared parameters are rejected, as are values expanded beyond the bound. This is not a sanitizer for arbitrary HTML rendering. Passing the resulting text into another context such as HTML or SQL needs handling suited to that context. Preserving data during compilation and safely presenting it later are separate responsibilities.

Explore the implementation

Complete independent synthetic implementation

"""Compile a narrow synthetic recipe to data. No browser or action executor."""
import hashlib,json,re
NAME=re.compile(r'[A-Za-z][A-Za-z0-9_]{0,40}')
SLOT=re.compile(r'\{\{([A-Za-z][A-Za-z0-9_]*)\}\}')
class PlanError(ValueError):pass

def exact(value,keys):
    if type(value) is not dict or set(value)!=set(keys):raise PlanError('INVALID_KEYS')

def compile_recipe(recipe,inputs):
    exact(recipe,['schema_version','name','parameters','steps'])
    if type(recipe['schema_version']) is not int or recipe['schema_version']!=1:raise PlanError('INVALID_VERSION')
    if not isinstance(recipe['name'],str) or not recipe['name'].strip() or len(recipe['name'])>80:raise PlanError('INVALID_NAME')
    parameters=recipe['parameters']
    if type(parameters) is not list or len(parameters)>20:raise PlanError('INVALID_PARAMETERS')
    if any(not isinstance(k,str) or not NAME.fullmatch(k) for k in parameters):raise PlanError('INVALID_PARAMETERS')
    if len(set(parameters))!=len(parameters):raise PlanError('DUPLICATE_PARAMETER')
    if type(inputs) is not dict or set(inputs)!=set(parameters):raise PlanError('CURRENT_INPUTS_REQUIRED')
    if any(not isinstance(v,str) or len(v)>20000 for v in inputs.values()):raise PlanError('INVALID_INPUT')
    steps=recipe['steps']
    if type(steps) is not list or not 1<=len(steps)<=20:raise PlanError('INVALID_STEPS')
    resolved=[]
    for step in steps:
        exact(step,['kind','field','value'])
        if step['kind'] not in ['fill','verify']:raise PlanError('UNSUPPORTED_STEP')
        if not isinstance(step['field'],str) or not NAME.fullmatch(step['field']):raise PlanError('INVALID_FIELD')
        text=step['value']
        if not isinstance(text,str) or len(text)>20000:raise PlanError('INVALID_TEMPLATE')
        remainder=SLOT.sub('',text)
        if '{{' in remainder or '}}' in remainder:raise PlanError('INVALID_TEMPLATE')
        def substitute(match):
            key=match.group(1)
            if key not in inputs:raise PlanError('UNDECLARED_PARAMETER')
            return inputs[key]
        value=SLOT.sub(substitute,text)
        if len(value)>20000:raise PlanError('EXPANDED_VALUE_TOO_LARGE')
        resolved.append({'kind':step['kind'],'field':step['field'],'value':value})
    raw=json.dumps(recipe,ensure_ascii=False,sort_keys=True,separators=(',',':'))
    return {'definition_hash':hashlib.sha256(raw.encode('utf-8')).hexdigest(),
            'steps':resolved,'compiled_only':True,'executed':False}

Compare actual Python entry and notebook-cell results

The command-line entry reads the same JSON documents and calls the compiler. Notebook code cells call that function too. A test runs the CLI in a Python subprocess and executes the trusted code cells authored inside this package as Python, comparing their plans. It starts neither a browser nor a separate Jupyter kernel.

Twenty-two tests cover missing and unknown inputs, types, duplicate parameters, unsupported steps, old-reference fields, malformed templates and expansion bounds. They also distinguish a changing input from a changing definition digest. These are actual Python checks, not verification of rendered notebook UI or the Jupyter kernel protocol.

Use five records to show the difference between inputs

Examples compile a first article, a second article and literal Unicode and braces, then record missing- and unknown-input rejection. The selector reads five records generated by local Python. Seeing fill or verify in a plan does not mean the visitor screen was changed or observed.

Returned metadata explicitly says compiled-only and not executed. The ZIP is separately extracted, checked against source hashes and tested with the same twenty-two cases. Both language record selectors are checked using DOM stubs. Compiling a plan, displaying its record and performing external work retain separate completion meanings.

A read-only viewer of synthetic local records

Connect real execution through an additional contract

Full code, JSON examples, Python entry, notebook, tests, record generation and README permit modification and reuse with the standard library. The notebook expects this package as its working directory. The pure compiler performs no file or network reads, while the supplied example entry does read its local JSON documents.

Real integration needs fresh semantic observations, current account and permission checks, dispatch, outcome confirmation and durable duplicate protection. The digest identifies sorted JSON and exact definition strings; it proves neither approval nor safety. I share the module so the conversion from current inputs into a procedure can be reviewed independently before building the execution layer.

Download code, tests and records
Python standard library, synthetic data, modification and reuse permitted