This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
}
|
||||
|
||||
const p = (text) => '<p>' + text + '</p>';
|
||||
const h2 = (text) => '<h2>' + text + '</h2>';
|
||||
const code = (text) => '<pre><code>' + escapeHtml(text) + '</code></pre>';
|
||||
const ol = (items) => '<ol>' + items.map((item) => '<li>' + item + '</li>').join('') + '</ol>';
|
||||
const figure = (src, alt, caption) => '<figure><img src="' + src + '" alt="' + alt + '" loading="lazy" /><figcaption>' + caption + '</figcaption></figure>';
|
||||
const table = (caption, headers, rows) => '<div class="table-scroll"><table><caption>' + caption + '</caption><thead><tr>' + headers.map((item) => '<th scope="col">' + item + '</th>').join('') + '</tr></thead><tbody>' + rows.map((row) => '<tr>' + row.map((item) => '<td>' + item + '</td>').join('') + '</tr>').join('') + '</tbody></table></div>';
|
||||
|
||||
function plainText(content) {
|
||||
return content.replace(/<[^>]+>/g, ' ').replace(/&(?:quot|amp|lt|gt|#039);/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function bodyText(content) {
|
||||
return plainText(content.replace(/<h2>Проверяемые источники<\/h2>[\s\S]*?(?=<h2>|$)/, ''));
|
||||
}
|
||||
|
||||
function deepFreeze(value) {
|
||||
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
||||
Object.values(value).forEach(deepFreeze);
|
||||
Object.freeze(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function cloneFixed(value) {
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
const REFERENCES = deepFreeze({
|
||||
php84: {
|
||||
title: 'PHP source: annotated tag php-8.4.0',
|
||||
url: 'https://github.com/php/php-src/tree/b437f2b32eb364c9496d24abcc734272e5c9c980',
|
||||
version: 'annotated tag php-8.4.0, tag object 501fc4ce253940b58a8744b1083951327e07f00e, 19 November 2024, immutable commit pin',
|
||||
},
|
||||
ecma2025: {
|
||||
title: 'ECMA-262, 16th edition: ECMAScript 2025 Language Specification',
|
||||
url: 'https://262.ecma-international.org/16.0/',
|
||||
version: '16th edition, June 2025, edition-specific publication',
|
||||
},
|
||||
d2111: {
|
||||
title: 'D compiler: annotated tag v2.111.0',
|
||||
url: 'https://github.com/dlang/dmd/tree/c403accecf9048802122c17852715251509adf56',
|
||||
version: 'annotated tag v2.111.0, tag object 6ae81e26cedc8f792d0074186e38974542f1a3cc, 31 March 2025; release 1 April 2025, immutable commit pin',
|
||||
},
|
||||
});
|
||||
|
||||
function sourceList(entries) {
|
||||
return '<ul>' + entries.map(({ key, use, boundary }) => {
|
||||
const reference = REFERENCES[key];
|
||||
return '<li><a href="' + reference.url + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(reference.title) + '</a> — версия: ' + escapeHtml(reference.version) + '. ' + escapeHtml(use) + ' Граница: ' + escapeHtml(boundary) + '</li>';
|
||||
}).join('') + '</ul>';
|
||||
}
|
||||
|
||||
const FIXED_MULTI_RUNTIME_RECORDS = deepFreeze({
|
||||
'named-contract-v1': {
|
||||
id: 'named-contract-v1',
|
||||
schemaVersion: 'fixed-boundary-1',
|
||||
contract: {
|
||||
operation: 'fixed-order-decision',
|
||||
value: { tag: 'order-ready', amountMinor: 4200, currency: 'RUB' },
|
||||
error: { semantics: 'named-envelope', code: null, retry: 'not-requested' },
|
||||
time: { basis: 'fixed-logical-ticks', opened: 100, closed: 108 },
|
||||
},
|
||||
adapters: [
|
||||
{ model: 'php', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },
|
||||
{ model: 'javascript', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },
|
||||
{ model: 'd', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },
|
||||
],
|
||||
boundary: 'Fixed JavaScript literal in memory. Names are model labels only: no PHP, JavaScript, or D runtime is started, connected, profiled, or tested.',
|
||||
},
|
||||
'incomplete-contract-v1': {
|
||||
id: 'incomplete-contract-v1',
|
||||
schemaVersion: '',
|
||||
contract: { operation: 'fixed-order-decision', value: { tag: 'order-ready', amountMinor: 4200, currency: 'RUB' }, error: { semantics: 'named-envelope', code: null, retry: 'not-requested' }, time: { basis: 'fixed-logical-ticks', opened: 100, closed: 108 } },
|
||||
adapters: [],
|
||||
boundary: 'Fixed JavaScript literal in memory.',
|
||||
},
|
||||
'mixed-error-semantics-v1': {
|
||||
id: 'mixed-error-semantics-v1',
|
||||
schemaVersion: 'fixed-boundary-1',
|
||||
contract: { operation: 'fixed-order-decision', value: { tag: 'order-ready', amountMinor: 4200, currency: 'RUB' }, error: { semantics: 'named-envelope', code: null, retry: 'not-requested' }, time: { basis: 'fixed-logical-ticks', opened: 100, closed: 108 } },
|
||||
adapters: [
|
||||
{ model: 'php', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },
|
||||
{ model: 'javascript', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'thrown-value', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },
|
||||
{ model: 'd', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },
|
||||
],
|
||||
boundary: 'Fixed JavaScript literal in memory.',
|
||||
},
|
||||
'undetermined-time-v1': {
|
||||
id: 'undetermined-time-v1',
|
||||
schemaVersion: 'fixed-boundary-1',
|
||||
contract: { operation: 'fixed-order-decision', value: { tag: 'order-ready', amountMinor: 4200, currency: 'RUB' }, error: { semantics: 'named-envelope', code: null, retry: 'not-requested' }, time: { basis: 'wall-clock', opened: 100, closed: null } },
|
||||
adapters: [
|
||||
{ model: 'php', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'wall-clock', mapping: 'exact' },
|
||||
{ model: 'javascript', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'wall-clock', mapping: 'exact' },
|
||||
{ model: 'd', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'wall-clock', mapping: 'exact' },
|
||||
],
|
||||
boundary: 'Fixed JavaScript literal in memory.',
|
||||
},
|
||||
'incomparable-adapter-v1': {
|
||||
id: 'incomparable-adapter-v1',
|
||||
schemaVersion: 'fixed-boundary-1',
|
||||
contract: { operation: 'fixed-order-decision', value: { tag: 'order-ready', amountMinor: 4200, currency: 'RUB' }, error: { semantics: 'named-envelope', code: null, retry: 'not-requested' }, time: { basis: 'fixed-logical-ticks', opened: 100, closed: 108 } },
|
||||
adapters: [
|
||||
{ model: 'php', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },
|
||||
{ model: 'javascript', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'coerced-number' },
|
||||
{ model: 'd', contractVersion: 'fixed-boundary-1', valueTag: 'order-ready', errorSemantics: 'named-envelope', timeBasis: 'fixed-logical-ticks', mapping: 'exact' },
|
||||
],
|
||||
boundary: 'Fixed JavaScript literal in memory.',
|
||||
},
|
||||
});
|
||||
|
||||
export function createFixedMultiRuntimeRecord(id) {
|
||||
const record = FIXED_MULTI_RUNTIME_RECORDS[id];
|
||||
return record ? deepFreeze(cloneFixed(record)) : undefined;
|
||||
}
|
||||
|
||||
function stop(status, reason, nextAction) {
|
||||
return deepFreeze({ status, reasons: deepFreeze([reason]), nextAction, productionEffect: 'not-attempted' });
|
||||
}
|
||||
|
||||
export function reviewFixedBoundaryContract(record) {
|
||||
if (!record?.id || record.schemaVersion !== 'fixed-boundary-1' || !record.contract?.operation || !record.contract?.value?.tag) {
|
||||
return stop('stop-incomplete-contract', 'contract-schema-or-named-value-missing', 'name-schema-operation-and-value-tag');
|
||||
}
|
||||
const { error, time } = record.contract;
|
||||
if (error?.semantics !== 'named-envelope' || !Object.hasOwn(error, 'code') || !Object.hasOwn(error, 'retry')) {
|
||||
return stop('stop-incomplete-contract', 'error-envelope-is-not-complete', 'name-code-and-retry-policy');
|
||||
}
|
||||
if (time?.basis !== 'fixed-logical-ticks' || !Number.isInteger(time.opened) || !Number.isInteger(time.closed) || time.closed < time.opened) {
|
||||
return stop('stop-undetermined-time-boundary', 'time-basis-or-ordered-fixed-ticks-missing', 'replace-time-with-ordered-fixed-logical-ticks');
|
||||
}
|
||||
if (!Array.isArray(record.adapters) || record.adapters.length !== 3) {
|
||||
return stop('stop-incomplete-contract', 'three-named-adapter-records-required', 'provide-php-javascript-and-d-model-records');
|
||||
}
|
||||
const models = new Set(record.adapters.map((adapter) => adapter.model));
|
||||
if (!['php', 'javascript', 'd'].every((model) => models.has(model))) {
|
||||
return stop('stop-incomplete-contract', 'named-model-set-is-incomplete', 'provide-one-record-per-model-label');
|
||||
}
|
||||
if (record.adapters.some((adapter) => adapter.contractVersion !== record.schemaVersion || adapter.valueTag !== record.contract.value.tag || adapter.errorSemantics !== error.semantics)) {
|
||||
return stop('stop-incomparable-adapter', 'adapter-does-not-preserve-contract-shape', 'align-version-value-tag-and-error-envelope');
|
||||
}
|
||||
if (record.adapters.some((adapter) => adapter.timeBasis !== time.basis)) {
|
||||
return stop('stop-undetermined-time-boundary', 'adapter-time-basis-does-not-match-contract', 'name-one-fixed-time-basis');
|
||||
}
|
||||
if (record.adapters.some((adapter) => adapter.mapping !== 'exact')) {
|
||||
return stop('stop-incomparable-adapter', 'adapter-mapping-is-not-exact', 'replace-coercion-with-explicit-contract-field');
|
||||
}
|
||||
return deepFreeze({
|
||||
status: 'synthetic-multi-runtime-review-hand-off',
|
||||
contractId: record.id,
|
||||
operation: record.contract.operation,
|
||||
valueTag: record.contract.value.tag,
|
||||
fixedElapsedTicks: record.contract.time.closed - record.contract.time.opened,
|
||||
adapterModels: deepFreeze(record.adapters.map((adapter) => adapter.model)),
|
||||
boundary: record.boundary,
|
||||
productionEffect: 'not-attempted',
|
||||
});
|
||||
}
|
||||
|
||||
export function createFixedSemanticMatrix(id) {
|
||||
const record = createFixedMultiRuntimeRecord(id);
|
||||
return record ? deepFreeze({ matrixId: 'fixed-semantic-matrix-' + id, record, allowedQuestion: 'Do named fields preserve one synthetic contract?', boundary: 'In-memory JavaScript literal; labels are not running language processes.' }) : undefined;
|
||||
}
|
||||
|
||||
export function explainFixedSemanticBoundary(matrix) {
|
||||
const result = reviewFixedBoundaryContract(matrix?.record);
|
||||
if (result.status !== 'synthetic-multi-runtime-review-hand-off') return result;
|
||||
return deepFreeze({
|
||||
status: 'synthetic-semantic-boundary-explained',
|
||||
typeRule: 'one-named-value-tag-with-explicit-fields',
|
||||
errorRule: 'one-named-envelope-with-code-and-retry',
|
||||
timeRule: 'one-ordered-fixed-logical-tick-interval',
|
||||
adapterRule: 'exact-mapping-only',
|
||||
handOff: result,
|
||||
productionEffect: 'not-attempted',
|
||||
});
|
||||
}
|
||||
|
||||
export function createFixedEvidencePacket(id) {
|
||||
const record = createFixedMultiRuntimeRecord(id);
|
||||
return record ? deepFreeze({ packetId: 'fixed-evidence-packet-' + id, input: record, reviewQuestion: 'Is the fixed contract record internally comparable?', requestedOutcome: 'synthetic-multi-runtime-review-hand-off', boundary: 'Named literals only; no network, filesystem, clock, telemetry, service, runtime, trace, profile, PII or deployment.' }) : undefined;
|
||||
}
|
||||
|
||||
export function reviewFixedEvidencePacket(packet) {
|
||||
if (packet?.requestedOutcome !== 'synthetic-multi-runtime-review-hand-off' || !packet.reviewQuestion || !packet.boundary?.includes('Named literals only')) {
|
||||
return stop('stop-incomplete-contract', 'review-packet-does-not-name-bounded-question', 'name-question-boundary-and-synthetic-outcome');
|
||||
}
|
||||
const handOff = reviewFixedBoundaryContract(packet.input);
|
||||
if (handOff.status !== 'synthetic-multi-runtime-review-hand-off') return handOff;
|
||||
return deepFreeze({ status: 'synthetic-integration-review-hand-off', packetId: packet.packetId, observedContract: handOff, nextAction: 'hand-off-fixed-synthetic-review', productionEffect: 'not-attempted' });
|
||||
}
|
||||
|
||||
export function runFixedMultiRuntimeFixture() {
|
||||
const cases = [
|
||||
['accepted', () => reviewFixedBoundaryContract(createFixedMultiRuntimeRecord('named-contract-v1')), 'synthetic-multi-runtime-review-hand-off'],
|
||||
['missing', () => reviewFixedBoundaryContract(createFixedMultiRuntimeRecord('incomplete-contract-v1')), 'stop-incomplete-contract'],
|
||||
['mixed-errors', () => reviewFixedBoundaryContract(createFixedMultiRuntimeRecord('mixed-error-semantics-v1')), 'stop-incomparable-adapter'],
|
||||
['time', () => reviewFixedBoundaryContract(createFixedMultiRuntimeRecord('undetermined-time-v1')), 'stop-undetermined-time-boundary'],
|
||||
['adapter', () => reviewFixedBoundaryContract(createFixedMultiRuntimeRecord('incomparable-adapter-v1')), 'stop-incomparable-adapter'],
|
||||
['mechanism', () => explainFixedSemanticBoundary(createFixedSemanticMatrix('named-contract-v1')), 'synthetic-semantic-boundary-explained'],
|
||||
['mechanism-stop', () => explainFixedSemanticBoundary(createFixedSemanticMatrix('mixed-error-semantics-v1')), 'stop-incomparable-adapter'],
|
||||
['field', () => reviewFixedEvidencePacket(createFixedEvidencePacket('named-contract-v1')), 'synthetic-integration-review-hand-off'],
|
||||
['field-stop', () => reviewFixedEvidencePacket(createFixedEvidencePacket('undetermined-time-v1')), 'stop-undetermined-time-boundary'],
|
||||
];
|
||||
const results = cases.map(([name, execute, expected]) => { const actual = execute(); return deepFreeze({ name, expected, actual: actual.status, passed: actual.status === expected }); });
|
||||
return deepFreeze({ passed: results.filter((result) => result.passed).length, total: results.length, accepted: results.every((result) => result.passed), results });
|
||||
}
|
||||
|
||||
const PRACTICE_SOURCES = sourceList([
|
||||
{ key: 'php84', use: 'Фиксирует конкретный исходный срез PHP 8.4.0; он не описывает совместимость с другими языками.', boundary: 'В статье не запускается PHP и не делается вывод о его поведении.' },
|
||||
{ key: 'ecma2025', use: 'Определяет ECMAScript language types и completion semantics для edition 2025.', boundary: 'JS-объекты в примере — только носители synthetic records.' },
|
||||
{ key: 'd2111', use: 'Фиксирует конкретный D compiler release line 2.111.0.', boundary: 'Метка d в literаl не означает исполнение D-кода.' },
|
||||
]);
|
||||
const MECHANISM_SOURCES = sourceList([
|
||||
{ key: 'ecma2025', use: 'Показывает, что edition отдельно называет language types и completion records.', boundary: 'Из этого не следует модель ошибок PHP или D.' },
|
||||
{ key: 'php84', use: 'Даёт проверяемую версию PHP source tree для исторической привязки.', boundary: 'Статья не приписывает этому дереву формат межъязыкового адаптера.' },
|
||||
{ key: 'd2111', use: 'Даёт проверяемую версию D compiler source tree для исторической привязки.', boundary: 'Статья не делает утверждения о запущенном компиляторе или runtime.' },
|
||||
]);
|
||||
const FIELD_SOURCES = sourceList([
|
||||
{ key: 'd2111', use: 'Используется как dated pin одной из трёх языковых линий.', boundary: 'Не является доказательством интеграции с каким-либо сервисом.' },
|
||||
{ key: 'php84', use: 'Используется как dated pin второй языковой линии.', boundary: 'Не подтверждает deploy, I/O, ошибку или время исполнения.' },
|
||||
{ key: 'ecma2025', use: 'Используется для строгого различения значения и completion в названии review-вопроса.', boundary: 'Не превращает synthetic review в сертификат совместимости.' },
|
||||
]);
|
||||
|
||||
const practiceContent = [
|
||||
h2('Один шов вместо трёх догадок'),
|
||||
p('Проблема начинается не в синтаксисе. Команда называет один ответ «успехом», но PHP, JavaScript и D понимают значение, ошибку и время через разные собственные модели. Цена такой неясности — не абстрактная несовместимость, а дорогое обсуждение после того, как один адаптер уже превратил отсутствие поля в ноль, другой сохранил исключение, а третий потерял основание времени.'),
|
||||
p('Первый плохой ход — рисовать связь трёх runtime-ов и сразу искать реальную интеграцию. Это увеличивает стоимость ошибки: спор становится про окружение, логи и доступы, хотя вопрос ещё не сформулирован. Здесь выбирается более узкий объект: named fixed synthetic JS literal, который описывает только boundary contract и допускает только review hand-off.'),
|
||||
h2('Что именно является контрактом'),
|
||||
p('Narrow boundary contract — это не «универсальный DTO» и не обещание, что любой язык автоматически его прочитает. В пакете это один объект с версией, именованной операцией, tagged value, error envelope, временной шкалой и тремя adapter records. Каждый элемент существует потому, что его отсутствие меняет смысл вывода. Вне объекта нет неявного default, локальной конфигурации и догадки рецензента.'),
|
||||
p('Поле <code>value.tag</code> отделяет вид значения от его представления. <code>amountMinor</code> не объявлен денежным протоколом и не приводится к float. Это фиксированное число в учебном record. Поле <code>error</code> не хранит exception object: там named semantics, code и retry. Так boundary не импортирует чужую иерархию ошибок и не выдаёт local throw за переносимую информацию.'),
|
||||
table('Минимальные поля fixed boundary contract', ['Поле', 'Зачем оно названо', 'Когда остановиться'], [['schemaVersion', 'связывает record и adapter', 'версия пуста или различается'], ['value.tag', 'не даёт подменить вид значения coercion-ом', 'tag отсутствует или другой'], ['error', 'переносит код и retry без object identity', 'нет named envelope'], ['time', 'задаёт сравнимый интервал', 'нет ordered fixed ticks'], ['mapping', 'показывает, сохранён ли shape', 'не exact']]),
|
||||
figure('/assets/editorial/2026/multi-runtime-2026-contract-boundary-map.svg', 'Карта фиксированной границы: три языковые модели сходятся к одному named contract, затем к синтетическому review hand-off.', 'Рисунок не изображает сетевое соединение или работающие runtime. Он показывает только структуру in-memory record.'),
|
||||
h2('Порядок сборки record'),
|
||||
ol(['Назвать одну операцию, а не домен целиком: в fixture это fixed-order-decision.', 'Записать value как tagged fields; не отправлять «любое JSON».', 'Выбрать единственную error semantics — named envelope — и назвать retry.', 'Выбрать fixed logical ticks и записать оба конца интервала.', 'Добавить по одному model label: php, javascript, d; требовать exact mapping.', 'Запустить review и принять только synthetic multi-runtime review hand-off либо stop reason.']),
|
||||
h2('Исполняемый пример'),
|
||||
p('Ниже нет запуска PHP, D или отдельного JavaScript runtime. Модуль уже исполняется JavaScript-интерпретатором, а строки <code>php</code>, <code>javascript</code> и <code>d</code> — данные fixed literal. Поэтому результат отвечает только на вопрос о полноте модели, а не о совместимости программ.'),
|
||||
code("import { createFixedMultiRuntimeRecord, reviewFixedBoundaryContract } from './upgrade-2026-06.mjs';\n\nconst record = createFixedMultiRuntimeRecord('named-contract-v1');\nconst handOff = reviewFixedBoundaryContract(record);\n\nconsole.log({\n status: handOff.status,\n valueTag: handOff.valueTag,\n fixedElapsedTicks: handOff.fixedElapsedTicks,\n productionEffect: handOff.productionEffect,\n});\n// synthetic-multi-runtime-review-hand-off, order-ready, 8, not-attempted"),
|
||||
p('У accepted path есть ровно одна положительная формулировка: record можно передать на synthetic multi-runtime review. Он не является integration certificate, deployment plan, результатом тестирования среды или подтверждением того, что adapters когда-либо связывались. Поле <code>productionEffect: not-attempted</code> специально остаётся рядом с результатом, чтобы код не расширил текстовое обещание.'),
|
||||
h2('Где граница намеренно жёсткая'),
|
||||
p('Один contract record не обязан сохранять все особенности каждой модели. Он не переносит nullable semantics, class hierarchy, stack trace, GC, scheduler и представление времени в конкретной библиотеке. Это не пробел для последующего «умного адаптера». Это запрет на скрытый перенос. Если свойство необходимо для решения, оно получает поле и named rule; если его нельзя назвать, review закрывается.'),
|
||||
p('Ровно поэтому error semantics не равны строке сообщения. Сообщение может быть полезно человеку, но не задаёт сравнимого действия. Code и retry здесь также не претендуют на общий стандарт: они лишь делают фиксированный record проверяемым. Новая ошибка потребует новой зафиксированной fixture, а не добавления catch-all поля.') ,
|
||||
h2('Контрпримеры полезнее happy path'),
|
||||
p('Пустая схема возвращает <code>stop-incomplete-contract</code>, а не частичный ответ. Модель с thrown-value в одном adapter record и named-envelope в двух других не «почти готова»: она возвращает <code>stop-incomparable-adapter</code>. Эти stop reasons описывают недостаток record, не дефект PHP, JavaScript или D. Такой язык экономит время: владельцу понятно, что нужно назвать, а не что нужно срочно починить в неизвестной системе.'),
|
||||
p('Неопределённое время закрывается <code>stop-undetermined-time-boundary</code>. Wall-clock и пустой конец интервала нельзя превратить в duration задним числом. Этот отказ не говорит, что часы плохие. Он говорит, что выбранный synthetic вопрос требует fixed logical ticks. Для другой задачи можно создать отдельный договор, но нельзя тихо ослабить этот.'),
|
||||
h2('Чек перед передачей'),
|
||||
ol(['Все три model label присутствуют по одному разу.', 'Версия каждого adapter record совпадает с верхней версией.', 'Tag значения и error semantics совпадают буквально.', 'Время имеет fixed basis и два упорядоченных integer tick.', 'Mapping у каждого adapter — exact; coercion даёт stop.', 'Положительный результат говорит только о review hand-off.']),
|
||||
h2('Как не расширить контракт при первой же правке'),
|
||||
p('Самая частая деградация начинается с полезного на вид поля <code>metadata</code>. Его добавляют, чтобы не выпускать новую версию, а затем в нём оказываются локальный тип, текст исключения, timezone или флаг конкретной библиотеки. Contract перестаёт быть narrow: значимая информация снова живёт без schema и без правила mapping. Правильная реакция на новый факт обратная: сначала решить, участвует ли он в review-вопросе. Если нет — не добавлять. Если да — назвать поле, version change и fail-closed случай.'),
|
||||
p('Вторая деградация — выдавать нулевое значение за отсутствие данных. Например, <code>amountMinor: 0</code> в этом literal остаётся числом, а не сигналом ошибки, потому что tag уже сказал вид value. Пустой amount не заменяется нулём; он требует другого tag или stop. Такой запрет кажется формальным, но он защищает обсуждение: рецензент не должен выяснять, означает ли ноль бесплатную операцию, отсутствие конверсии или непрочитанное поле.'),
|
||||
p('Третья деградация — считать модельный label адресом реализации. Поле <code>model: php</code> не несёт версию интерпретатора, process id, package list или transport. Оно лишь требует, чтобы в fixed contract было три заранее названных perspective records. Если будущая задача зависит от конкретной версии, её нельзя спрятать в label. Нужен другой data contract и отдельная historical source pin, иначе сравнением управляет неявное знание автора.'),
|
||||
h2('Разбор одного accepted record'),
|
||||
p('В <code>named-contract-v1</code> операция названа до adapter list. Это важно: adapters не голосуют за смысл операции, а сверяются с уже объявленным объектом. Tag <code>order-ready</code> не описывает состояние реального заказа; он всего лишь дискретная метка fixed input. Amount и currency показывают, что tag может нести поля, но review не проверяет их экономический смысл, диапазон и format. Его задача уже уже: увидеть одинаковый shape во всех трёх records.'),
|
||||
p('Error envelope в accepted record содержит <code>code: null</code>. Null здесь не превращается в «ошибок в реальной системе нет». Он означает, что конкретный synthetic value хранит отсутствие named error code. Retry <code>not-requested</code> также не запускает повтор и не задаёт backoff. Два поля нужны только потому, что иначе один adapter мог бы молча добавить retry rule, а другой — интерпретировать null как неизвестное состояние.'),
|
||||
p('Ticks 100 и 108 не являются timestamp. Их нельзя сопоставить с календарём и нельзя вычесть из них метрику. У них один полезный смысл: fixed record содержит порядок и разность восемь. Это удерживает автора от ложного вывода о скорости. Если reader хочет говорить о времени исполнения, ему придётся сформулировать другой input и разрешить другой evidence; старая карточка не сможет случайно стать основанием.'),
|
||||
h2('Решение владельца по каждому stop'),
|
||||
table('Стоп-сигнал и минимальная правка literal', ['Status', 'Чего не хватает', 'Минимальная следующая правка'], [['stop-incomplete-contract', 'schema, operation, tag или полный adapter set', 'назвать отсутствующее поле без default'], ['stop-undetermined-time-boundary', 'единые ordered fixed ticks', 'заменить time record, не подставлять clock'], ['stop-incomparable-adapter', 'exact сохранение contract shape', 'убрать coercion или завести явный новый вариант']]),
|
||||
p('Владелец не обязан немедленно исправлять stop. Иногда точный ответ — оставить record закрытым, потому что необходимого факта нет. Это лучше, чем вводить вымышленный mapping ради зелёного fixture. Fail-closed protocol не измеряет продуктивность команды количеством accepted results. Он измеряет качество тем, что не даёт невнятному input стать переносимым утверждением.'),
|
||||
h2('Ограничения и следующий шаг'),
|
||||
p('Этот материал не измеряет latency, не вызывает файловую систему, не читает clock, не собирает telemetry, не строит trace и не содержит PII. Он не проверяет PHP, JavaScript или D и не утверждает, что они реально связаны. Даже слова adapter и integration здесь обозначают поля synthetic record, а не сетевой слой.'),
|
||||
p('Следующий безопасный шаг — добавить один новый named fail-closed literal: например, value tag без явного mapping rule. Затем надо повторить review и убедиться, что он останавливается точной причиной. Только после отдельного решения о реальном контуре можно обсуждать другой вид проверки; данный пакет такого решения не принимает.'),
|
||||
h2('Проверяемые источники'), PRACTICE_SOURCES,
|
||||
].join('');
|
||||
|
||||
const mechanismContent = [
|
||||
h2('Когда одинаковая строка скрывает разные решения'),
|
||||
p('Техническая проблема не в том, что три языка «разные». Она в том, что граница часто получает один неименованный payload и предлагает каждому участнику восстановить type, error и time по привычке. Цена — ложный успех: adapter может вернуть похожий объект, но потерять distinction между отсутствующим значением, ошибкой и неподтверждённой длительностью.'),
|
||||
p('Нельзя лечить это каталогом особенностей PHP, JavaScript и D. Такой каталог быстро становится неточным и всё равно не даёт rule на границе. Вместо него mechanism article строит три явные оси для fixed literal: tagged type, named error envelope и ordered logical time. Их проверяет JS-функция, но она не моделирует и не запускает иной runtime.'),
|
||||
h2('Три оси, которые нельзя склеить'),
|
||||
p('Тип отвечает на вопрос «какой вид значения разрешён». Ошибка отвечает «в каком режиме завершилась операция и что делать с ним в рамках record». Время отвечает «какой интервал определён в выбранной шкале». Если поле отсутствует, нельзя заменить его соседней осью. Например, error code не даёт timestamp, а числовая величина не подтверждает value tag.'),
|
||||
p('ECMA-262 16th edition отдельно описывает language types и completion records. Это полезная граница терминов, но не лицензия приписывать JavaScript общую семантику другим языкам. PHP 8.4.0 и D 2.111.0 в списке источников закреплены как отдельные исторические линии. В synthetic matrix они остаются только подписями model records.'),
|
||||
table('Разделение вопросов на boundary', ['Ось', 'Допустимый fixed signal', 'Недопустимая подмена'], [['type', 'value.tag = order-ready', 'coerced number вместо тега'], ['error', 'named-envelope + code + retry', 'throw в одном record и envelope в другом'], ['time', 'fixed-logical-ticks: 100..108', 'wall-clock без закрытия'], ['adapter', 'version и shape совпадают', 'local default или implicit conversion']]),
|
||||
figure('/assets/editorial/2026/multi-runtime-2026-error-type-time-matrix.svg', 'Матрица type, error и time: каждое из трёх model labels проходит через отдельные named rules и может остановить review.', 'Матрица описывает требование к synthetic record. Это не сравнительная таблица реальных возможностей трёх языков.'),
|
||||
h2('Почему tag важнее похожего shape'),
|
||||
p('JSON-подобный shape создаёт опасную иллюзию одинаковости. У двух объектов могут совпадать поля <code>amountMinor</code> и <code>currency</code>, но один означает подготовленное решение, а другой — неполное. Named tag делает это различие проверяемым. Функция не выводит tag по наличию amount и не пытается догадаться о версии контракта.'),
|
||||
p('Такой выбор сознательно скучный. Он заставляет автора назвать новый вариант и добавить fixture. Зато рецензент не ищет смысл в convention, которой нет в данных. Coercion тоже запрещён: <code>mapping: coerced-number</code> останавливает review. Это не оценка конкретного языка и не утверждение, что coercion всегда плох; это правило одного узкого boundary contract.'),
|
||||
h2('Ошибка — не транспорт чужого исключения'),
|
||||
p('Error envelope содержит три fixed факта: semantics, code и retry. В accepted literal semantics равны <code>named-envelope</code>, code равен <code>null</code>, retry — <code>not-requested</code>. Такой набор может выразить отсутствие ошибки без передачи stack trace или object identity. Он не выбирает стандартный тип ошибки для PHP, JavaScript или D.'),
|
||||
p('Смешанный случай важнее учебного успеха. Если php и d records пишут envelope, а javascript record отмечен <code>thrown-value</code>, boundary не имеет общего смысла, даже если все три человекочитаемых сообщения похожи. Возврат <code>stop-incomparable-adapter</code> не исправляет и не ловит исключение. Он отказывается склеить разные семантики в один вывод.'),
|
||||
h2('Время — это не число без основания'),
|
||||
p('Временная часть intentionally excludes clock. Fixed logical ticks — целые значения, заданные прямо в literal. Они подходят только для вопроса «есть ли упорядоченный интервал внутри этого record». В accepted path 100..108 даёт восемь ticks. Это не миллисекунды, не latency и не performance observation.'),
|
||||
p('Если basis поменялся на wall-clock либо closed отсутствует, функция возвращает <code>stop-undetermined-time-boundary</code>. Она не подставляет текущее время, не вычитает дату и не ищет внешнюю трассу. Так output не делает вид, будто время известно. В реальном проекте измерение потребует собственной границы, источника и policy; здесь оно только запрещено.'),
|
||||
h2('Исполняемая matrix'),
|
||||
code("import { createFixedSemanticMatrix, explainFixedSemanticBoundary } from './upgrade-2026-06.mjs';\n\nconst matrix = createFixedSemanticMatrix('named-contract-v1');\nconst explanation = explainFixedSemanticBoundary(matrix);\n\nconsole.log({\n status: explanation.status,\n typeRule: explanation.typeRule,\n timeRule: explanation.timeRule,\n productionEffect: explanation.productionEffect,\n});\n// synthetic-semantic-boundary-explained, one-named-value-tag-with-explicit-fields"),
|
||||
p('Этот export добавляет к accepted review четыре named rules, но не меняет результат на сертификат. <code>synthetic-semantic-boundary-explained</code> означает, что внутри fixed matrix type, error, time и mapping были названы. Он не сообщает, как скомпилируется D-код, что бросит PHP или какой host определит JavaScript completion.'),
|
||||
h2('Последовательность разбора'),
|
||||
ol(['Проверить schemaVersion и operation; без них нельзя сравнивать adapters.', 'Проверить tag до числовых полей и не делать inference по shape.', 'Проверить error envelope целиком, включая null code и retry.', 'Проверить единую basis и порядок opened/closed.', 'Сравнить каждый adapter с contract, а не adapters друг с другом.', 'Вернуть explanation только после exact mapping; иначе сохранить stop reason.']),
|
||||
h2('Что даёт stop каждому владельцу'),
|
||||
p('Stop — это рабочий контракт, а не ругательство. <code>contract-schema-or-named-value-missing</code> назначает следующий шаг: назвать схему, operation и tag. <code>adapter-does-not-preserve-contract-shape</code> просит выровнять version, value tag и error envelope. <code>adapter-time-basis-does-not-match-contract</code> не спорит о точности часов; он требует одно основание именно для данного вопроса.'),
|
||||
p('Благодаря такой точности нельзя выдать один отказ за другой. Incomplete contract не равен mixed errors. Mixed errors не равны incomparability от coercion. Undetermined time не равен длинному интервалу. Эти distinctions поддерживают T-shaped работу: автор понимает контрактную грань, но не изображает специалиста по каждому runtime без запуска и доказательства.'),
|
||||
h2('Границы типов на языке поля, а не реализации'),
|
||||
p('Type rule здесь не говорит «используйте строгую типизацию». Он говорит гораздо уже: consumer видит только named tag и заранее определённые fields. Поэтому неважно, как конкретная реализация хранит целое число или где проверяет nullable value. Пока значение не попало в synthetic record, статья ничего не утверждает. После попадания reader не имеет права реконструировать дополнительные детали из названия model или из знакомого ему поведения языка.'),
|
||||
p('Это различие полезно в review. Фраза «у нас там число» не является доказательством совместимости, пока не названы tag, unit и пустой случай. Фраза «у нас объект ошибки» тоже не говорит, какой режим завершения переносится. Fixed matrix принуждает переводить обе фразы в fields. Если перевод невозможен, это не повод искусственно унифицировать модели; это повод оставить boundary без accepted result.'),
|
||||
p('Ещё один ложный shortcut — сравнить serialised text. Одинаковая строка может быть создана из разных source types, а разная строка может корректно представлять один named tag. Matrix не оценивает serialization. Она проверяет record после того, как serialization уже вынесена за границу. Так работа остаётся T-shaped: автор глубоко знает правило контракта, но не делает вид, что охватил все codec и language bindings.'),
|
||||
h2('Error semantics как отдельный договор'),
|
||||
p('Named envelope выбран не из-за удобства JSON. Он выбран потому, что error mode должен быть наблюдаемым полем, а не побочным эффектом чтения. В example <code>code</code> и <code>retry</code> остаются явными даже при null code. Благодаря этому проверка отличает «значение отсутствует в envelope» от «сам envelope не был задан». Эти ситуации нельзя объединять: первая допускается одним literal, вторая закрывает contract.'),
|
||||
p('Смешанный error mode особенно опасен в документации. Автор может сказать, что adapter «умеет бросать или возвращать ошибку», а consumer начнёт проверять только удобный путь. Fixed reviewer поступает иначе: presence <code>thrown-value</code> в одном adapter record не трактуется и не оборачивается. Он делает результат несопоставимым. Это освобождает текст от недоказуемых утверждений о stack, class и catch semantics конкретной среды.'),
|
||||
h2('Время как порядок, а не наблюдение'),
|
||||
p('Logical ticks помогают отделить порядок шага от наблюдаемой длительности. У интерактивной системы могут быть задержки, планировщик и внешний transport, но none of them присутствуют в literal. Когда boundary сохраняет 100 и 108, он утверждает только то, что author fixed two ordered integers. Такой минимализм не беднее реального наблюдения; он честнее к поставленному вопросу.'),
|
||||
p('Нельзя заменить missing closed tick формулой или приблизить его текущим моментом. Тогда тест станет зависеть от clock, а статья начнёт говорить о runtime effect. Rule требует, чтобы time basis совпала также у adapters. Этот шаг ловит ситуацию, где верхний contract использует logical ticks, а один participant тайно переименовал их в wall-clock. Даже при равных числах это разные единицы вывода.'),
|
||||
h2('Практический способ читать fixture'),
|
||||
ol(['Сначала смотреть на expected status, а не на реализацию проверки.', 'Для каждого fail case назвать единственное изменённое свойство literal.', 'Проверить, что status не шире причины: time case не сообщает ничего об error.', 'Проверить, что accepted case не содержит слов о связи processes или services.', 'Добавлять новый case только для нового rule, а не для повторения happy path.']),
|
||||
p('Fixture тем самым работает как маленькая карта допустимых утверждений. Она не обеспечивает полноту языка, но показывает, где text обязан остановиться. Это важнее для boundary documentation, чем набор эффектных примеров: документ должен пережить смену реализации, не обещая того, что проверка не могла наблюдать.'),
|
||||
h2('Предел модели'),
|
||||
p('Matrix не содержит union types, nullable policy, memory layout, stack unwinding, scheduling, ABI, serialization format или schema registry. Она не использует сеть, filesystem, clock, telemetry, profile, trace и PII. Эти отсутствия намеренные. Добавлять их через «ещё одно поле details» опасно: получится контейнер, который снова потребует неявной интерпретации.'),
|
||||
p('У модели нет права объявить error recoverable, type compatible или time measured вне своих literals. Если следующее решение требует это сказать, его нужно описать отдельным пакетом с собственными sources и границами. Надёжность этого текста как раз в том, что он не берёт чужую доказательную нагрузку.'),
|
||||
h2('Следующий шаг'),
|
||||
p('Добавьте fixture, где value tag сохранён, но один adapter записывает другую schemaVersion. Она должна вернуть точный stop, не accepted result. Затем проверьте, что опубликованный пример по-прежнему не имеет общих длинных фрагментов с practice и field article. Это поддерживает три самостоятельные идеи: boundary, semantics и evidence review.'),
|
||||
h2('Проверяемые источники'), MECHANISM_SOURCES,
|
||||
].join('');
|
||||
|
||||
const fieldContent = [
|
||||
h2('Evidence до обсуждения интеграции'),
|
||||
p('Самая дорогая ошибка на межъязыковой границе — назвать integration success там, где есть только три похожих описания. Тогда команда тратит время на согласование вывода, а не на проверку условия. Цена растёт ещё сильнее, когда review использует реальные логи, окружение или устные сведения, но не может показать, какой факт поддерживает решение.'),
|
||||
p('Этот field protocol намеренно не тестирует интеграцию. Его positive path — только synthetic integration review hand-off. Пакет берёт named fixed JS literal, формулирует один question и либо возвращает hand-off, либо останавливается с причиной. Ни PHP, ни JavaScript, ни D не запускаются; model names не дают права говорить о сервисе, deploy или production effect.'),
|
||||
h2('Что составляет evidence packet'),
|
||||
p('Packet не является сертификатом. Он состоит из input record, review question, requested outcome и boundary. Вопрос узкий: «внутренне сопоставим ли fixed contract record». Outcome заранее ограничен. Boundary говорит, что запрещены network, filesystem, clock, telemetry, service, runtime, trace, profile, PII и deployment. Эти поля делают отрицание части данных, а не скрытым соглашением.'),
|
||||
p('Такой пакет полезен тем, что другой рецензент может запустить тот же public export и получить тот же status. Ему не нужен доступ к панели, секрету или репозиторию чужого адаптера. Если для вывода всё же нужен внешний контекст, значит он не относится к этому package и не должен пробраться в формулировку hand-off.'),
|
||||
table('Evidence, которое этот review принимает и отклоняет', ['Артефакт', 'Роль в пакете', 'Чего он не подтверждает'], [['fixed input record', 'единственный наблюдаемый объект', 'реальный запрос'], ['named stop reason', 'показывает недостающую связь', 'производственный сбой'], ['exact adapter mapping', 'сохраняет shape в literal', 'работу адаптера'], ['ordered fixed ticks', 'задаёт synthetic interval', 'latency или SLA'], ['hand-off', 'разрешает дальнейшее чтение record', 'integration certificate']]),
|
||||
figure('/assets/editorial/2026/multi-runtime-2026-integration-evidence-loop.svg', 'Цикл evidence review: fixed input, four boundary checks, named stop или synthetic hand-off и возврат к следующему literal.', 'Визуал не содержит среды выполнения, сетевых стрелок или production-метрик; его петля — процедура чтения данных в памяти.'),
|
||||
h2('Последовательность review'),
|
||||
ol(['Взять ровно один packet, созданный public export, и не дополнять его внешними фактами.', 'Сверить requested outcome: разрешён только synthetic multi-runtime review hand-off.', 'Проверить, что boundary явно запрещает внешнее наблюдение.', 'Передать input в contract reviewer и сохранить его status без редакторской замены.', 'При stop вернуть точную named reason владельцу record.', 'При accepted status передать packet на review, не объявляя интеграцию выполненной.']),
|
||||
h2('Исполняемый hand-off'),
|
||||
code("import { createFixedEvidencePacket, reviewFixedEvidencePacket } from './upgrade-2026-06.mjs';\n\nconst packet = createFixedEvidencePacket('named-contract-v1');\nconst outcome = reviewFixedEvidencePacket(packet);\n\nconsole.log({\n status: outcome.status,\n nextAction: outcome.nextAction,\n productionEffect: outcome.productionEffect,\n});\n// synthetic-integration-review-hand-off, hand-off-fixed-synthetic-review, not-attempted"),
|
||||
p('Слово integration в status описывает предмет обсуждения, а не осуществлённое действие. В accepted literal evidence показывает лишь следующее: fixed data полностью соответствует rules, а внешний эффект не предпринимался. Правильный ответ на вопрос «значит ли это, что системы связаны?» — нет. Пакет специально не имеет возможности вернуть такой вывод.'),
|
||||
h2('Причины stop не должны быть декоративными'),
|
||||
p('Если packet содержит incomplete contract, review возвращает <code>stop-incomplete-contract</code> с указанием, что нужно назвать schema, operation и tag. Если adapters смешивают envelope и thrown value, причина <code>adapter-does-not-preserve-contract-shape</code> не даёт им слиться в удобное «ошибок нет». Если mapping coerced, reason остаётся отдельным: перенос shape не доказан.'),
|
||||
p('Если closed tick отсутствует или basis не fixed logical, status <code>stop-undetermined-time-boundary</code> не позволяет написать «быстро» или «медленно». Он не требует получить clock прямо сейчас. Он просто завершает эту проверку с именованным отсутствующим фактом. Такой stop можно переслать владельцу без толкования и без расширения scope.'),
|
||||
h2('Как вести разговор после отказа'),
|
||||
p('Отказ — хороший hand-off только тогда, когда следующий шаг меньше исходной задачи. Вместо «исследовать все runtime» причина предлагает назвать одно поле или удалить coercion. Следующий literal можно подготовить в памяти и снова пропустить через те же exports. Это дисциплинирует обсуждение: изменение фиксируется как данные, а не как воспоминание о том, что якобы произошло на стенде.'),
|
||||
p('Важен и обратный запрет. Нельзя использовать accepted packet как доказательство, что следующая реальная интеграция безопасна. У record нет host, transport, retry policy конкретного клиента, доступов, версии зависимостей или данных пользователя. Неявно добавив хотя бы одно из них, автор превратит synthetic review в неподтверждённый operational claim.'),
|
||||
h2('Минимальный журнал решения'),
|
||||
p('Для review достаточно сохранить packetId, input id, status, reasons или nextAction и productionEffect. Этого достаточно, чтобы повторить логику. Не надо добавлять timestamp запуска, trace id или ссылку на реальный incident: таких фактов в данной модели не существует. Короткий журнал лучше длинного, если он не создаёт ложный след наблюдения.'),
|
||||
p('Такой подход отражает зрелый T-shaped навык: автор умеет соединить contract design, type reasoning и review discipline, но удерживает границу между моделью и эксплуатацией. Текст не заменяет специалиста по PHP, JavaScript или D. Он делает видно, какой именно вопрос ещё не подтверждён специалистом и почему.'),
|
||||
h2('Кому и что передаёт hand-off'),
|
||||
p('Hand-off не назначает владельца интеграции и не открывает change request. Он передаёт рецензенту ограниченный артефакт: packetId, вопрос, фиксированный input, status и запрет production effect. Этого достаточно, чтобы другой человек проверил процедуру. Этого недостаточно, чтобы кто-либо подключил endpoint, изменил конфигурацию или решил, что реальный contract уже согласован.'),
|
||||
p('Для автора hand-off снимает соблазн дописать уверенный итог. Его работа завершена, когда stop или accepted status воспроизводимы. Для владельца boundary это означает более ясную очередь: он видит, нужно ли назвать field, выровнять semantics или остановить инициативу. Для domain reviewer это означает, что business interpretation value tag остаётся отдельным вопросом, не смешанным с формой record.'),
|
||||
p('Важно, что evidence packet не передаёт доверие по цепочке. Новый reviewer не обязан верить предыдущему: он повторяет public export над тем же literal. Если результат отличается, проблема в коде или input, а не в комментарии к pull request. Такая проверяемость полезнее истории переписки, потому что не зависит от доступа к ушедшему сотруднику или внешней панели.'),
|
||||
h2('Пять вопросов перед словом integration'),
|
||||
table('Пять вопросов перед словом integration', ['Вопрос', 'Ответ внутри P100'], [['Что было запущено?', 'Ничего: в памяти прочитан fixed JS literal.'], ['Какие среды связаны?', 'Никакие; model labels являются данными.'], ['Что измерено?', 'Ничего; logical ticks не metric.'], ['Что доказано?', 'Только внутреннее соответствие named rules.'], ['Что разрешено дальше?', 'Только synthetic review hand-off или repair literal.']]),
|
||||
p('Эта таблица нужна не для ритуала. Она отсекает язык, который легко проскальзывает в заголовки: «проверили интеграцию», «совместимость подтверждена», «адаптер работает». Ни одна из этих фраз не поддержана packet. Чем ярче обещание, тем важнее вернуть его к наблюдаемому объекту. Если объектом остаётся literal, то и итог должен оставаться про literal.'),
|
||||
h2('Как выглядит корректное продолжение'),
|
||||
p('Допустимое продолжение не добавляет сетевой тест. Оно создаёт новый packet, в котором одна version не совпадает, и проверяет named stop. Можно также удалить retry из envelope и убедиться, что result становится incomplete contract. Эти изменения изолированы, обратимы и не требуют представлять, что реальная система доступна. Они развивают protocol, не подменяя его эксплуатацией.'),
|
||||
p('Недопустимое продолжение начинается словами «давайте просто посмотрим логи» или «позовём сервис на тестовом адресе». Возможно, это полезное действие в другом task, но здесь оно меняет authority и доказательную модель. Правильный ответ — остановиться и завести новую границу. P100 честно заканчивается до этого порога, поэтому его accepted path можно безопасно читать как review artifact, а не как operational decision.'),
|
||||
h2('Пример контрфакта'),
|
||||
p('Возьмём literal <code>undetermined-time-v1</code>. В нём названия model records и value tag сохранены, но time basis — wall-clock, а closed отсутствует. Если функция всё равно вернёт hand-off, она будет утверждать сравнимость без основания. Fixture требует другой результат. Так negative case проверяет не реализацию трёх языков, а честность самого review protocol.'),
|
||||
p('Аналогично <code>mixed-error-semantics-v1</code> не доказывает, что какой-то runtime бросает исключение. Он хранит строку <code>thrown-value</code> в fixed literal, чтобы показать: один contract cannot silently mix it with named envelope. Эта разница критична для процедуры, даже когда реальная система вообще не рассматривается.'),
|
||||
h2('Ограничения и следующий шаг'),
|
||||
p('P100 не запускает real runtime, не вызывает service, не читает сеть или диск, не использует clock, не получает telemetry, не содержит PII и не проверяет production effect. В нём нет profile, trace, certificate, deployment или утверждения о фактической совместимости. Это не недостающие шаги сборки, а условия корректности изолированного draft.'),
|
||||
p('Следующий шаг внутри этой же границы — добавить один fail-closed packet с несовпадающей версией adapter и буквально выполнить export. Если нужен реальный integration test, нужно остановиться: потребуется новое разрешение, другой scope и отдельные доказательства. Данный draft не пытается незаметно перейти эту черту.'),
|
||||
h2('Проверяемые источники'), FIELD_SOURCES,
|
||||
].join('');
|
||||
|
||||
export const revisions = [
|
||||
{ slug: 'editorial-2026-06-practice-multi-runtime', title: 'Три языка, один узкий контракт: граница без магии адаптеров', excerpt: 'Как назвать value, error и time так, чтобы fixed record можно было честно передать на review.', series: 'Практика системной инженерии', readingMinutes: 20, contentHtml: practiceContent },
|
||||
{ slug: 'editorial-2026-06-mechanism-multi-runtime', title: 'Type, error, time: почему похожий payload ещё не общий смысл', excerpt: 'Механика synthetic matrix, которая останавливает смешанную семантику до ложного вывода.', series: 'Механика систем', readingMinutes: 20, contentHtml: mechanismContent },
|
||||
{ slug: 'editorial-2026-06-field-multi-runtime', title: 'Integration review без интеграции: evidence и точка stop', excerpt: 'Как передать проверяемый synthetic packet, не выдав его за сертификат среды.', series: 'Полевые заметки', readingMinutes: 20, contentHtml: fieldContent },
|
||||
];
|
||||
|
||||
export function verifyRevisionsAgainstFixture() {
|
||||
const fixture = runFixedMultiRuntimeFixture();
|
||||
const articleChecks = revisions.map((revision) => {
|
||||
const text = bodyText(revision.contentHtml);
|
||||
return text.length >= 5000 && text.length <= 15000 && /(?:цен[аы]|стоимост|ошибк)/i.test(text.slice(0, 900)) && /<table>/.test(revision.contentHtml) && /<figure>/.test(revision.contentHtml) && /<pre><code>/.test(revision.contentHtml) && /<ol>/.test(revision.contentHtml);
|
||||
});
|
||||
return deepFreeze({ passed: fixture.passed + articleChecks.filter(Boolean).length, total: fixture.total + articleChecks.length, accepted: fixture.accepted && articleChecks.every(Boolean), fixture, articleChecks, characters: Object.fromEntries(revisions.map((revision) => [revision.slug, bodyText(revision.contentHtml).length])) });
|
||||
}
|
||||
|
||||
if (process.argv.includes('--verify-fixture')) {
|
||||
const report = verifyRevisionsAgainstFixture();
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
if (!report.accepted) process.exitCode = 1;
|
||||
}
|
||||
if (process.argv.includes('--print-revisions')) console.log(JSON.stringify(revisions));
|
||||
Reference in New Issue
Block a user