mikser-io 11.10.2 → 11.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1039,6 +1039,12 @@ distinction is the whole of [Faults](#faults) above.
1039
1039
  | `preset-no-match` | warn | A configured preset matched none of the entities evaluated this run. Phrased as what was observed: an incremental cycle legitimately evaluates a handful. |
1040
1040
  | `preset-unknown` | warn | `--render-presets` named a preset that is not configured. |
1041
1041
 
1042
+ ### Extraction (`mikser-io-ocr`)
1043
+
1044
+ | Code | Severity | Means |
1045
+ | --- | --- | --- |
1046
+ | `ocr-schema-changed` | notice | A step's schema changed, so stored answers no longer match it by hash. Says how many documents are in the pass and which fields are being asked for. Answers that still satisfy the new schema are reused without a model call; the rest are asked only for the fields they are missing. |
1047
+
1042
1048
  ### Configuration and stores
1043
1049
 
1044
1050
  | Code | Severity | Means |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io",
3
- "version": "11.10.2",
3
+ "version": "11.10.4",
4
4
  "files": [
5
5
  "app.js",
6
6
  "index.js",
@@ -53,8 +53,47 @@ onFinalize(async () => {
53
53
  const newDestinationsByParent = new Map()
54
54
  const lostPagination = new Set()
55
55
 
56
- for await (const { entity } of useJournal('Manifest cleanup', [OPERATION.DELETE])) {
57
- deletedIds.push(entity.id)
56
+ // A DELETE only counts if nothing re-created the entity after it.
57
+ //
58
+ // Collected with no regard for supersession, this unlinked a page that
59
+ // had just been written. A file created, deleted and re-created AT THE
60
+ // SAME PATH while one cycle was held open leaves that cycle holding
61
+ // CREATE, DELETE and CREATE for one id — and since 11.10.2 a cancelled
62
+ // cycle's entries are carried rather than dropped, which is what lets
63
+ // the stale DELETE reach the cycle that finally renders the entity. That
64
+ // fix is right; this is the other end of it.
65
+ //
66
+ // The staging below and the `claimedByThisCycle` guard were both written
67
+ // for the RENAME case, where the DELETE carries the OLD id and the RENDER
68
+ // a NEW one. There the new destination is correctly protected. When the
69
+ // id is the SAME, the guard skipped the very destination just written,
70
+ // `stillClaimed` was empty because a first render has no snapshot row
71
+ // yet, and the unlink ran: an empty output directory, no snapshot, no
72
+ // failure, `Rendered: 1` and a green build. Nothing recovered it — the
73
+ // source gate saw the file unchanged, and missingOutputIds() iterates
74
+ // SNAPSHOTS, so an entity that never got one is invisible to it.
75
+ //
76
+ // ORDER, not catalog presence. Catalog presence gives the same answers
77
+ // today, but only because catalog.js's finalize drain happens to be
78
+ // registered before this one (index.js exports catalog.js first), so the
79
+ // row is already gone when cleanup reads it. That is an invisible
80
+ // coupling: reordering two exports would silently restore this bug. The
81
+ // journal knows the answer on its own, and mikser-io-layouts already
82
+ // reconciles the same journal by order when it seeds dispatch.
83
+ const lastDelete = new Map()
84
+ const lastWrite = new Map()
85
+ for await (const { id: seq, operation, entity } of useJournal(
86
+ 'Manifest cleanup', [OPERATION.DELETE, OPERATION.CREATE, OPERATION.UPDATE])) {
87
+ if (!entity?.id) continue
88
+ const seen = operation === OPERATION.DELETE ? lastDelete : lastWrite
89
+ const prior = seen.get(entity.id)
90
+ if (prior === undefined || seq > prior) seen.set(entity.id, seq)
91
+ }
92
+ for (const [id, seq] of lastDelete) {
93
+ // `>` and not `>=`: a render that opts out of the catalog journals its
94
+ // DELETE after the entity's own CREATE, so the DELETE is genuinely
95
+ // later and must win — the rule 2e documents.
96
+ if (seq > (lastWrite.get(id) ?? -1)) deletedIds.push(id)
58
97
  }
59
98
 
60
99
  // Every destination claimed by a render task this cycle — whether it