@ai-matrx/content-ir 0.14.2 → 0.15.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.3
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/content-ir-core/v0.14.2..npm/content-ir-core/v0.14.3 -- apps/shared/content-ir-core`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## 0.15.0 — 2026-09-25
10
+
11
+ **`/source` — the attack suite stands alone; the last twin drift and flicker
12
+ closed** (verify-RC-B3 § Re-verification (0.14.2): G1–G3, R4′, R2′). Proof:
13
+ the verifier's six planted mutations (M1 skip the in-range island check, M2
14
+ integrity default off, M3 streaming ignored, M4 code-point offset off by one,
15
+ M5 unbounded `}}` scan, M6 `islandEdit` accepts any span) each turn
16
+ `__tests__/source-attack.test.ts` red ON ITS OWN; unmutated code is green in
17
+ CI and in four consecutive `SOURCE_ATTACK=long` runs on a heavily loaded host.
18
+
19
+ - **G1** — the streaming check covers every complete island in every prefix,
20
+ the LAST one included, keyed by span AND block/inline; only line-grown
21
+ constructs (tree, callout, footnote, HTML block, directive) may grow as the
22
+ trailing block. A third message carries the R2′ case and the extended syntax.
23
+ - **G2** — save verdicts are checked against an independent oracle (which
24
+ islands survive in the expected text), exactly: `island_edit` vs
25
+ `integrity` vs accepted; two-sided edits must map an island exactly (never
26
+ through a deleted range); mangling must be `island_edit`; forged
27
+ `island:true` spans must be `misaligned`.
28
+ - **G3** — timing compares the MINIMUM of interleaved runs at a 4× size step
29
+ (linear ≈ 4×, quadratic ≈ 16×, bound 8×) and fails only if every one of
30
+ three attempts breaches.
31
+ - **`island_edit` is now also the verdict** when an island inside the edited
32
+ range survives only as ordinary text (its bytes matched a non-island copy,
33
+ or the edit turned it into code) — previously reported as `integrity`.
34
+ `integrity` now means an island OUTSIDE the edited ranges was disturbed.
35
+ - **R2′** — streaming: a `$$` block whose closer sits on the still-open last
36
+ line is not settled (the rest of that line decides block vs inline); a
37
+ closing `$` that is the last byte so far is not yet math.
38
+ - **R4′** — `FENCE_OPENER_WHITESPACE` + `parseFenceOpener`: the opener parse
39
+ (JavaScript `trim()` / `/\s/`, written out — proven equal to `/\s/` over
40
+ every BMP code point) shared with the Python twin, whose block detector now
41
+ reads `markdown\u001f` as the renderer does. Vectors gained an `openers`
42
+ section and four exotic-whitespace documents (old Python: red).
43
+
44
+ **Consumer action:** code that branches on `SourceSpliceError.code` treats a
45
+ carried island that no longer stands as `island_edit` (it was `integrity`).
46
+
3
47
  ## 0.14.2 — 2026-09-25
4
48
 
5
49
  **`/source` — a tilde fence locks what the renderer draws inside it**
package/dist/index.cjs CHANGED
@@ -5195,6 +5195,24 @@ function trimFenceLine(line) {
5195
5195
  while (end > start && isFenceWhitespace(line[end - 1])) end--;
5196
5196
  return start === 0 && end === line.length ? line : line.slice(start, end);
5197
5197
  }
5198
+ var FENCE_OPENER_WHITESPACE = " \n\v\f\r \xA0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF";
5199
+ function isOpenerWhitespace(ch) {
5200
+ return ch !== void 0 && ch !== "" && FENCE_OPENER_WHITESPACE.includes(ch);
5201
+ }
5202
+ function parseFenceOpener(line) {
5203
+ let start = 0;
5204
+ let end = line.length;
5205
+ while (start < end && isOpenerWhitespace(line[start])) start++;
5206
+ while (end > start && isOpenerWhitespace(line[end - 1])) end--;
5207
+ let ticks = 0;
5208
+ while (start + ticks < end && line[start + ticks] === "`") ticks++;
5209
+ if (ticks < 3) return null;
5210
+ let infoStart = start + ticks;
5211
+ while (infoStart < end && isOpenerWhitespace(line[infoStart])) infoStart++;
5212
+ let langEnd = infoStart;
5213
+ while (langEnd < end && !isOpenerWhitespace(line[langEnd])) langEnd++;
5214
+ return { ticks, lang: line.slice(infoStart, langEnd) };
5215
+ }
5198
5216
  function classifyInnerFenceLine(trimmed, openTicks, nests, nestedDepth) {
5199
5217
  let ticks = 0;
5200
5218
  while (ticks < trimmed.length && trimmed[ticks] === "`") ticks++;
@@ -5487,6 +5505,37 @@ var NOT_WHITESPACE = /\S/;
5487
5505
  var DIRECTIVE_OPEN = /^(:{2,})([A-Za-z][\w-]*)/;
5488
5506
  var MKDOCS_OPEN = /^(!!!|\?\?\?\+?)[ \t]+([A-Za-z][\w-]*)/;
5489
5507
  var CALLOUT_OPEN = /^>[ \t]*\[!([A-Za-z][\w-]*)\]([+-])?/;
5508
+ var CALLOUT_NAMES = /* @__PURE__ */ new Set([
5509
+ "note",
5510
+ "seealso",
5511
+ "example",
5512
+ "tip",
5513
+ "hint",
5514
+ "important",
5515
+ "warning",
5516
+ "warn",
5517
+ "attention",
5518
+ "caution",
5519
+ "danger",
5520
+ "error",
5521
+ "failure",
5522
+ "fail",
5523
+ "missing",
5524
+ "bug",
5525
+ "info",
5526
+ "abstract",
5527
+ "summary",
5528
+ "tldr",
5529
+ "todo",
5530
+ "success",
5531
+ "check",
5532
+ "done",
5533
+ "question",
5534
+ "help",
5535
+ "faq",
5536
+ "quote",
5537
+ "cite"
5538
+ ]);
5490
5539
  var FOOTNOTE_DEF = /^\[\^([^\]\s]+)\]:/;
5491
5540
  function countStructuralBraces(source) {
5492
5541
  let opens = 0;
@@ -5880,7 +5929,7 @@ var Tokenizer = class {
5880
5929
  /** A blockquote whose first line is `> [!TYPE]…` — every following `>` line belongs to it. */
5881
5930
  callout(p, li, t) {
5882
5931
  const open = CALLOUT_OPEN.exec(t);
5883
- if (!open) return null;
5932
+ if (!open || !CALLOUT_NAMES.has(open[1].toLowerCase())) return null;
5884
5933
  let last = li;
5885
5934
  for (let k = li + 1; k < this.lineCount; k++) {
5886
5935
  if (!/^ {0,3}>/.test(this.lineText(k))) break;
@@ -5924,8 +5973,7 @@ var Tokenizer = class {
5924
5973
  */
5925
5974
  backtickFence(p, li, t) {
5926
5975
  const openTicks = backtickRunLength(t, 0);
5927
- const info = t.slice(openTicks).trim();
5928
- const lang = info.split(/\s/)[0] ?? "";
5976
+ const lang = parseFenceOpener(t)?.lang ?? "";
5929
5977
  const isJson = lang === "json";
5930
5978
  const normalized = normalizeFenceLanguage(lang);
5931
5979
  const meta = { fence: "`", ticks: openTicks, lang };
@@ -6055,6 +6103,7 @@ var Tokenizer = class {
6055
6103
  const endLine = this.lineOf(close);
6056
6104
  const endCe = this.contentEnds[endLine];
6057
6105
  if (endLine !== li && !this.isBlank(end, endCe)) return null;
6106
+ if (this.isOpenTail(endLine)) return this.island("math_block", p, this.n, false, { delimiter: "$$" });
6058
6107
  return this.island("math_block", p, end, true, { delimiter: "$$" });
6059
6108
  }
6060
6109
  /** `<artifact …>`, `<decision …>`, … at the start of a line. */
@@ -6384,7 +6433,7 @@ var Tokenizer = class {
6384
6433
  continue;
6385
6434
  }
6386
6435
  const end = singleDollarMathEnd(text, i, pe);
6387
- if (end > 0) {
6436
+ if (end > 0 && !(this.streaming && end === this.n)) {
6388
6437
  inlines.push(this.island("math_inline", i, end, true, { delimiter: "$" }));
6389
6438
  i = end;
6390
6439
  continue;
@@ -6794,11 +6843,17 @@ function spliceSave(original, edits, options = {}) {
6794
6843
  else newIslandsByStart.set(island.start, [island.raw]);
6795
6844
  }
6796
6845
  const disturbed = [];
6846
+ const carried = (island) => sorted.some((edit) => !edit.island && island.start >= edit.start && island.end <= edit.end);
6847
+ const changedInside = [];
6797
6848
  for (const island of islands) {
6798
6849
  if (replacedIslands.has(`${island.start}:${island.end}`)) continue;
6799
6850
  const mappedStart = mapPosition(changes, island.start, { assoc: 1 }).pos;
6800
6851
  const candidates = newIslandsByStart.get(mappedStart);
6801
6852
  if (!candidates || !candidates.includes(island.raw)) {
6853
+ if (carried(island)) {
6854
+ changedInside.push(island);
6855
+ continue;
6856
+ }
6802
6857
  disturbed.push({
6803
6858
  islandType: island.islandType,
6804
6859
  inline: island.inline,
@@ -6808,6 +6863,14 @@ function spliceSave(original, edits, options = {}) {
6808
6863
  });
6809
6864
  }
6810
6865
  }
6866
+ if (changedInside.length > 0) {
6867
+ const first = changedInside[0];
6868
+ const name = first.meta.tag ?? first.meta.name ?? first.meta.lang ?? "";
6869
+ throw new SourceSpliceError(
6870
+ "island_edit",
6871
+ `the edit changes the protected ${first.islandType}${name ? ` (${String(name)})` : ""} island at [${first.start}, ${first.end}) \u2014 its bytes survive only as ordinary text${changedInside.length > 1 ? ` (and ${changedInside.length - 1} more)` : ""}; islands change only through islandEdit()`
6872
+ );
6873
+ }
6811
6874
  const integrity = {
6812
6875
  ok: bytesOutsideEditsIdentical && disturbed.length === 0,
6813
6876
  bytesOutsideEditsIdentical,
@@ -6860,10 +6923,12 @@ function mapRange(changes, start, end, unit = "utf16") {
6860
6923
  }
6861
6924
 
6862
6925
  exports.ACTION_BY_CLASS = ACTION_BY_CLASS;
6926
+ exports.CALLOUT_NAMES = CALLOUT_NAMES;
6863
6927
  exports.CAPABILITY_BY_CLASS = CAPABILITY_BY_CLASS;
6864
6928
  exports.CLASSES = CLASSES;
6865
6929
  exports.DIRECTIVE_VERSION = DIRECTIVE_VERSION;
6866
6930
  exports.DirectiveDecodeError = DirectiveDecodeError;
6931
+ exports.FENCE_OPENER_WHITESPACE = FENCE_OPENER_WHITESPACE;
6867
6932
  exports.FENCE_WHITESPACE = FENCE_WHITESPACE;
6868
6933
  exports.IN_CONTENT_CLASSES = IN_CONTENT_CLASSES;
6869
6934
  exports.IR_ENVELOPE_CACHE_VERSION = IR_ENVELOPE_CACHE_VERSION;
@@ -6966,6 +7031,7 @@ exports.nounTitleColumn = nounTitleColumn;
6966
7031
  exports.openParseSession = openParseSession;
6967
7032
  exports.pairDisplayMath = pairDisplayMath;
6968
7033
  exports.parseDirectiveSlug = parseDirectiveSlug;
7034
+ exports.parseFenceOpener = parseFenceOpener;
6969
7035
  exports.readEnvelope = readEnvelope;
6970
7036
  exports.readNodeOutcomeValue = readNodeOutcomeValue;
6971
7037
  exports.readObjectKind = readObjectKind;