@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 +44 -0
- package/dist/index.cjs +70 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +68 -5
- package/dist/index.js.map +1 -1
- package/dist/source.cjs +70 -4
- package/dist/source.cjs.map +1 -1
- package/dist/source.d.cts +24 -1
- package/dist/source.d.ts +24 -1
- package/dist/source.js +68 -5
- package/dist/source.js.map +1 -1
- package/package.json +1 -1
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
|
|
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;
|