Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions KNOWN-LIMITS.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<!-- known-limits:pin v1
This file describes commit 23a213afa39fb8f56d0783f0ba5ab24ad4323320
This file describes commit 60c5406d7e362bb1d794ac9c26ff8a625116fb1d
stamped 2026-10-28
subject: Land chain checkpoints and inclusion proofs (PR 89) at the 2026-10-27 signing sitting; limits 87-89
body-sha256: f037bc54db74e50e3a9711b4549dea8f153686621f70b8b2d0020723699bea43
subject: Merge main (22ae903, PR 89 landed) into the l79 branch; pin header conflict only
body-sha256: 1c252b58db946798a21a4c134101cb40ab335bdffd50af2a9617ab9312643d81
Re-stamp after updating this log: npm run limits-pin -- --stamp
Divergent checkout? See: npm run limits-pin -- --check
known-limits:pin end -->
Expand Down Expand Up @@ -3248,9 +3248,15 @@ Before this change, `observeRungs` found content markers with two case-sensitive
regex, so content ending in an upper- or mixed-case closing think tag was inline
reasoning to one witness and not R3 to the other. Reach now uses the same regex,
and `test/reach-glm-leak-fixtures.test.js` pins agreement on eight probes plus six
real glm-5.3-flash bodies where `think:false` still leaked a closing tag. What
remains: the two regexes are two copies, not one import, so they can drift again;
the probe table is the only tripwire. Separately, the `ollama launch claude`
real glm-5.3-flash bodies where `think:false` still leaked a closing tag. The
two copies are now one: `src/reasoning/think-tag.js` defines `THINK_TAG`, both
witnesses import and re-export it, and `test/think-tag-one-source.test.js` pins
object identity, the `i` flag, a match table, and a source scan that fails if
either witness file defines its own `const THINK_TAG` again (closed 2026-10-27,
branch `lotor-lane/l79-think-tag-one-source`). What remains: the scan keys on
that one spelling, so a copy under another name would pass it; identity is the
real check, and it only holds while both witnesses read the shared binding.
Separately, the `ollama launch claude`
executor channel drops lowercase think tags (glm) and mangles backslash escapes
(minimax), so test sources on this lane build the less-than sign with
`String.fromCharCode(60)`. That is a property of the authoring channel, not of
Expand Down
5 changes: 2 additions & 3 deletions src/reach/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
*/

import crypto from 'node:crypto';
import { THINK_TAG } from '../reasoning/think-tag.js';
export { THINK_TAG };

export const RUNG_ORDER = Object.freeze(['R0', 'R1', 'R3', 'R4']);

Expand All @@ -36,9 +38,6 @@ function isFiniteNumber(value) {
return typeof value === 'number' && Number.isFinite(value);
}

// Matches src/reasoning's THINK_TAG so the two witnesses agree.
const THINK_TAG = /<\/?think>/i;

// A top_logprobs element that is not an object (null, a number, a string)
// is malformed. It hashes as null, so the digest still moves if it changes,
// and its position is reported instead of throwing a TypeError.
Expand Down
3 changes: 2 additions & 1 deletion src/reasoning/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import fs from 'node:fs';
import path from 'node:path';
import { createStore } from '../store/index.js';
import { claudeUserText } from '../views/chapters.js';
import { THINK_TAG } from './think-tag.js';
export { THINK_TAG };

/**
* src/reasoning/index.js
Expand Down Expand Up @@ -117,7 +119,6 @@ const HEAD_BYTES = 4096; // rewrite detection: digest of the file head
const MAX_UNCLAIMED = 512; // segments seen but not yet claimed by a hook
const MAX_CLAIMED = 4096; // ids already recorded, so a reset never doubles
const MAX_DIGEST_ROWS = 64; // per receipt; segmentDigest still covers all
const THINK_TAG = /<\/?think>/i;

function sha256(text) {
return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
Expand Down
11 changes: 11 additions & 0 deletions src/reasoning/think-tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* src/reasoning/think-tag.js
*
* The one shared THINK_TAG definition for src/reach and src/reasoning
* (KNOWN-LIMITS 79: separate copies can drift and make the two witnesses
* disagree). The less-than is built with String.fromCharCode(60) because the
* agent authoring channel is known to drop that character sequence and to
* mangle backslashes; a forward slash inside a RegExp constructor string is a
* plain character, so no escape is needed.
*/
export const THINK_TAG = new RegExp(String.fromCharCode(60) + '/?think>', 'i');
64 changes: 64 additions & 0 deletions test/think-tag-one-source.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* test/think-tag-one-source.test.js
*
* KNOWN-LIMITS 79: pin that reach and reasoning share one THINK_TAG
* definition, pin its flags, pin its behaviour with probes built from a
* fromCharCode less-than (never typed), and scan both witness sources for a
* reintroduced local const THINK_TAG (the drift tripwire).
*/

import { test } from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import * as reachMod from '../src/reach/index.js';
import * as reasoningMod from '../src/reasoning/index.js';

const LT = String.fromCharCode(60);

test('one source: reach and reasoning export the very same regex object', () => {
assert.equal(reachMod.THINK_TAG, reasoningMod.THINK_TAG);
});

test('shared regex has flags exactly i (no g, so .test has no lastIndex state)', () => {
assert.equal(reachMod.THINK_TAG.flags, 'i');
});

test('behaviour table: matches', () => {
const re = reasoningMod.THINK_TAG;
const matches = [
LT + 'think>',
LT + '/think>',
LT + 'THINK>',
LT + '/Think>',
'a ' + LT + '/tHiNk> b'
];
for (const probe of matches) {
assert.equal(re.test(probe), true, 'should match: ' + JSON.stringify(probe));
}
});

test('behaviour table: does not match', () => {
const re = reasoningMod.THINK_TAG;
const doesNotMatch = [
'think>',
LT + 'thinking>',
LT + 'think >',
LT + '/ think>',
''
];
for (const probe of doesNotMatch) {
assert.equal(re.test(probe), false, 'should not match: ' + JSON.stringify(probe));
}
});

test('drift tripwire: neither witness source contains const THINK_TAG', () => {
const here = path.dirname(fileURLToPath(import.meta.url));
for (const rel of ['../src/reach/index.js', '../src/reasoning/index.js']) {
const file = path.resolve(here, rel);
const text = fs.readFileSync(file, 'utf8');
assert.equal(text.includes('const THINK_TAG'), false,
file + ' contains const THINK_TAG again');
}
});
Loading