A missing field should not fail an import, and a filter you typed should be used
1. Thirty seconds
Section titled “1. Thirty seconds”Two things went wrong on the ordinary import wizard — the classic column-table path, not the beta live mapping workbench.
| What was wrong | What it does now |
|---|---|
| A property mapped from a field that some records simply do not have made those records fail to render | The property is left off that note. The note is written |
| The row filter you typed was carried into generation only in workbench mode, and silently dropped otherwise | The filter is carried on both paths, as a per-run instruction |
Both are the same failure shape this project keeps ruling out: acting confidently on an incomplete picture instead of saying what it actually knows. A dropped filter produced more notes than you asked for and said nothing. A missing field produced an error naming a property, not a cause you could act on.
Neither change loosens anything strict. File names, identity, folder structure, body content, links, and hand-written canonical recipes all behave exactly as before, including still failing loudly when a required value is missing.
2. Two minutes: one source row
Section titled “2. Two minutes: one source row”Say you import a JSON export of technique records. Most records have a revoked field; some do not. You type revoked=false in Keep only matching records on the record-selection screen.
Here is a single record that has no revoked field at all, and you mapped revoked as a note property:
| Before | Now | |
|---|---|---|
| Your typed filter | dropped on the ordinary path; every record was imported | applied during generation; only matching records become notes |
A record missing revoked | the row failed to render, naming the property | the note is written without a revoked property |
A record with revoked: false | — | keeps the value. False and zero are values, not absence |
| A record missing the field you mapped as the file name | error | still an error. A selected row is never quietly skipped to make an import succeed |
| A hand-written canonical recipe with a required property | strict error | still a strict error. Only wizard-generated property templates changed |
Why false and zero matter. Treating “absent” and “false” as the same thing is how a compliance note quietly loses the fact that a control was explicitly marked not applicable. Omission is for absence — a key that is not there, a null, an empty value — and nothing else. The existing parser’s value conversions are unchanged, so this promises omission behavior, not raw type fidelity.
One honest gap you will see in the interface. The Step 3 preview still samples your source unfiltered, so a preview row may not survive your filter. Rather than rebuild preview, the screen now says so plainly when a filter is set: Preview uses unfiltered source samples. Your filter is applied during generation. The authoritative filtering happens in generation, where an unknown field name or a filter that matches nothing reports an actionable error instead of quietly writing the wrong set of notes.
3. Why this matters for real use
Section titled “3. Why this matters for real use”An import that produces too many notes is worse than one that fails. A failed import is obvious and you fix it. A silently unfiltered import lands hundreds of notes you did not want into a vault where import-set ownership now has to account for every one of them, and you find out weeks later.
The missing-field failure has a different cost: it makes heterogeneous real sources unimportable. Published exports are not rectangular. STIX, OSCAL and vendor JSON routinely carry fields on some objects and not others, and demanding every mapped field on every record means the tool works on tidy spreadsheets and refuses the actual published artifacts it exists to ingest.
The limit worth saying in the same breath: an absent property means the source did not supply one, not that the answer is no. That is the same reading discipline as an absent source fingerprint, and the same one the closure-cache fix established: an incomplete answer must not be dressed as a complete one.
4. What was accepted, rejected and deferred
Section titled “4. What was accepted, rejected and deferred”| Decision | |
|---|---|
| Accepted | Property templates synthesized by the legacy shim are optional by default. Absent, null and empty values are omitted; the renderer’s existing optional and omission semantics do the work, unweakened globally |
| Accepted | An explicit omitIfEmpty: false on a mapping keeps that property strict. The escape hatch stays available per property |
| Accepted | False and zero are preserved under the existing parser and renderer conversions. Only absence omits |
| Accepted | Ownership of managed properties stays declaration-based. An omitted declared property still removes a stale managed value on a permitted refresh, and user_preserve still wins. Merge authority and write decisions are untouched |
| Accepted | The wizard’s filter becomes an already-translated per-run predicate passed through the generation boundary, applied immutably to a copy of the resolved recipe before hashing and source-stage preparation, so joins, transforms and fields the engine does not interpret survive |
| Accepted | A nonblank filter replaces an existing declared predicate, matching workbench behavior. A blank field leaves a canonical predicate intact. No implicit combining of the two |
| Rejected | Turning ordinary imports into recipe-override imports to carry the filter. That branch decides provenance recipe identity; changing it would move recipe ids, minted import-set identity, ontology pinning, refresh selection and orphan ownership for every classic import. The run predicate is applied after that choice, so none of it moves |
| Rejected | Any global relaxation of the renderer, and any wiring of the currently inactive global empty-handling setting or unrelated legacy formatting flags |
| Rejected | Loosening file names, identity derivation, folder or hierarchy inputs, body content, links, or canonical recipe templates. A selected row missing a required value is still an error |
| Rejected | Rebuilding preview to filter its samples, adding a predicted-count promise, or claiming preview and output parity. Honest copy instead |
| Deferred | Everything the failed runs would still need to prove: the JSON negative control, and any statement about the CSV or XLSX filter controls, which have no evidence of their own |
5. The one consequence worth stating plainly
Section titled “5. The one consequence worth stating plainly”Ordinary imports will produce a different effective recipe hash than they did before, because the property templates the shim generates are literally different text.
This is correct and expected. It is what that hash is for: it answers did output-affecting behavior change?, and it did. What did not happen is any reinterpretation — no hash algorithm changed, no canonical recipe digest changed meaning, no source-byte digest is involved, and no stored identity was repurposed. Nothing is rewritten or backfilled to chase the new value; existing skip, no-op and preservation policies stay authoritative, exactly as they did for the recipe-document digest work.
6. What this does NOT do
Section titled “6. What this does NOT do”- It does not close the JSON acceptance case that blocked the XLSX slice. That remains open until a real run reaches it.
- It does not establish that the CSV or XLSX filter controls behave correctly. The demonstrated path is the ordinary wizard’s record filter; do not generalize it without its own evidence.
- It does not change the canonical
source_refcontract, any schema,spec/in any form, or the v0.1 schema spec. - It does not filter preview, promise a row count before generation, or add any control to the preview screen.
- It does not add a setting, a storage surface, a parser, a dependency, or any replay behavior.
- It does not close v0.1.7, clear a quarantine, or make any performance, bundle-size, scale or mobile claim.
- It does not erase the two earlier failed runs. They happened, their evidence is archived, and this repair is a response to them rather than a replacement for them.
7. Related
Section titled “7. Related”CHANGELOG.md[Unreleased]— the single delivery log: what was built and exactly which checks have and have not run- XLSX source snapshot — the slice whose acceptance run exposed these two gaps, and whose negative control is still unreached
- Source contract delivered (Ch 46) — the declared row predicate and joins this run override sits beside without replacing
- Import wizard — the screens described here
- Generation engine — what a note carries after Generate
- Import set ownership · Reimport is identity reconciliation — the preservation and ownership rules this change leaves authoritative
- A silent wrong-answer bug — the same failure shape: confidently incomplete rather than saying “unknown”
- Save and exact replay — Gate 1 ratified — the fingerprint vocabulary this page’s hash consequence is stated against
- ETL and import — why heterogeneous published sources are the target, not an edge case
- Ontology lifecycle — the Acquire stage both repairs belong to
- Terminology — recipe, provenance, managed property, and import set
- v0.1.7 milestone — the milestone this branch is parked under, unchanged by these repairs