isthmus-cli 0.5.0 → 0.6.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/README.ko.md +49 -28
- package/README.md +55 -31
- package/Skills/isthmus/SKILL.md +93 -4
- package/dist/cli/command-support.d.ts +1 -1
- package/dist/cli/command-support.js +2 -2
- package/dist/cli/command-support.js.map +1 -1
- package/dist/cli/impact-command.d.ts +5 -0
- package/dist/cli/impact-command.js +111 -0
- package/dist/cli/impact-command.js.map +1 -0
- package/dist/cli/main.js +15 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/preflight-command.d.ts +5 -0
- package/dist/cli/preflight-command.js +83 -0
- package/dist/cli/preflight-command.js.map +1 -0
- package/dist/cli/runtime-command.d.ts +5 -0
- package/dist/cli/runtime-command.js +42 -0
- package/dist/cli/runtime-command.js.map +1 -0
- package/dist/cli/runtime-json-reader.d.ts +12 -0
- package/dist/cli/runtime-json-reader.js +37 -0
- package/dist/cli/runtime-json-reader.js.map +1 -0
- package/dist/exchange/impact-selection.d.ts +13 -0
- package/dist/exchange/impact-selection.js +35 -0
- package/dist/exchange/impact-selection.js.map +1 -0
- package/dist/exchange/kartograph-impact.d.ts +4 -0
- package/dist/exchange/kartograph-impact.js +169 -0
- package/dist/exchange/kartograph-impact.js.map +1 -0
- package/dist/exchange/messages.d.ts +51 -0
- package/dist/exchange/messages.js +126 -0
- package/dist/exchange/messages.js.map +1 -0
- package/dist/exchange/parse.d.ts +8 -0
- package/dist/exchange/parse.js +12 -8
- package/dist/exchange/parse.js.map +1 -1
- package/dist/exchange/preflight-context.d.ts +71 -0
- package/dist/exchange/preflight-context.js +317 -0
- package/dist/exchange/preflight-context.js.map +1 -0
- package/dist/exchange/producer-impact.d.ts +17 -0
- package/dist/exchange/producer-impact.js +226 -0
- package/dist/exchange/producer-impact.js.map +1 -0
- package/dist/exchange/runtime.d.ts +69 -0
- package/dist/exchange/runtime.js +160 -0
- package/dist/exchange/runtime.js.map +1 -0
- package/dist/join/message-address.d.ts +7 -0
- package/dist/join/message-address.js +37 -0
- package/dist/join/message-address.js.map +1 -0
- package/dist/join/messages.d.ts +23 -0
- package/dist/join/messages.js +80 -0
- package/dist/join/messages.js.map +1 -0
- package/dist/report/diff.js +4 -3
- package/dist/report/diff.js.map +1 -1
- package/dist/report/impact.d.ts +62 -0
- package/dist/report/impact.js +168 -0
- package/dist/report/impact.js.map +1 -0
- package/dist/report/preflight-runtime.d.ts +42 -0
- package/dist/report/preflight-runtime.js +193 -0
- package/dist/report/preflight-runtime.js.map +1 -0
- package/dist/report/preflight-view.d.ts +146 -0
- package/dist/report/preflight-view.js +189 -0
- package/dist/report/preflight-view.js.map +1 -0
- package/dist/report/preflight.d.ts +104 -0
- package/dist/report/preflight.js +296 -0
- package/dist/report/preflight.js.map +1 -0
- package/dist/report/runtime-impact.d.ts +38 -0
- package/dist/report/runtime-impact.js +94 -0
- package/dist/report/runtime-impact.js.map +1 -0
- package/dist/report/runtime.d.ts +54 -0
- package/dist/report/runtime.js +137 -0
- package/dist/report/runtime.js.map +1 -0
- package/dist/report/sorted-json.d.ts +1 -1
- package/dist/report/sorted-json.js +2 -2
- package/dist/report/sorted-json.js.map +1 -1
- package/docs/BRIDGE-MESSAGES.md +102 -0
- package/docs/GRAPH-EXCHANGE.md +255 -0
- package/docs/IMPACT.md +96 -0
- package/docs/PREFLIGHT.md +295 -0
- package/docs/RUNTIME.md +172 -0
- package/docs/TOOLCHAIN.md +101 -0
- package/package.json +11 -2
- package/scripts/build-preflight-toolchain.mjs +186 -0
- package/scripts/capture-preflight.mjs +392 -0
- package/scripts/run-child.mjs +16 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"producer-impact.js","sourceRoot":"","sources":["../../src/exchange/producer-impact.ts"],"names":[],"mappings":"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","sourcesContent":["import { isProjectRelativePath, isSafeNonEmptyString } from './parse.ts';\nimport { parseImpactSelection } from './impact-selection.ts';\nimport type { ImpactSelection } from './impact-selection.ts';\nimport {\n validateLanguageImpact,\n PreflightValidationError,\n type ImpactSymbol,\n type LanguageImpact,\n} from './preflight-context.ts';\nimport type { BridgeLocation } from './parse.ts';\n\n/** producer 영향 문서에 주입할 isthmus 실행 문맥이다. */\nexport interface ProducerImpactMetadata {\n readonly id: string;\n readonly project: string;\n readonly requested: ImpactSelection;\n readonly tool: Readonly<{ name: string; version: string }>;\n readonly trigger?: string;\n}\n\n/** Cartograph `change-impact` v1을 isthmus 공통 영향 형식으로 투영한다. */\nexport function adaptCartographImpact(raw: unknown, metadata: ProducerImpactMetadata): LanguageImpact {\n const value = object(raw, 'Cartograph impact must be a JSON object.');\n if (value.format !== 'change-impact' || value.version !== 1 || value.level !== 'symbol') {\n fail('Expected Cartograph change-impact version 1 at symbol level.');\n }\n const context = normalizeMetadata(metadata, 'swift');\n const limitations = textStrings(value.limitations, 'Invalid Cartograph limitations.');\n for (const [field, code] of [['selectionIssues', 'selection-issues'], ['runtimeReview', 'runtime-review'],\n ['runtimeDependencies', 'runtime-dependencies']] as const) {\n if (value[field] === undefined) continue;\n const rows = rawArray(value[field], `Invalid Cartograph ${field}.`);\n if (rows.length > 0) limitations.push(`cartograph-${code}: ${rows.length} item(s) require review in the producer report`);\n }\n let outsideLocations = 0;\n const roots = rawArray(value.changeScope, 'Invalid Cartograph change scope.').map((item) => {\n const parsed = parseCartographSymbol(item, context.project, () => outsideLocations++);\n return parsed;\n });\n const affected = rawArray(value.affected, 'Invalid Cartograph affected symbols.').map((item) => {\n const row = object(item, 'Invalid Cartograph affected symbol.');\n const symbol = parseCartographSymbol(row.symbol, context.project, () => outsideLocations++);\n const via = safe(row.via, 'Invalid Cartograph affected parent.');\n const depth = positiveDepth(row.depth);\n const relationship = safe(row.relationship, 'Invalid Cartograph relationship.');\n const edges = row.edges === undefined ? [] : rawStrings(row.edges, 'Invalid Cartograph edges.');\n const relationships = [...new Set([relationship, ...edges])];\n if (relationships.length > 32) fail('Cartograph relationships exceed their limit.');\n return { symbol, via, depth, relationships };\n });\n if (outsideLocations > 0) {\n limitations.push(`cartograph-location-outside-project: ${outsideLocations} symbol location(s) omitted`);\n }\n const result = {\n ...context,\n roots,\n affected,\n limitations,\n truncated: truncation(value.truncated),\n } satisfies LanguageImpact;\n return validateLanguageImpact(result);\n}\n\n/** Dartograph `_document` v1을 isthmus 공통 영향 형식으로 투영한다. */\nexport function adaptDartographImpact(raw: unknown, metadata: ProducerImpactMetadata): LanguageImpact {\n const value = object(raw, 'Dartograph impact must be a JSON object.');\n if (value.version !== 1) fail('Expected Dartograph impact version 1.');\n const context = normalizeMetadata(metadata, 'dart');\n const changed = object(value.changed, 'Invalid Dartograph changed scope.');\n const changedSymbols = rawStrings(changed.symbols, 'Invalid Dartograph changed symbols.');\n const changedLibraries = changed.libraries === undefined ? [] : rawStrings(changed.libraries, 'Invalid Dartograph changed libraries.');\n if (changed.sources !== undefined) {\n rawStrings(changed.sources, 'Invalid Dartograph changed sources.').forEach(dartSource);\n }\n const roots: ImpactSymbol[] = [\n ...changedLibraries.map((id) => ({ id, qualifiedName: id, kind: 'library' })),\n ...changedSymbols.map((id) => ({ id, qualifiedName: id, kind: 'declaration' })),\n ];\n const rootIds = new Set(roots.map(({ id }) => id));\n const impacted = rawArray(value.impacted, 'Invalid Dartograph impacted symbols.');\n const rows = impacted.map((item) => {\n const row = object(item, 'Invalid Dartograph impacted symbol.');\n const id = safe(row.id, 'Invalid Dartograph impacted symbol id.');\n const depth = positiveDepth(row.depth);\n const path = rawStrings(row.path, 'Invalid Dartograph impact path.');\n if (path.length !== depth + 1 || path[0] !== id || !rootIds.has(path[path.length - 1]!) ||\n new Set(path).size !== path.length) {\n fail('Dartograph impact path is not a complete parent-safe path.');\n }\n const location = dartLocation(row);\n if (row.source !== undefined) dartSource(row.source);\n return {\n symbol: { id, qualifiedName: id, ...(location === undefined ? {} : { location }),\n ...(row.kind === undefined ? {} : { kind: safe(row.kind, 'Invalid Dartograph impacted kind.') }) },\n via: path[1] as string,\n depth,\n relationships: [],\n path,\n };\n });\n const known = new Set([...roots.map((root) => root.id), ...rows.map((row) => row.symbol.id)]);\n for (const row of rows) {\n if (row.path.some((id) => !known.has(id))) fail('Dartograph impact path contains an omitted intermediate symbol.');\n }\n const limitations = textStrings(value.limitations, 'Invalid Dartograph limitations.');\n const missing = value.missingSymbols === undefined ? [] : rawStrings(value.missingSymbols, 'Invalid Dartograph missing symbols.');\n const unattributed = changed.unattributedSources === undefined ? [] : rawStrings(changed.unattributedSources, 'Invalid Dartograph unattributed sources.');\n unattributed.forEach(dartSource);\n if (missing.length > 0) limitations.push(`dartograph-missing-symbols: ${missing.length} requested symbol(s) were not found`);\n if (unattributed.length > 0) limitations.push(`dartograph-unattributed-sources: ${unattributed.length} changed source(s) could not be attributed`);\n const result: LanguageImpact = {\n ...context,\n roots,\n affected: rows.map(({ path: _path, ...row }) => row),\n limitations,\n truncated: dartTruncation(value.truncated),\n };\n return validateLanguageImpact(result);\n}\n\ntype AdapterContext = {\n readonly id: string;\n readonly project: string;\n readonly requested: ImpactSelection;\n readonly tool: Readonly<{ name: string; version: string }>;\n readonly trigger?: string;\n readonly platform: 'dart' | 'swift';\n};\n\nfunction normalizeMetadata(metadata: ProducerImpactMetadata, platform: 'dart' | 'swift'): AdapterContext {\n const id = safe(metadata.id, 'Invalid producer impact id.');\n const project = safe(metadata.project, 'Invalid producer impact project.');\n const tool = {\n name: safe(metadata.tool?.name, 'Invalid producer impact tool name.'),\n version: safe(metadata.tool?.version, 'Invalid producer impact tool version.'),\n };\n let requested: ImpactSelection;\n try {\n requested = parseImpactSelection({ format: 'isthmus-changes', version: 1, ...metadata.requested });\n } catch {\n fail('Invalid producer impact selection.');\n }\n return { id, project, requested, tool, ...(metadata.trigger === undefined ? {} : { trigger: safe(metadata.trigger, 'Invalid producer impact trigger.') }), platform };\n}\n\nfunction parseCartographSymbol(input: unknown, project: string, outside: () => void): ImpactSymbol {\n const value = object(input, 'Invalid Cartograph symbol.');\n const id = safe(value.usr, 'Cartograph symbols require a stable usr identity.');\n const qualifiedName = safe(value.qualifiedName, 'Invalid Cartograph symbol qualified name.');\n const kind = value.kind === undefined ? undefined : safe(value.kind, 'Invalid Cartograph symbol kind.');\n const rawLocation = value.location;\n if (rawLocation === undefined || rawLocation === null) return { id, qualifiedName, ...(kind === undefined ? {} : { kind }) };\n const location = rebaseLocation(rawLocation, project, outside);\n return location === undefined ? { id, qualifiedName, ...(kind === undefined ? {} : { kind }) } : { id, qualifiedName, ...(kind === undefined ? {} : { kind }), location };\n}\n\nfunction rebaseLocation(input: unknown, project: string, outside: () => void): BridgeLocation | undefined {\n const value = object(input, 'Invalid Cartograph symbol location.');\n const path = safe(value.path, 'Invalid Cartograph symbol location path.');\n if (!Number.isSafeInteger(value.line) || (value.line as number) < 1 ||\n !Number.isSafeInteger(value.column) || (value.column as number) < 1) {\n fail('Invalid Cartograph symbol location coordinates.');\n }\n if (!path.startsWith('/')) fail('Cartograph symbol locations must be absolute paths.');\n const root = project === '/' ? '/' : project.replace(/\\/+$/u, '');\n if (path === root) {\n outside();\n return undefined;\n }\n const prefix = root === '/' ? '/' : `${root}/`;\n if (!path.startsWith(prefix)) {\n outside();\n return undefined;\n }\n const relative = path.slice(prefix.length);\n if (!isProjectRelativePath(relative)) fail('Invalid rebased Cartograph symbol location.');\n return { path: relative, line: value.line as number, column: value.column as number };\n}\n\nfunction dartLocation(value: Record<string, unknown>): BridgeLocation | undefined {\n if (value.line === undefined && value.column === undefined) return undefined;\n if (!Number.isSafeInteger(value.line) || (value.line as number) < 1 ||\n !Number.isSafeInteger(value.column) || (value.column as number) < 1) {\n fail('Invalid Dartograph impact location.');\n }\n // producer가 GraphNode에서 실은 선언 위치만 사용하며 경로나 좌표를 추측하지 않는다.\n return value.source === undefined ? undefined\n : { path: dartSource(value.source), line: value.line as number, column: value.column as number };\n}\n\nfunction dartTruncation(input: unknown): boolean {\n if (!Number.isSafeInteger(input) || (input as number) < 0) fail('Invalid Dartograph truncation count.');\n return (input as number) > 0;\n}\n\nfunction dartSource(input: unknown): string {\n const source = safe(input, 'Invalid Dartograph impact source.');\n const path = source.startsWith('project:') ? source.slice('project:'.length) : source;\n if (!isProjectRelativePath(path)) fail('Dartograph impact source must be project-relative.');\n return path;\n}\n\nfunction truncation(input: unknown): boolean {\n if (typeof input === 'boolean') return input;\n const value = object(input, 'Invalid impact truncation.');\n if (typeof value.depth !== 'boolean' || typeof value.output !== 'boolean' || !Array.isArray(value.sections) ||\n !value.sections.every((section) => isSafeNonEmptyString(section))) fail('Invalid impact truncation.');\n return value.depth || value.output || value.sections.length > 0;\n}\n\nfunction positiveDepth(input: unknown): number {\n if (!Number.isSafeInteger(input) || (input as number) < 1 || (input as number) > 128) fail('Impact depth must be between 1 and 128.');\n return input as number;\n}\n\nfunction object(input: unknown, message: string): Record<string, unknown> {\n if (typeof input !== 'object' || input === null || Array.isArray(input)) fail(message);\n return input as Record<string, unknown>;\n}\n\nfunction rawArray(input: unknown, message: string): unknown[] {\n if (!Array.isArray(input) || input.length > 50_000) fail(message);\n return input;\n}\n\nfunction rawStrings(input: unknown, message: string): string[] {\n if (!Array.isArray(input) || input.length > 50_000 || !input.every((item) => isSafeNonEmptyString(item))) fail(message);\n return [...input] as string[];\n}\n\nfunction textStrings(input: unknown, message: string): string[] {\n if (!Array.isArray(input) || input.length > 50_000 || !input.every((item) => typeof item === 'string')) fail(message);\n return [...input] as string[];\n}\n\nfunction safe(input: unknown, message: string): string {\n if (!isSafeNonEmptyString(input)) fail(message);\n return input;\n}\n\nfunction fail(message: string): never {\n throw new PreflightValidationError(message);\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { BridgeLocation } from './parse.ts';
|
|
2
|
+
/** 실제 실행에서 관찰한 플랫폼이다. Dart/Swift 같은 소스 언어와 혼동하지 않는다. */
|
|
3
|
+
export type RuntimePlatform = 'ios' | 'macos' | 'android' | 'linux' | 'windows';
|
|
4
|
+
/** 메시지 형태가 다른 채널을 이름만으로 합치지 않도록 구분한다. */
|
|
5
|
+
export type RuntimeTransport = 'method-channel' | 'basic-message-channel';
|
|
6
|
+
/** 원문 예외나 payload 없이 통신 결과만 분류한다. */
|
|
7
|
+
export type RuntimeOutcome = 'success' | 'missing-handler' | 'error' | 'timeout' | 'pending';
|
|
8
|
+
/** 기대 목록에서 허용할 수 있는 응답 결과다. pending은 종료 결과가 아니므로 제외한다. */
|
|
9
|
+
export type RuntimeTerminalOutcome = Exclude<RuntimeOutcome, 'pending'>;
|
|
10
|
+
/** 결과 허용 목록이 입력을 통한 비용 증폭을 일으키지 않도록 제한한다. */
|
|
11
|
+
export declare const MAX_RUNTIME_ALLOWED_OUTCOMES = 4;
|
|
12
|
+
/** 실제 라우팅 주소다. BasicMessageChannel에는 가상의 method를 만들지 않는다. */
|
|
13
|
+
export interface RuntimeRoute {
|
|
14
|
+
readonly transport: RuntimeTransport;
|
|
15
|
+
readonly channel: string;
|
|
16
|
+
readonly method?: string;
|
|
17
|
+
}
|
|
18
|
+
/** 실행 순서와 엔진 인스턴스를 가진 호출 관찰이다. */
|
|
19
|
+
export interface RuntimeEvent extends RuntimeRoute {
|
|
20
|
+
readonly sequence: number;
|
|
21
|
+
readonly instance: string;
|
|
22
|
+
readonly outcome: RuntimeOutcome;
|
|
23
|
+
readonly caller?: BridgeLocation;
|
|
24
|
+
}
|
|
25
|
+
/** 한 시나리오 실행의 폐쇄 여부와 코드 revision을 선언하는 생산 문서다. */
|
|
26
|
+
export interface BridgeRuntimeDocument {
|
|
27
|
+
readonly format: 'bridge-runtime';
|
|
28
|
+
readonly version: 1;
|
|
29
|
+
readonly project: string;
|
|
30
|
+
readonly revision: string;
|
|
31
|
+
readonly tool: {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly version: string;
|
|
34
|
+
};
|
|
35
|
+
readonly run: {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly scenario: string;
|
|
38
|
+
readonly platform: RuntimePlatform;
|
|
39
|
+
readonly status: 'completed' | 'incomplete';
|
|
40
|
+
readonly startedAt: string;
|
|
41
|
+
readonly finishedAt?: string;
|
|
42
|
+
};
|
|
43
|
+
readonly droppedEvents: number;
|
|
44
|
+
readonly events: readonly RuntimeEvent[];
|
|
45
|
+
}
|
|
46
|
+
/** 지정한 시나리오·플랫폼에서 관찰해야 하는 통신이다. instance 생략은 모든 인스턴스다. */
|
|
47
|
+
export interface RuntimeExpectation extends RuntimeRoute {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly scenario: string;
|
|
50
|
+
readonly platform: RuntimePlatform;
|
|
51
|
+
readonly instance?: string;
|
|
52
|
+
readonly allowedOutcomes?: readonly RuntimeTerminalOutcome[];
|
|
53
|
+
}
|
|
54
|
+
/** CI가 실행해야 하는 검증 목록. 런타임 로그에서 기대값을 역생성하지 않는다. */
|
|
55
|
+
export interface RuntimeExpectations {
|
|
56
|
+
readonly format: 'bridge-expectations';
|
|
57
|
+
readonly version: 1;
|
|
58
|
+
readonly project: string;
|
|
59
|
+
readonly revision: string;
|
|
60
|
+
readonly checks: readonly RuntimeExpectation[];
|
|
61
|
+
}
|
|
62
|
+
/** 입력 계약 실패를 원문 데이터 없는 메시지로 전달한다. */
|
|
63
|
+
export declare class RuntimeValidationError extends Error {
|
|
64
|
+
constructor(message: string);
|
|
65
|
+
}
|
|
66
|
+
/** 신뢰하지 않는 런타임 JSON에서 라우팅·관측 결과·생산 문맥만 보존한다. */
|
|
67
|
+
export declare function parseBridgeRuntime(input: unknown): BridgeRuntimeDocument;
|
|
68
|
+
/** 기대 목록의 빈 통과와 중복 식별자를 거부한다. */
|
|
69
|
+
export declare function parseRuntimeExpectations(input: unknown): RuntimeExpectations;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { isBridgeTimestamp, isProjectRelativePath, isSafeNonEmptyString } from "./parse.js";
|
|
2
|
+
/** 결과 허용 목록이 입력을 통한 비용 증폭을 일으키지 않도록 제한한다. */
|
|
3
|
+
export const MAX_RUNTIME_ALLOWED_OUTCOMES = 4;
|
|
4
|
+
/** 입력 계약 실패를 원문 데이터 없는 메시지로 전달한다. */
|
|
5
|
+
export class RuntimeValidationError extends Error {
|
|
6
|
+
constructor(message) { super(message); this.name = 'RuntimeValidationError'; }
|
|
7
|
+
}
|
|
8
|
+
/** 신뢰하지 않는 런타임 JSON에서 라우팅·관측 결과·생산 문맥만 보존한다. */
|
|
9
|
+
export function parseBridgeRuntime(input) {
|
|
10
|
+
const value = document(input, 'bridge-runtime');
|
|
11
|
+
const tool = object(value.tool);
|
|
12
|
+
const run = object(value.run);
|
|
13
|
+
const status = run.status;
|
|
14
|
+
if (status !== 'completed' && status !== 'incomplete')
|
|
15
|
+
fail('Invalid runtime run status.');
|
|
16
|
+
if (!isBridgeTimestamp(run.startedAt) ||
|
|
17
|
+
(run.finishedAt !== undefined && !isBridgeTimestamp(run.finishedAt)) ||
|
|
18
|
+
(status === 'completed' && run.finishedAt === undefined) ||
|
|
19
|
+
(typeof run.finishedAt === 'string' && Date.parse(run.finishedAt) < Date.parse(run.startedAt))) {
|
|
20
|
+
fail('Invalid runtime run timestamps.');
|
|
21
|
+
}
|
|
22
|
+
const droppedEvents = nonnegativeInteger(value.droppedEvents);
|
|
23
|
+
const rawEvents = array(value.events, 100_000);
|
|
24
|
+
const sequences = new Set();
|
|
25
|
+
let maximumSequence = 0;
|
|
26
|
+
const events = rawEvents.map((item) => {
|
|
27
|
+
const event = object(item);
|
|
28
|
+
const sequence = nonnegativeInteger(event.sequence);
|
|
29
|
+
if (sequence === 0 || sequences.has(sequence))
|
|
30
|
+
fail('Runtime sequences must be positive and unique.');
|
|
31
|
+
sequences.add(sequence);
|
|
32
|
+
maximumSequence = Math.max(maximumSequence, sequence);
|
|
33
|
+
return {
|
|
34
|
+
...route(event), sequence, instance: safeString(event.instance), outcome: outcome(event.outcome),
|
|
35
|
+
...(event.caller === undefined ? {} : { caller: location(event.caller) }),
|
|
36
|
+
};
|
|
37
|
+
}).sort((a, b) => a.sequence - b.sequence);
|
|
38
|
+
if (maximumSequence - events.length > droppedEvents) {
|
|
39
|
+
fail('Missing runtime sequences must be counted in droppedEvents.');
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
format: 'bridge-runtime', version: 1,
|
|
43
|
+
project: safeString(value.project), revision: safeString(value.revision),
|
|
44
|
+
tool: { name: safeString(tool.name), version: safeString(tool.version) },
|
|
45
|
+
run: { id: safeString(run.id), scenario: safeString(run.scenario),
|
|
46
|
+
platform: platform(run.platform), status, startedAt: run.startedAt,
|
|
47
|
+
...(run.finishedAt === undefined ? {} : { finishedAt: run.finishedAt }),
|
|
48
|
+
}, droppedEvents, events,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** 기대 목록의 빈 통과와 중복 식별자를 거부한다. */
|
|
52
|
+
export function parseRuntimeExpectations(input) {
|
|
53
|
+
const value = document(input, 'bridge-expectations');
|
|
54
|
+
const items = array(value.checks, 10_000);
|
|
55
|
+
if (items.length === 0)
|
|
56
|
+
fail('Runtime expectations require at least one check.');
|
|
57
|
+
const ids = new Set();
|
|
58
|
+
const checks = items.map((item) => {
|
|
59
|
+
const check = object(item);
|
|
60
|
+
const id = safeString(check.id);
|
|
61
|
+
if (ids.has(id))
|
|
62
|
+
fail('Runtime expectation ids must be unique.');
|
|
63
|
+
ids.add(id);
|
|
64
|
+
return {
|
|
65
|
+
...route(check), id, scenario: safeString(check.scenario), platform: platform(check.platform),
|
|
66
|
+
...(check.instance === undefined ? {} : { instance: safeString(check.instance) }),
|
|
67
|
+
allowedOutcomes: allowedOutcomes(check.allowedOutcomes),
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
return { format: 'bridge-expectations', version: 1,
|
|
71
|
+
project: safeString(value.project), revision: safeString(value.revision), checks };
|
|
72
|
+
}
|
|
73
|
+
/** 실행 문서와 기대 문서가 공유하는 버전 경계다. */
|
|
74
|
+
function document(input, format) {
|
|
75
|
+
const value = object(input);
|
|
76
|
+
if (value.format !== format || value.version !== 1)
|
|
77
|
+
fail('Unsupported runtime document format or version.');
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
/** 외부 JSON 객체의 구조만 검증한다. */
|
|
81
|
+
function object(input) {
|
|
82
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input))
|
|
83
|
+
fail('Expected a runtime JSON object.');
|
|
84
|
+
return input;
|
|
85
|
+
}
|
|
86
|
+
/** 상한 검사를 순회보다 먼저 수행한다. */
|
|
87
|
+
function array(input, maximum) {
|
|
88
|
+
if (!Array.isArray(input) || input.length > maximum)
|
|
89
|
+
fail('Invalid runtime array or item limit exceeded.');
|
|
90
|
+
return input;
|
|
91
|
+
}
|
|
92
|
+
/** 채널 이름·문맥 식별자에 기존 교환 문자열 규칙을 적용한다. */
|
|
93
|
+
function safeString(input) {
|
|
94
|
+
if (!isSafeNonEmptyString(input))
|
|
95
|
+
fail('Expected a non-empty safe runtime string.');
|
|
96
|
+
return input;
|
|
97
|
+
}
|
|
98
|
+
/** 유실 개수와 순서를 부동소수점이나 음수로 위조할 수 없게 한다. */
|
|
99
|
+
function nonnegativeInteger(input) {
|
|
100
|
+
if (!Number.isSafeInteger(input) || Number(input) < 0)
|
|
101
|
+
fail('Expected a nonnegative runtime integer.');
|
|
102
|
+
return input;
|
|
103
|
+
}
|
|
104
|
+
/** 플랫폼을 소스 경로나 실행 파일 이름에서 추측하지 않는다. */
|
|
105
|
+
function platform(input) {
|
|
106
|
+
if (input !== 'ios' && input !== 'macos' && input !== 'android' && input !== 'linux' && input !== 'windows') {
|
|
107
|
+
fail('Unsupported runtime platform.');
|
|
108
|
+
}
|
|
109
|
+
return input;
|
|
110
|
+
}
|
|
111
|
+
/** 런타임 채널별 주소 형태를 검증한다. */
|
|
112
|
+
function route(input) {
|
|
113
|
+
const transport = input.transport;
|
|
114
|
+
if (transport !== 'method-channel' && transport !== 'basic-message-channel')
|
|
115
|
+
fail('Unsupported runtime transport.');
|
|
116
|
+
const channel = safeString(input.channel);
|
|
117
|
+
if (transport === 'method-channel')
|
|
118
|
+
return { transport, channel, method: safeString(input.method) };
|
|
119
|
+
if (input.method !== undefined)
|
|
120
|
+
fail('Basic message routes cannot declare a method.');
|
|
121
|
+
return { transport, channel };
|
|
122
|
+
}
|
|
123
|
+
/** 예외 텍스트 대신 고정된 통신 결과만 허용한다. */
|
|
124
|
+
function outcome(input) {
|
|
125
|
+
if (input !== 'success' && input !== 'missing-handler' && input !== 'error' && input !== 'timeout' && input !== 'pending') {
|
|
126
|
+
fail('Unsupported runtime outcome.');
|
|
127
|
+
}
|
|
128
|
+
return input;
|
|
129
|
+
}
|
|
130
|
+
/** 기대 결과는 비어 있지 않은 고유 terminal outcome 목록으로 제한한다. */
|
|
131
|
+
function allowedOutcomes(input) {
|
|
132
|
+
if (input === undefined)
|
|
133
|
+
return ['success'];
|
|
134
|
+
if (!Array.isArray(input) || input.length === 0 || input.length > MAX_RUNTIME_ALLOWED_OUTCOMES) {
|
|
135
|
+
fail('Runtime allowed outcomes require 1 to 4 terminal outcomes.');
|
|
136
|
+
}
|
|
137
|
+
const seen = new Set();
|
|
138
|
+
const outcomes = [];
|
|
139
|
+
for (const item of input) {
|
|
140
|
+
if (item !== 'success' && item !== 'missing-handler' && item !== 'error' && item !== 'timeout') {
|
|
141
|
+
fail('Runtime allowed outcomes must be unique terminal outcomes.');
|
|
142
|
+
}
|
|
143
|
+
if (seen.has(item))
|
|
144
|
+
fail('Runtime allowed outcomes must be unique terminal outcomes.');
|
|
145
|
+
seen.add(item);
|
|
146
|
+
outcomes.push(item);
|
|
147
|
+
}
|
|
148
|
+
return outcomes;
|
|
149
|
+
}
|
|
150
|
+
/** 자동 추측한 호출 스택 대신 생산자가 선언한 상대 소스 위치를 검증한다. */
|
|
151
|
+
function location(input) {
|
|
152
|
+
const value = object(input);
|
|
153
|
+
if (!isProjectRelativePath(value.path) || !Number.isSafeInteger(value.line) || Number(value.line) < 1 ||
|
|
154
|
+
!Number.isSafeInteger(value.column) || Number(value.column) < 1)
|
|
155
|
+
fail('Invalid runtime caller location.');
|
|
156
|
+
return { path: value.path, line: value.line, column: value.column };
|
|
157
|
+
}
|
|
158
|
+
/** 입력 값이나 예외 payload를 메시지에 포함하지 않는다. */
|
|
159
|
+
function fail(message) { throw new RuntimeValidationError(message); }
|
|
160
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/exchange/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { isBridgeTimestamp, isProjectRelativePath, isSafeNonEmptyString } from './parse.ts';\nimport type { BridgeLocation } from './parse.ts';\n\n/** 실제 실행에서 관찰한 플랫폼이다. Dart/Swift 같은 소스 언어와 혼동하지 않는다. */\nexport type RuntimePlatform = 'ios' | 'macos' | 'android' | 'linux' | 'windows';\n/** 메시지 형태가 다른 채널을 이름만으로 합치지 않도록 구분한다. */\nexport type RuntimeTransport = 'method-channel' | 'basic-message-channel';\n/** 원문 예외나 payload 없이 통신 결과만 분류한다. */\nexport type RuntimeOutcome = 'success' | 'missing-handler' | 'error' | 'timeout' | 'pending';\n/** 기대 목록에서 허용할 수 있는 응답 결과다. pending은 종료 결과가 아니므로 제외한다. */\nexport type RuntimeTerminalOutcome = Exclude<RuntimeOutcome, 'pending'>;\n\n/** 결과 허용 목록이 입력을 통한 비용 증폭을 일으키지 않도록 제한한다. */\nexport const MAX_RUNTIME_ALLOWED_OUTCOMES = 4;\n\n/** 실제 라우팅 주소다. BasicMessageChannel에는 가상의 method를 만들지 않는다. */\nexport interface RuntimeRoute {\n readonly transport: RuntimeTransport;\n readonly channel: string;\n readonly method?: string;\n}\n\n/** 실행 순서와 엔진 인스턴스를 가진 호출 관찰이다. */\nexport interface RuntimeEvent extends RuntimeRoute {\n readonly sequence: number;\n readonly instance: string;\n readonly outcome: RuntimeOutcome;\n readonly caller?: BridgeLocation;\n}\n\n/** 한 시나리오 실행의 폐쇄 여부와 코드 revision을 선언하는 생산 문서다. */\nexport interface BridgeRuntimeDocument {\n readonly format: 'bridge-runtime';\n readonly version: 1;\n readonly project: string;\n readonly revision: string;\n readonly tool: { readonly name: string; readonly version: string };\n readonly run: {\n readonly id: string;\n readonly scenario: string;\n readonly platform: RuntimePlatform;\n readonly status: 'completed' | 'incomplete';\n readonly startedAt: string;\n readonly finishedAt?: string;\n };\n readonly droppedEvents: number;\n readonly events: readonly RuntimeEvent[];\n}\n\n/** 지정한 시나리오·플랫폼에서 관찰해야 하는 통신이다. instance 생략은 모든 인스턴스다. */\nexport interface RuntimeExpectation extends RuntimeRoute {\n readonly id: string;\n readonly scenario: string;\n readonly platform: RuntimePlatform;\n readonly instance?: string;\n readonly allowedOutcomes?: readonly RuntimeTerminalOutcome[];\n}\n\n/** CI가 실행해야 하는 검증 목록. 런타임 로그에서 기대값을 역생성하지 않는다. */\nexport interface RuntimeExpectations {\n readonly format: 'bridge-expectations';\n readonly version: 1;\n readonly project: string;\n readonly revision: string;\n readonly checks: readonly RuntimeExpectation[];\n}\n\n/** 입력 계약 실패를 원문 데이터 없는 메시지로 전달한다. */\nexport class RuntimeValidationError extends Error {\n constructor(message: string) { super(message); this.name = 'RuntimeValidationError'; }\n}\n\n/** 신뢰하지 않는 런타임 JSON에서 라우팅·관측 결과·생산 문맥만 보존한다. */\nexport function parseBridgeRuntime(input: unknown): BridgeRuntimeDocument {\n const value = document(input, 'bridge-runtime');\n const tool = object(value.tool);\n const run = object(value.run);\n const status = run.status;\n if (status !== 'completed' && status !== 'incomplete') fail('Invalid runtime run status.');\n if (!isBridgeTimestamp(run.startedAt) ||\n (run.finishedAt !== undefined && !isBridgeTimestamp(run.finishedAt)) ||\n (status === 'completed' && run.finishedAt === undefined) ||\n (typeof run.finishedAt === 'string' && Date.parse(run.finishedAt) < Date.parse(run.startedAt))) {\n fail('Invalid runtime run timestamps.');\n }\n const droppedEvents = nonnegativeInteger(value.droppedEvents);\n const rawEvents = array(value.events, 100_000);\n const sequences = new Set<number>();\n let maximumSequence = 0;\n const events = rawEvents.map((item): RuntimeEvent => {\n const event = object(item);\n const sequence = nonnegativeInteger(event.sequence);\n if (sequence === 0 || sequences.has(sequence)) fail('Runtime sequences must be positive and unique.');\n sequences.add(sequence);\n maximumSequence = Math.max(maximumSequence, sequence);\n return {\n ...route(event), sequence, instance: safeString(event.instance), outcome: outcome(event.outcome),\n ...(event.caller === undefined ? {} : { caller: location(event.caller) }),\n };\n }).sort((a, b) => a.sequence - b.sequence);\n if (maximumSequence - events.length > droppedEvents) {\n fail('Missing runtime sequences must be counted in droppedEvents.');\n }\n return {\n format: 'bridge-runtime', version: 1,\n project: safeString(value.project), revision: safeString(value.revision),\n tool: { name: safeString(tool.name), version: safeString(tool.version) },\n run: { id: safeString(run.id), scenario: safeString(run.scenario),\n platform: platform(run.platform), status, startedAt: run.startedAt,\n ...(run.finishedAt === undefined ? {} : { finishedAt: run.finishedAt as string }),\n }, droppedEvents, events,\n };\n}\n\n/** 기대 목록의 빈 통과와 중복 식별자를 거부한다. */\nexport function parseRuntimeExpectations(input: unknown): RuntimeExpectations {\n const value = document(input, 'bridge-expectations');\n const items = array(value.checks, 10_000);\n if (items.length === 0) fail('Runtime expectations require at least one check.');\n const ids = new Set<string>();\n const checks = items.map((item): RuntimeExpectation => {\n const check = object(item);\n const id = safeString(check.id);\n if (ids.has(id)) fail('Runtime expectation ids must be unique.');\n ids.add(id);\n return {\n ...route(check), id, scenario: safeString(check.scenario), platform: platform(check.platform),\n ...(check.instance === undefined ? {} : { instance: safeString(check.instance) }),\n allowedOutcomes: allowedOutcomes(check.allowedOutcomes),\n };\n });\n return { format: 'bridge-expectations', version: 1,\n project: safeString(value.project), revision: safeString(value.revision), checks };\n}\n\n/** 실행 문서와 기대 문서가 공유하는 버전 경계다. */\nfunction document(input: unknown, format: string): Record<string, unknown> {\n const value = object(input);\n if (value.format !== format || value.version !== 1) fail('Unsupported runtime document format or version.');\n return value;\n}\n\n/** 외부 JSON 객체의 구조만 검증한다. */\nfunction object(input: unknown): Record<string, unknown> {\n if (typeof input !== 'object' || input === null || Array.isArray(input)) fail('Expected a runtime JSON object.');\n return input as Record<string, unknown>;\n}\n\n/** 상한 검사를 순회보다 먼저 수행한다. */\nfunction array(input: unknown, maximum: number): unknown[] {\n if (!Array.isArray(input) || input.length > maximum) fail('Invalid runtime array or item limit exceeded.');\n return input;\n}\n\n/** 채널 이름·문맥 식별자에 기존 교환 문자열 규칙을 적용한다. */\nfunction safeString(input: unknown): string {\n if (!isSafeNonEmptyString(input)) fail('Expected a non-empty safe runtime string.');\n return input;\n}\n\n/** 유실 개수와 순서를 부동소수점이나 음수로 위조할 수 없게 한다. */\nfunction nonnegativeInteger(input: unknown): number {\n if (!Number.isSafeInteger(input) || Number(input) < 0) fail('Expected a nonnegative runtime integer.');\n return input as number;\n}\n\n/** 플랫폼을 소스 경로나 실행 파일 이름에서 추측하지 않는다. */\nfunction platform(input: unknown): RuntimePlatform {\n if (input !== 'ios' && input !== 'macos' && input !== 'android' && input !== 'linux' && input !== 'windows') {\n fail('Unsupported runtime platform.');\n }\n return input;\n}\n\n/** 런타임 채널별 주소 형태를 검증한다. */\nfunction route(input: Record<string, unknown>): RuntimeRoute {\n const transport = input.transport;\n if (transport !== 'method-channel' && transport !== 'basic-message-channel') fail('Unsupported runtime transport.');\n const channel = safeString(input.channel);\n if (transport === 'method-channel') return { transport, channel, method: safeString(input.method) };\n if (input.method !== undefined) fail('Basic message routes cannot declare a method.');\n return { transport, channel };\n}\n\n/** 예외 텍스트 대신 고정된 통신 결과만 허용한다. */\nfunction outcome(input: unknown): RuntimeOutcome {\n if (input !== 'success' && input !== 'missing-handler' && input !== 'error' && input !== 'timeout' && input !== 'pending') {\n fail('Unsupported runtime outcome.');\n }\n return input;\n}\n\n/** 기대 결과는 비어 있지 않은 고유 terminal outcome 목록으로 제한한다. */\nfunction allowedOutcomes(input: unknown): readonly RuntimeTerminalOutcome[] {\n if (input === undefined) return ['success'];\n if (!Array.isArray(input) || input.length === 0 || input.length > MAX_RUNTIME_ALLOWED_OUTCOMES) {\n fail('Runtime allowed outcomes require 1 to 4 terminal outcomes.');\n }\n const seen = new Set<RuntimeTerminalOutcome>();\n const outcomes: RuntimeTerminalOutcome[] = [];\n for (const item of input) {\n if (item !== 'success' && item !== 'missing-handler' && item !== 'error' && item !== 'timeout') {\n fail('Runtime allowed outcomes must be unique terminal outcomes.');\n }\n if (seen.has(item)) fail('Runtime allowed outcomes must be unique terminal outcomes.');\n seen.add(item);\n outcomes.push(item);\n }\n return outcomes;\n}\n\n/** 자동 추측한 호출 스택 대신 생산자가 선언한 상대 소스 위치를 검증한다. */\nfunction location(input: unknown): BridgeLocation {\n const value = object(input);\n if (!isProjectRelativePath(value.path) || !Number.isSafeInteger(value.line) || Number(value.line) < 1 ||\n !Number.isSafeInteger(value.column) || Number(value.column) < 1) fail('Invalid runtime caller location.');\n return { path: value.path, line: value.line as number, column: value.column as number };\n}\n\n/** 입력 값이나 예외 payload를 메시지에 포함하지 않는다. */\nfunction fail(message: string): never { throw new RuntimeValidationError(message); }\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** 리터럴과 증명된 접두사 주소를 입력 길이에 따라 조회한다. */
|
|
2
|
+
export class MessageAddressIndex {
|
|
3
|
+
literals = new Map();
|
|
4
|
+
root = { children: new Map(), values: [] };
|
|
5
|
+
add(address, matching, value) {
|
|
6
|
+
if (matching === 'literal') {
|
|
7
|
+
const values = this.literals.get(address) ?? [];
|
|
8
|
+
values.push(value);
|
|
9
|
+
this.literals.set(address, values);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
let node = this.root;
|
|
13
|
+
for (const character of address) {
|
|
14
|
+
let child = node.children.get(character);
|
|
15
|
+
if (child === undefined) {
|
|
16
|
+
child = { children: new Map(), values: [] };
|
|
17
|
+
node.children.set(character, child);
|
|
18
|
+
}
|
|
19
|
+
node = child;
|
|
20
|
+
}
|
|
21
|
+
node.values.push(value);
|
|
22
|
+
}
|
|
23
|
+
matching(address) {
|
|
24
|
+
const values = [...(this.literals.get(address) ?? [])];
|
|
25
|
+
let node = this.root;
|
|
26
|
+
for (const character of address) {
|
|
27
|
+
const next = node.children.get(character);
|
|
28
|
+
if (next === undefined)
|
|
29
|
+
break;
|
|
30
|
+
node = next;
|
|
31
|
+
for (const value of node.values)
|
|
32
|
+
values.push(value);
|
|
33
|
+
}
|
|
34
|
+
return values;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=message-address.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-address.js","sourceRoot":"","sources":["../../src/join/message-address.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,MAAM,OAAO,mBAAmB;IACb,QAAQ,GAAG,IAAI,GAAG,EAAe,CAAC;IAClC,IAAI,GAAkB,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAE3E,GAAG,CAAC,OAAe,EAAE,QAA8B,EAAE,KAAQ;QAC3D,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAChD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACnC,OAAO;QACT,CAAC;QACD,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACrB,KAAK,MAAM,SAAS,IAAI,OAAO,EAAE,CAAC;YAChC,IAAI,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACzC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBAAC,KAAK,GAAG,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;gBAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YAAC,CAAC;YAC9G,IAAI,GAAG,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,QAAQ,CAAC,OAAe;QACtB,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACvD,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACrB,KAAK,MAAM,SAAS,IAAI,OAAO,EAAE,CAAC;YAChC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC1C,IAAI,IAAI,KAAK,SAAS;gBAAE,MAAM;YAC9B,IAAI,GAAG,IAAI,CAAC;YACZ,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM;gBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF","sourcesContent":["/** 리터럴과 증명된 접두사 주소를 입력 길이에 따라 조회한다. */\nexport class MessageAddressIndex<T> {\n private readonly literals = new Map<string, T[]>();\n private readonly root: PrefixNode<T> = { children: new Map(), values: [] };\n\n add(address: string, matching: 'literal' | 'prefix', value: T): void {\n if (matching === 'literal') {\n const values = this.literals.get(address) ?? [];\n values.push(value);\n this.literals.set(address, values);\n return;\n }\n let node = this.root;\n for (const character of address) {\n let child = node.children.get(character);\n if (child === undefined) { child = { children: new Map(), values: [] }; node.children.set(character, child); }\n node = child;\n }\n node.values.push(value);\n }\n\n matching(address: string): readonly T[] {\n const values = [...(this.literals.get(address) ?? [])];\n let node = this.root;\n for (const character of address) {\n const next = node.children.get(character);\n if (next === undefined) break;\n node = next;\n for (const value of node.values) values.push(value);\n }\n return values;\n }\n}\n\ninterface PrefixNode<T> { readonly children: Map<string, PrefixNode<T>>; readonly values: T[]; }\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { BridgeHandlerDependency, BridgeHandlerScope, BridgeMessageDocument } from '../exchange/messages.ts';
|
|
2
|
+
import type { BridgeEndpoint, JoinLimitation } from './join.ts';
|
|
3
|
+
/** 동적 접두사의 원래 표현식도 근거에 보존한다. */
|
|
4
|
+
export interface MessageEndpoint extends BridgeEndpoint {
|
|
5
|
+
readonly channelExpression?: string;
|
|
6
|
+
readonly handlerScope?: BridgeHandlerScope;
|
|
7
|
+
readonly dependencies?: readonly BridgeHandlerDependency[];
|
|
8
|
+
}
|
|
9
|
+
/** literal 주소와 prefix 후보를 다른 키로 연결한다. */
|
|
10
|
+
export interface MessageBridgeRoute {
|
|
11
|
+
readonly channel: string;
|
|
12
|
+
readonly matching: 'literal' | 'prefix';
|
|
13
|
+
readonly senders: readonly MessageEndpoint[];
|
|
14
|
+
readonly handlers: readonly MessageEndpoint[];
|
|
15
|
+
}
|
|
16
|
+
/** Basic 범위에서 관찰한 경계와 끝내 연결하지 못한 사실이다. */
|
|
17
|
+
export interface MessageBridgeJoin {
|
|
18
|
+
readonly routes: readonly MessageBridgeRoute[];
|
|
19
|
+
readonly unresolved: readonly MessageEndpoint[];
|
|
20
|
+
readonly limitations: readonly JoinLimitation[];
|
|
21
|
+
}
|
|
22
|
+
/** 서로 같은 project의 양쪽 Basic 문서만 조인하며 MethodChannel 키 공간을 사용하지 않는다. */
|
|
23
|
+
export declare function joinMessageBridges(documents: readonly BridgeMessageDocument[], project: string): MessageBridgeJoin;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { compareStrings } from "../compare.js";
|
|
2
|
+
import { validateMessageDocuments } from "../exchange/messages.js";
|
|
3
|
+
import { BridgeJoinValidationError } from "./join.js";
|
|
4
|
+
import { MessageAddressIndex } from "./message-address.js";
|
|
5
|
+
/** 서로 같은 project의 양쪽 Basic 문서만 조인하며 MethodChannel 키 공간을 사용하지 않는다. */
|
|
6
|
+
export function joinMessageBridges(documents, project) {
|
|
7
|
+
if (documents.length === 0)
|
|
8
|
+
return { routes: [], unresolved: [], limitations: [] };
|
|
9
|
+
validateMessageDocuments(documents, project);
|
|
10
|
+
const groups = new Map();
|
|
11
|
+
const unresolved = new Map();
|
|
12
|
+
const limitations = [];
|
|
13
|
+
for (const document of documents) {
|
|
14
|
+
for (const message of document.limitations)
|
|
15
|
+
limitations.push({ platform: document.platform,
|
|
16
|
+
target: document.target, tool: document.tool.name, message });
|
|
17
|
+
for (const fact of document.facts) {
|
|
18
|
+
const endpoint = { platform: document.platform, location: fact.location,
|
|
19
|
+
...(fact.symbol === undefined ? {} : { symbol: fact.symbol }),
|
|
20
|
+
...(fact.sourceLanguage === undefined ? {} : { sourceLanguage: fact.sourceLanguage }),
|
|
21
|
+
...(fact.handlerScope === undefined ? {} : { handlerScope: fact.handlerScope, dependencies: fact.dependencies }),
|
|
22
|
+
...(fact.dynamic && fact.channel !== null ? { channelExpression: fact.channel } : {}) };
|
|
23
|
+
const identity = JSON.stringify([document.platform, fact.location.path, fact.location.line, fact.location.column,
|
|
24
|
+
fact.symbol?.usr ?? null, fact.symbol?.qualifiedName ?? null, fact.sourceLanguage ?? null,
|
|
25
|
+
fact.dynamic, fact.channel, fact.channelPrefix ?? null, fact.handlerScope ?? null, fact.dependencies ?? null]);
|
|
26
|
+
const channel = fact.dynamic ? fact.channelPrefix : fact.channel;
|
|
27
|
+
if (!channel) {
|
|
28
|
+
unresolved.set(identity, endpoint);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const matching = fact.dynamic ? 'prefix' : 'literal';
|
|
32
|
+
const key = JSON.stringify([matching, channel]);
|
|
33
|
+
let group = groups.get(key);
|
|
34
|
+
if (group === undefined) {
|
|
35
|
+
group = { channel, matching, senders: new Map(), handlers: new Map() };
|
|
36
|
+
groups.set(key, group);
|
|
37
|
+
}
|
|
38
|
+
(fact.kind === 'message-send' ? group.senders : group.handlers).set(identity, endpoint);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const endpoints = (values) => [...values.entries()]
|
|
42
|
+
.sort(([a], [b]) => compareStrings(a, b)).map(([, value]) => value);
|
|
43
|
+
// 전파로 추가한 literal을 더 좁은 prefix로 다시 복사하면 서로 다른 주소가 연결된다.
|
|
44
|
+
// 원래 prefix 사실만 스냅샷으로 두고, 교집합 범위에 한 번씩 투영한다.
|
|
45
|
+
const prefixes = [...groups.values()].filter(({ matching }) => matching === 'prefix').map((group) => ({
|
|
46
|
+
group, senders: new Map(group.senders), handlers: new Map(group.handlers),
|
|
47
|
+
}));
|
|
48
|
+
const prefixIndex = new MessageAddressIndex();
|
|
49
|
+
for (const prefix of prefixes)
|
|
50
|
+
prefixIndex.add(prefix.group.channel, 'prefix', prefix);
|
|
51
|
+
let projections = 0;
|
|
52
|
+
const merge = (target, source) => {
|
|
53
|
+
for (const [key, endpoint] of source)
|
|
54
|
+
if (!target.has(key)) {
|
|
55
|
+
if (++projections > 1_000_000)
|
|
56
|
+
throw new BridgeJoinValidationError('Message prefix projection budget exceeded.');
|
|
57
|
+
target.set(key, endpoint);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
for (const group of groups.values()) {
|
|
61
|
+
for (const prefix of prefixIndex.matching(group.channel)) {
|
|
62
|
+
if (prefix.group === group)
|
|
63
|
+
continue;
|
|
64
|
+
if (group.matching === 'literal') {
|
|
65
|
+
merge(prefix.group.senders, group.senders);
|
|
66
|
+
merge(prefix.group.handlers, group.handlers);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
merge(group.senders, prefix.senders);
|
|
70
|
+
merge(group.handlers, prefix.handlers);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
routes: [...groups.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, group]) => ({
|
|
76
|
+
channel: group.channel, matching: group.matching, senders: endpoints(group.senders), handlers: endpoints(group.handlers),
|
|
77
|
+
})), unresolved: endpoints(unresolved), limitations,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/join/messages.ts"],"names":[],"mappings":"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","sourcesContent":["import { compareStrings } from '../compare.ts';\nimport type { BridgeHandlerDependency, BridgeHandlerScope, BridgeMessageDocument } from '../exchange/messages.ts';\nimport { validateMessageDocuments } from '../exchange/messages.ts';\nimport type { BridgeEndpoint, JoinLimitation } from './join.ts';\nimport { BridgeJoinValidationError } from './join.ts';\nimport { MessageAddressIndex } from './message-address.ts';\n\n/** 동적 접두사의 원래 표현식도 근거에 보존한다. */\nexport interface MessageEndpoint extends BridgeEndpoint {\n readonly channelExpression?: string;\n readonly handlerScope?: BridgeHandlerScope;\n readonly dependencies?: readonly BridgeHandlerDependency[];\n}\n\n/** literal 주소와 prefix 후보를 다른 키로 연결한다. */\nexport interface MessageBridgeRoute {\n readonly channel: string;\n readonly matching: 'literal' | 'prefix';\n readonly senders: readonly MessageEndpoint[];\n readonly handlers: readonly MessageEndpoint[];\n}\n\n/** Basic 범위에서 관찰한 경계와 끝내 연결하지 못한 사실이다. */\nexport interface MessageBridgeJoin {\n readonly routes: readonly MessageBridgeRoute[];\n readonly unresolved: readonly MessageEndpoint[];\n readonly limitations: readonly JoinLimitation[];\n}\n\n/** 서로 같은 project의 양쪽 Basic 문서만 조인하며 MethodChannel 키 공간을 사용하지 않는다. */\nexport function joinMessageBridges(documents: readonly BridgeMessageDocument[], project: string): MessageBridgeJoin {\n if (documents.length === 0) return { routes: [], unresolved: [], limitations: [] };\n validateMessageDocuments(documents, project);\n const groups = new Map<string, { channel: string; matching: 'literal' | 'prefix';\n senders: Map<string, MessageEndpoint>; handlers: Map<string, MessageEndpoint> }>();\n const unresolved = new Map<string, MessageEndpoint>();\n const limitations: JoinLimitation[] = [];\n for (const document of documents) {\n for (const message of document.limitations) limitations.push({ platform: document.platform,\n target: document.target, tool: document.tool.name, message });\n for (const fact of document.facts) {\n const endpoint: MessageEndpoint = { platform: document.platform, location: fact.location,\n ...(fact.symbol === undefined ? {} : { symbol: fact.symbol }),\n ...(fact.sourceLanguage === undefined ? {} : { sourceLanguage: fact.sourceLanguage }),\n ...(fact.handlerScope === undefined ? {} : { handlerScope: fact.handlerScope, dependencies: fact.dependencies! }),\n ...(fact.dynamic && fact.channel !== null ? { channelExpression: fact.channel } : {}) };\n const identity = JSON.stringify([document.platform, fact.location.path, fact.location.line, fact.location.column,\n fact.symbol?.usr ?? null, fact.symbol?.qualifiedName ?? null, fact.sourceLanguage ?? null,\n fact.dynamic, fact.channel, fact.channelPrefix ?? null, fact.handlerScope ?? null, fact.dependencies ?? null]);\n const channel = fact.dynamic ? fact.channelPrefix : fact.channel;\n if (!channel) { unresolved.set(identity, endpoint); continue; }\n const matching = fact.dynamic ? 'prefix' : 'literal';\n const key = JSON.stringify([matching, channel]);\n let group = groups.get(key);\n if (group === undefined) {\n group = { channel, matching, senders: new Map(), handlers: new Map() };\n groups.set(key, group);\n }\n (fact.kind === 'message-send' ? group.senders : group.handlers).set(identity, endpoint);\n }\n }\n const endpoints = (values: Map<string, MessageEndpoint>) => [...values.entries()]\n .sort(([a], [b]) => compareStrings(a, b)).map(([, value]) => value);\n // 전파로 추가한 literal을 더 좁은 prefix로 다시 복사하면 서로 다른 주소가 연결된다.\n // 원래 prefix 사실만 스냅샷으로 두고, 교집합 범위에 한 번씩 투영한다.\n const prefixes = [...groups.values()].filter(({ matching }) => matching === 'prefix').map((group) => ({\n group, senders: new Map(group.senders), handlers: new Map(group.handlers),\n }));\n const prefixIndex = new MessageAddressIndex<(typeof prefixes)[number]>();\n for (const prefix of prefixes) prefixIndex.add(prefix.group.channel, 'prefix', prefix);\n let projections = 0;\n const merge = (target: Map<string, MessageEndpoint>, source: Map<string, MessageEndpoint>) => {\n for (const [key, endpoint] of source) if (!target.has(key)) {\n if (++projections > 1_000_000) throw new BridgeJoinValidationError('Message prefix projection budget exceeded.');\n target.set(key, endpoint);\n }\n };\n for (const group of groups.values()) {\n for (const prefix of prefixIndex.matching(group.channel)) {\n if (prefix.group === group) continue;\n if (group.matching === 'literal') {\n merge(prefix.group.senders, group.senders); merge(prefix.group.handlers, group.handlers);\n } else {\n merge(group.senders, prefix.senders); merge(group.handlers, prefix.handlers);\n }\n }\n }\n return {\n routes: [...groups.entries()].sort(([a], [b]) => compareStrings(a, b)).map(([, group]) => ({\n channel: group.channel, matching: group.matching, senders: endpoints(group.senders), handlers: endpoints(group.handlers),\n })), unresolved: endpoints(unresolved), limitations,\n };\n}\n"]}
|
package/dist/report/diff.js
CHANGED
|
@@ -46,12 +46,13 @@ export function createBridgeDiff(before, after) {
|
|
|
46
46
|
function validateSnapshots(before, after) {
|
|
47
47
|
const all = [...before, ...after];
|
|
48
48
|
if (new Set(all.map((doc) => doc.project)).size !== 1 ||
|
|
49
|
+
new Set(all.filter((doc) => doc.platform === 'swift' || doc.platform === 'kotlin').map((doc) => doc.platform)).size !== 1 ||
|
|
49
50
|
![before, after].every((docs) => docs.some((doc) => doc.platform === 'dart') &&
|
|
50
|
-
docs.some((doc) => doc.platform === 'swift')) ||
|
|
51
|
+
docs.some((doc) => doc.platform === 'swift' || doc.platform === 'kotlin')) ||
|
|
51
52
|
JSON.stringify(producerInventory(before)) !== JSON.stringify(producerInventory(after)) ||
|
|
52
|
-
all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift') ||
|
|
53
|
+
all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift' && doc.platform !== 'kotlin') ||
|
|
53
54
|
(doc.target !== null && doc.target !== 'flutter'))) {
|
|
54
|
-
throw new BridgeJoinValidationError('Diff requires the same project and matching Flutter
|
|
55
|
+
throw new BridgeJoinValidationError('Diff requires the same project and matching Flutter Dart/native producer '
|
|
55
56
|
+ 'inventories in both snapshots; rebuild both snapshots from one checkout.');
|
|
56
57
|
}
|
|
57
58
|
}
|
package/dist/report/diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/report/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport type {\n BridgeJoinResult,\n JoinLimitation,\n MatchedMethod,\n} from '../join/join.ts';\nimport { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments } from '../join/join.ts';\nimport type { CheckIssue } from './check-report.ts';\nimport { baselineEntryKey } from './baseline.ts';\nimport { createCheckReport } from './check-report.ts';\n\n/** 두 스냅샷의 생산 도구 버전 근거다. */\nexport interface DiffProducerVersion {\n readonly platform: string;\n readonly name: string;\n readonly version: string;\n readonly generatedAt: string;\n}\n\n/**\n * 동일 프로젝트의 전후 관찰을 비교한 isthmus-diff 문서다.\n *\n * added/removed는 키 집합 차이라 소스 줄 이동은 차이가 아니다. introduced와\n * resolved는 논리 이슈 키 기준이라 코드가 바뀌면(-unverified → 본 판정)\n * 해결·추가 한 쌍으로 보인다 — 심각도 상승은 새 관찰이기 때문이다.\n */\nexport interface BridgeDiffDocument {\n readonly format: 'isthmus-diff';\n readonly version: 1;\n readonly summary: {\n readonly addedMethods: number;\n readonly removedMethods: number;\n readonly introducedErrors: number;\n readonly introducedWarnings: number;\n readonly resolvedIssues: number;\n };\n readonly addedMethods: readonly MatchedMethod[];\n readonly removedMethods: readonly MatchedMethod[];\n readonly introducedIssues: readonly CheckIssue[];\n readonly resolvedIssues: readonly CheckIssue[];\n readonly limitations: {\n readonly before: readonly JoinLimitation[];\n readonly after: readonly JoinLimitation[];\n readonly added: readonly JoinLimitation[];\n readonly removed: readonly JoinLimitation[];\n };\n readonly producers: {\n readonly before: readonly DiffProducerVersion[];\n readonly after: readonly DiffProducerVersion[];\n };\n}\n\n/** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */\nexport function createBridgeDiff(\n before: readonly BridgeFactsDocument[],\n after: readonly BridgeFactsDocument[],\n): BridgeDiffDocument {\n validateSnapshots(before, after);\n const oldJoin = joinBridgeDocuments(before);\n const newJoin = joinBridgeDocuments(after);\n if (isBridgeJoinDeferred(oldJoin) || isBridgeJoinDeferred(newJoin)) {\n throw new BridgeJoinValidationError(\n 'Cannot compare deferred bridge joins; split mixed bridge targets and '\n + `retry. The before inputs observed ${oldJoin.observedFacts} facts across `\n + `${before.length} documents, and the after inputs observed `\n + `${newJoin.observedFacts} facts across ${after.length} documents.`,\n );\n }\n const oldReport = createCheckReport(oldJoin);\n const newReport = createCheckReport(newJoin);\n const addedMethods = difference(newJoin.matchedMethods, oldJoin.matchedMethods, logicalKey);\n const removedMethods = difference(oldJoin.matchedMethods, newJoin.matchedMethods, logicalKey);\n const introducedIssues = difference(newReport.issues, oldReport.issues, baselineEntryKey);\n const resolvedIssues = difference(oldReport.issues, newReport.issues, baselineEntryKey);\n return {\n format: 'isthmus-diff' as const,\n version: 1 as const,\n summary: {\n addedMethods: addedMethods.length,\n removedMethods: removedMethods.length,\n introducedErrors: introducedIssues.filter((issue) => issue.severity === 'error').length,\n introducedWarnings: introducedIssues.filter((issue) => issue.severity === 'warning').length,\n resolvedIssues: resolvedIssues.length,\n },\n addedMethods,\n removedMethods,\n introducedIssues,\n resolvedIssues,\n limitations: {\n before: oldReport.limitations,\n after: newReport.limitations,\n added: difference(newReport.limitations, oldReport.limitations, limitationKey),\n removed: difference(oldReport.limitations, newReport.limitations, limitationKey),\n },\n producers: { before: producerVersions(before), after: producerVersions(after) },\n };\n}\n\n/** 플랫폼 누락이나 다른 프로젝트를 코드 삭제로 오해하지 않도록 입력 구성을 고정한다. */\nfunction validateSnapshots(before: readonly BridgeFactsDocument[], after: readonly BridgeFactsDocument[]): void {\n const all = [...before, ...after];\n if (new Set(all.map((doc) => doc.project)).size !== 1 ||\n ![before, after].every((docs) => docs.some((doc) => doc.platform === 'dart') &&\n docs.some((doc) => doc.platform === 'swift')) ||\n JSON.stringify(producerInventory(before)) !== JSON.stringify(producerInventory(after)) ||\n all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift') ||\n (doc.target !== null && doc.target !== 'flutter'))) {\n throw new BridgeJoinValidationError(\n 'Diff requires the same project and matching Flutter dart/swift producer '\n + 'inventories in both snapshots; rebuild both snapshots from one checkout.',\n );\n }\n}\n\n/** 버전 변화는 출력하되 플랫폼·도구별 문서 개수 변화는 허용하지 않는다. */\nfunction producerInventory(docs: readonly BridgeFactsDocument[]): string[] {\n return docs.map((doc) => JSON.stringify([doc.platform, doc.tool.name])).sort(compareStrings);\n}\n\n/** 추출기 업그레이드가 관찰 차이의 원인인지 검토할 버전 근거다. */\nfunction producerVersions(docs: readonly BridgeFactsDocument[]) {\n return docs.map((doc) => ({ platform: doc.platform, ...doc.tool, generatedAt: doc.generatedAt }))\n .sort((a, b) => compareStrings(JSON.stringify(a), JSON.stringify(b)));\n}\n\n/** 충돌 없는 논리 키로 비교해 소스 줄 이동을 추가·삭제로 보고하지 않는다. */\nfunction logicalKey(item: { readonly target: BridgeTarget; readonly channel: string; readonly method?: string }): string {\n return JSON.stringify([item.target, item.channel, item.method ?? null]);\n}\n\nfunction limitationKey(item: { readonly platform: string; readonly target: string | null; readonly tool: string; readonly message: string; readonly channels?: readonly string[]; readonly origin?: 'consumer' }): string {\n return JSON.stringify([item.platform, item.target, item.tool, item.message, item.channels ?? null, item.origin ?? null]);\n}\n\n/** 키 집합 차이를 안정적으로 정렬하고 원래 증거를 보존한다. */\nfunction difference<T>(left: readonly T[], right: readonly T[], key: (item: T) => string): T[] {\n const existing = new Set(right.map(key));\n return left.filter((item) => !existing.has(key(item)))\n .sort((a, b) => compareStrings(key(a), key(b)));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/report/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BridgeFactsDocument, BridgeTarget } from '../exchange/parse.ts';\nimport { compareStrings } from '../compare.ts';\nimport type {\n BridgeJoinResult,\n JoinLimitation,\n MatchedMethod,\n} from '../join/join.ts';\nimport { BridgeJoinValidationError, isBridgeJoinDeferred, joinBridgeDocuments } from '../join/join.ts';\nimport type { CheckIssue } from './check-report.ts';\nimport { baselineEntryKey } from './baseline.ts';\nimport { createCheckReport } from './check-report.ts';\n\n/** 두 스냅샷의 생산 도구 버전 근거다. */\nexport interface DiffProducerVersion {\n readonly platform: string;\n readonly name: string;\n readonly version: string;\n readonly generatedAt: string;\n}\n\n/**\n * 동일 프로젝트의 전후 관찰을 비교한 isthmus-diff 문서다.\n *\n * added/removed는 키 집합 차이라 소스 줄 이동은 차이가 아니다. introduced와\n * resolved는 논리 이슈 키 기준이라 코드가 바뀌면(-unverified → 본 판정)\n * 해결·추가 한 쌍으로 보인다 — 심각도 상승은 새 관찰이기 때문이다.\n */\nexport interface BridgeDiffDocument {\n readonly format: 'isthmus-diff';\n readonly version: 1;\n readonly summary: {\n readonly addedMethods: number;\n readonly removedMethods: number;\n readonly introducedErrors: number;\n readonly introducedWarnings: number;\n readonly resolvedIssues: number;\n };\n readonly addedMethods: readonly MatchedMethod[];\n readonly removedMethods: readonly MatchedMethod[];\n readonly introducedIssues: readonly CheckIssue[];\n readonly resolvedIssues: readonly CheckIssue[];\n readonly limitations: {\n readonly before: readonly JoinLimitation[];\n readonly after: readonly JoinLimitation[];\n readonly added: readonly JoinLimitation[];\n readonly removed: readonly JoinLimitation[];\n };\n readonly producers: {\n readonly before: readonly DiffProducerVersion[];\n readonly after: readonly DiffProducerVersion[];\n };\n}\n\n/** 동일 프로젝트의 관찰 결과를 비교하며 삭제 안전성이나 rename을 추측하지 않는다. */\nexport function createBridgeDiff(\n before: readonly BridgeFactsDocument[],\n after: readonly BridgeFactsDocument[],\n): BridgeDiffDocument {\n validateSnapshots(before, after);\n const oldJoin = joinBridgeDocuments(before);\n const newJoin = joinBridgeDocuments(after);\n if (isBridgeJoinDeferred(oldJoin) || isBridgeJoinDeferred(newJoin)) {\n throw new BridgeJoinValidationError(\n 'Cannot compare deferred bridge joins; split mixed bridge targets and '\n + `retry. The before inputs observed ${oldJoin.observedFacts} facts across `\n + `${before.length} documents, and the after inputs observed `\n + `${newJoin.observedFacts} facts across ${after.length} documents.`,\n );\n }\n const oldReport = createCheckReport(oldJoin);\n const newReport = createCheckReport(newJoin);\n const addedMethods = difference(newJoin.matchedMethods, oldJoin.matchedMethods, logicalKey);\n const removedMethods = difference(oldJoin.matchedMethods, newJoin.matchedMethods, logicalKey);\n const introducedIssues = difference(newReport.issues, oldReport.issues, baselineEntryKey);\n const resolvedIssues = difference(oldReport.issues, newReport.issues, baselineEntryKey);\n return {\n format: 'isthmus-diff' as const,\n version: 1 as const,\n summary: {\n addedMethods: addedMethods.length,\n removedMethods: removedMethods.length,\n introducedErrors: introducedIssues.filter((issue) => issue.severity === 'error').length,\n introducedWarnings: introducedIssues.filter((issue) => issue.severity === 'warning').length,\n resolvedIssues: resolvedIssues.length,\n },\n addedMethods,\n removedMethods,\n introducedIssues,\n resolvedIssues,\n limitations: {\n before: oldReport.limitations,\n after: newReport.limitations,\n added: difference(newReport.limitations, oldReport.limitations, limitationKey),\n removed: difference(oldReport.limitations, newReport.limitations, limitationKey),\n },\n producers: { before: producerVersions(before), after: producerVersions(after) },\n };\n}\n\n/** 플랫폼 누락이나 다른 프로젝트를 코드 삭제로 오해하지 않도록 입력 구성을 고정한다. */\nfunction validateSnapshots(before: readonly BridgeFactsDocument[], after: readonly BridgeFactsDocument[]): void {\n const all = [...before, ...after];\n if (new Set(all.map((doc) => doc.project)).size !== 1 ||\n new Set(all.filter((doc) => doc.platform === 'swift' || doc.platform === 'kotlin').map((doc) => doc.platform)).size !== 1 ||\n ![before, after].every((docs) => docs.some((doc) => doc.platform === 'dart') &&\n docs.some((doc) => doc.platform === 'swift' || doc.platform === 'kotlin')) ||\n JSON.stringify(producerInventory(before)) !== JSON.stringify(producerInventory(after)) ||\n all.some((doc) => (doc.platform !== 'dart' && doc.platform !== 'swift' && doc.platform !== 'kotlin') ||\n (doc.target !== null && doc.target !== 'flutter'))) {\n throw new BridgeJoinValidationError(\n 'Diff requires the same project and matching Flutter Dart/native producer '\n + 'inventories in both snapshots; rebuild both snapshots from one checkout.',\n );\n }\n}\n\n/** 버전 변화는 출력하되 플랫폼·도구별 문서 개수 변화는 허용하지 않는다. */\nfunction producerInventory(docs: readonly BridgeFactsDocument[]): string[] {\n return docs.map((doc) => JSON.stringify([doc.platform, doc.tool.name])).sort(compareStrings);\n}\n\n/** 추출기 업그레이드가 관찰 차이의 원인인지 검토할 버전 근거다. */\nfunction producerVersions(docs: readonly BridgeFactsDocument[]) {\n return docs.map((doc) => ({ platform: doc.platform, ...doc.tool, generatedAt: doc.generatedAt }))\n .sort((a, b) => compareStrings(JSON.stringify(a), JSON.stringify(b)));\n}\n\n/** 충돌 없는 논리 키로 비교해 소스 줄 이동을 추가·삭제로 보고하지 않는다. */\nfunction logicalKey(item: { readonly target: BridgeTarget; readonly channel: string; readonly method?: string }): string {\n return JSON.stringify([item.target, item.channel, item.method ?? null]);\n}\n\nfunction limitationKey(item: { readonly platform: string; readonly target: string | null; readonly tool: string; readonly message: string; readonly channels?: readonly string[]; readonly origin?: 'consumer' }): string {\n return JSON.stringify([item.platform, item.target, item.tool, item.message, item.channels ?? null, item.origin ?? null]);\n}\n\n/** 키 집합 차이를 안정적으로 정렬하고 원래 증거를 보존한다. */\nfunction difference<T>(left: readonly T[], right: readonly T[], key: (item: T) => string): T[] {\n const existing = new Set(right.map(key));\n return left.filter((item) => !existing.has(key(item)))\n .sort((a, b) => compareStrings(key(a), key(b)));\n}\n"]}
|