@fuaran-ui/ops 0.21.0 → 0.22.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/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
- import { Result, NodeId, NodeKind, JsonValue, Binding, SemanticStyle, StateBehaviour, Node, Cell, ColExpr, DataSource, Transform, EvalError, Table, TextSource, CellFormat, ColumnWidth, Orientation, ToneVariant, StyleWeight, Emphasis, TrendPolarity, HeadingVariant, BadgeVariant, IconSource, IconSize, DecodePolicy } from '@fuaran-ui/schema';
1
+ import { Result, NodeId, NodeKind, JsonValue, Binding, SemanticStyle, StateBehaviour, Node, Cell, ColExpr, DataSource, Transform, EvalError, Table, TextSource, CellFormat, ColumnWidth, Orientation, ToneVariant, StyleWeight, Emphasis, TrendPolarity, HeadingVariant, BadgeVariant, IconSource, IconSize, DecodePolicy, Capability } from '@fuaran-ui/schema';
2
+ export { Capability, Placement as CapabilityPlacement, CapabilitySigEntry, CapabilitySignature, IslandKind } from '@fuaran-ui/schema';
2
3
 
3
4
  /** Local JSON AST. Shape-for-shape port of the F# decoder's private `Json` DU. */
4
5
  type JsonAst = {
@@ -35,9 +36,11 @@ interface ParseError {
35
36
  }
36
37
  /**
37
38
  * Parse a JSON document into the local AST. Mirrors the F# `tryParse`: empty /
38
- * whitespace-only input is a structural error; otherwise a single top-level
39
- * value is parsed (trailing content after the first value is not inspected,
40
- * matching the F# parser).
39
+ * whitespace-only input is a structural error; a document past the §21.7
40
+ * ceiling is refused BEFORE the parse; and per §20.2 row 2 the root value must
41
+ * be followed by nothing but whitespace — §1 makes a wire artefact a single
42
+ * JSON document, and this parser used to stop at the first value and ignore the
43
+ * remainder, which is a framing ambiguity rather than a tolerance.
41
44
  */
42
45
  declare const parse: (input: string) => Result<JsonAst, ParseError>;
43
46
  /** Look up an object field by key (key-order tolerant per WIRE_FORMAT.md §2). */
@@ -179,6 +182,17 @@ interface DecodeError {
179
182
  readonly expectedShape?: string;
180
183
  }
181
184
  type R<T> = Result<T, DecodeError>;
185
+ type CR<T> = {
186
+ readonly ok: true;
187
+ readonly value: T;
188
+ } | {
189
+ readonly ok: false;
190
+ readonly error: string;
191
+ };
192
+ /** A `DataSource` — port of F# `ColumnCodec.decodeJson` (Phase 88: `schema`
193
+ * may be omitted on an embedded source; inferred in Ordinal column order). */
194
+ declare const decodeDataSource: (j: JsonAst) => CR<DataSource>;
195
+ declare const decodePipelineCore: (j: JsonAst) => CR<Transform[]>;
182
196
  /**
183
197
  * Phase 818 — materialise a LIVE Transform source's resolved store value as the
184
198
  * evaluation input table: row-major rows transpose through the same 815
@@ -256,6 +270,54 @@ declare const decodeNode: (json: string, policy?: DecodePolicy) => R<Node<unknow
256
270
  * only rather than a closure.
257
271
  */
258
272
  declare const decodeOp: (json: string, policy?: DecodePolicy) => R<TreeOp<unknown>>;
273
+ /**
274
+ * Decode a canonical-JSON payload holding EITHER one `TreeOp` or an array of
275
+ * them — the shape a host sends down an op channel.
276
+ *
277
+ * It exists because the obvious composition is unsound. A consumer holding
278
+ * "one op or an array" reaches for `JSON.parse`, walks the array, and calls
279
+ * `decodeOp(JSON.stringify(entry))` per element; the embedded standalone bundle
280
+ * did exactly that. Three things go wrong, and none of them is visible from the
281
+ * call site:
282
+ *
283
+ * - The whole payload passes through `JSON.parse` BEFORE any bound applies, so
284
+ * the §21 depth gate never sees it. `JSON.parse` is native and will not
285
+ * overflow, but the document it hands back is unbounded, and the per-op
286
+ * decode that follows measures each op against a limit the batch already
287
+ * escaped.
288
+ * - `JSON.parse` is a different parser with different §20 answers: it keeps the
289
+ * LAST duplicate member, reads `1e999` as `Infinity`, and accepts nothing
290
+ * this parser refuses. Re-stringifying its output launders every one of those
291
+ * differences into bytes this decoder then accepts without complaint — the
292
+ * §20.1 undeclared-entry-point defect, arrived at by composition.
293
+ * - `MAX_NODES` is reset per `decodeOp` call, so it bounded ONE op rather than
294
+ * the batch: 1 000 ops of 100 000 nodes each passed every check.
295
+ *
296
+ * So the batch is parsed ONCE through this package's own parser, the array is
297
+ * split at the AST, and the node budget is carried ACROSS the ops rather than
298
+ * reset per op.
299
+ */
300
+ declare const decodeOps: (json: string, policy?: DecodePolicy) => R<readonly TreeOp<unknown>[]>;
301
+
302
+ /** A total codec result — a value, or a named refusal. Never a throw. */
303
+ type CapabilityDeclResult<T> = {
304
+ readonly ok: true;
305
+ readonly value: T;
306
+ } | {
307
+ readonly ok: false;
308
+ readonly error: string;
309
+ };
310
+ /**
311
+ * Encode a capability declaration to its canonical-JSON string. Total: every
312
+ * field of every case is representable, so there is nothing to refuse.
313
+ */
314
+ declare const encodeCapabilityDeclaration: (cap: Capability) => string;
315
+ /**
316
+ * Decode a canonical capability-declaration string. Total — a malformed or
317
+ * un-carryable declaration yields `{ ok: false, error }` naming what was wrong,
318
+ * never an exception and never a partially-built capability.
319
+ */
320
+ declare const decodeCapabilityDeclaration: (json: string) => CapabilityDeclResult<Capability>;
259
321
 
260
322
  type ApplyErrorCode = 'NodeNotFound' | 'ParentNotFound' | 'ChildlessKind' | 'PositionOutOfRange' | 'DuplicateNodeId' | 'FieldNotFound' | 'SlotNotFound' | 'KindMismatch' | 'PathInvalid' | 'PathNotSupportedYet' | 'OrderingMismatch' | 'BatchAborted';
261
323
  interface ApplyError {
@@ -509,10 +571,62 @@ type DecodeResult<T> = {
509
571
  declare const decodeDagRecord: (json: string) => DecodeResult<DagOpRecord<unknown>>;
510
572
 
511
573
  type N = Node<unknown>;
512
- /** A `(nodeId, facet)` cell that could not be auto-merged. */
574
+ /**
575
+ * The class of a merge refusal. Mirrors the reference host's
576
+ * `MergeConflictClass` spelling-for-spelling — these strings are the `class`
577
+ * member of the committed refusal envelope, so they are wire, not prose.
578
+ */
579
+ type MergeConflictClass = 'ConcurrentEdit' | 'ConcurrentMove' | 'DeleteModify' | 'KindSwapOrphansPin' | 'ReorderVsStructural' | 'CombinedCycle';
580
+ /**
581
+ * One SIDE of a two-sided refusal: that branch's value for the contended cell,
582
+ * plus the branch's own opaque provenance tag.
583
+ *
584
+ * The `value` is the contended cell's canonical encoding, EXCEPT for the
585
+ * `style.*` sub-facets, whose value is the sub-field's case name — which
586
+ * coincides with its wire spelling only because every style sub-field is
587
+ * enum-shaped. Do not generalise it to a compound cell.
588
+ */
589
+ interface MergeSide {
590
+ readonly value: string;
591
+ readonly tag?: string;
592
+ }
593
+ /**
594
+ * A `(nodeId, facet)` cell that could not be auto-merged, as a two-sided
595
+ * recovery envelope.
596
+ *
597
+ * `a` and `b` are the SIDES view: the first- and second-argument branches'
598
+ * values for the contended cell, populated on EVERY refusal. Swapping the
599
+ * branches TRANSPOSES them and changes nothing else — which is what lets two
600
+ * replicas that merged the same pair in opposite orders agree about what the
601
+ * other side wanted. `base` is the LCA value (the empty string for a cell that
602
+ * exists on neither side of the LCA, such as an `insert`).
603
+ *
604
+ * `primacyHeld` is the precedence view, and on this tier it is always `false`:
605
+ * `merge3Way` is the author-agnostic entry point, so neither side is pinned.
606
+ * The reference host's precedence slots (`primary` / `secondary` /
607
+ * `secondaryTag`) are deliberately NOT mirrored here — they are populated
608
+ * exactly when a pin is held, and this tier has no author classifier to hold one
609
+ * with, so they could only ever be absent. They arrive with the entry point that
610
+ * supplies an author, not before.
611
+ *
612
+ * The reference host's `choices` menu is not mirrored either, but for a
613
+ * different reason, and the difference matters to whoever ports it. It is NOT
614
+ * empty on this tier: every choice the reference offers names a slot the
615
+ * envelope populated, so an unpinned refusal offers `KeepBase` / `KeepA` /
616
+ * `KeepB` — all three well-defined here — rather than the `KeepSecondary` that
617
+ * would name the empty precedence slot. The menu is left out because it is
618
+ * derivable from the sides plus the pin, which is also why the shared refusal
619
+ * corpus does not encode it; a port that wants it computes it, and must not
620
+ * infer from the absence that this tier has no menu to offer.
621
+ */
513
622
  interface MergeConflict {
514
623
  readonly nodeId: string;
515
624
  readonly facet: string;
625
+ readonly class: MergeConflictClass;
626
+ readonly base: string;
627
+ readonly a: MergeSide;
628
+ readonly b: MergeSide;
629
+ readonly primacyHeld: boolean;
516
630
  }
517
631
  /** Outcome of a 3-way merge: the merged tree, or the conflicting cells. */
518
632
  type MergeResult = {
@@ -529,6 +643,25 @@ type MergeResult = {
529
643
  * tie-break, no wall-clock) — byte-identical to the F# `TreeMerge.merge3Way`.
530
644
  */
531
645
  declare const merge3Way: (base: N, a: N, b: N) => MergeResult;
646
+ /**
647
+ * Order a refusal set deterministically. `(nodeId, facet)` is unique within one
648
+ * merge — a facet of a node is merged once — so this totally orders an envelope
649
+ * regardless of the fold's internal emission order.
650
+ */
651
+ declare const sortConflictsCanonical: (conflicts: readonly MergeConflict[]) => readonly MergeConflict[];
652
+ /**
653
+ * Canonical JSON of a REFUSAL envelope: the conflict set as a sorted array of
654
+ * `{a,b,base,class,facet,nodeId,primacyHeld}` objects (object keys alphabetical,
655
+ * array entries in `(nodeId, facet)` order). Byte-stable across hosts, so a
656
+ * sha256 over it is the cross-host refusal hash — the determinism artefact for a
657
+ * REFUSED structural merge, the analogue of the outcome hash for an auto-merge.
658
+ *
659
+ * The precedence view is deliberately projected as `primacyHeld` alone: the
660
+ * pinned winner and loser are derivable from the sides plus the pin, and a
661
+ * corpus that committed both would pin the same value twice and go red on a host
662
+ * that agreed about the merge.
663
+ */
664
+ declare const encodeMergeEnvelope: (conflicts: readonly MergeConflict[]) => string;
532
665
 
533
666
  /**
534
667
  * A wire profile id — `<name>@<major>.<minor>` (e.g. `core@1.0`). `name` is the
@@ -840,4 +973,74 @@ declare const withStateSeeds: <TMsg, S extends {
840
973
  readonly state?: Readonly<Record<string, unknown>>;
841
974
  }>(tree: Node<TMsg>, sources: S) => S;
842
975
 
843
- export { type Answer, type AnswerContract, type AnswerField, type AnswerSpace, type AnswerValue, type ApplyError, type ApplyErrorCode, type ApplyResult, type Compatibility, type DagActor, type DagOpRecord, type DagResultEnvelope, type DecodeError, type DecodeErrorCode, type Decoded, ELICITATION_KEY, ELICITATION_VERSION, type ElicitationEnvelope, type ElicitationError, type ElicitationErrorCode, type ElicitationOutcome, type ElicitationOutcomeEnvelope, type Envelope, type EnvelopeError, type EnvelopeErrorCode, type EvalEnv, type FreshIds, HOST_RESERVED_PREFIX, type JsonAst, type MergeConflict, type MergeResult, type OpApplyTelemetryRecord, PAYLOAD_KEY, PROFILE_KEY, type ParseError, type PlaceError, type PlaceTarget, type Placement, type Profile, REQUIRED_PROFILE_KEY, type SourceResolver, type TreeOp, type UnknownKind, apply, canPlace, cellString, coerce, collectStateSeeds, coreV1, decodeDagRecord, decodeElicitation, decodeElicitationOutcome, decodeEnvelope, decodeEnvelopeAst, decodeNode, decodeNodeTolerant, decodeOp, decodeTolerant, derivedFreshIds, duplicateOp, duplicateOpWith, encodeCell, encodeColExpr, encodeDagRecord, encodeDataSource, encodeElicitation, encodeElicitationOutcome, encodeEnvelope, encodeNode, encodeOp, encodePipeline, evalErrorString, evalPipeline, evalPipelineInEnv, evalPipelineWith, evalPipelineWithInEnv, evalSource, field as jsonField, liveValueToTable, merge3Way, moveOp, negotiate, negotiateEnvelope, noResolve, nudgeOp, parse, pasteOp, pasteOpWith, pipelineParams, placeOp, reencodeNode, renderAstCanonical, renderProfile, sequentialFreshIds, stepParams, substituteListParams, tryParseProfile, validateAnswer, validateAnswerAt, validateAnswerDocument, withStateSeeds };
976
+ /**
977
+ * The identity text rendition of a buffered value.
978
+ *
979
+ * `String(v)` is deliberately not used for numbers: it spells `1e21` as `1e+21`
980
+ * here and `1E+21` on .NET, and the hosts have to agree. `formatFiniteDouble` is
981
+ * the layout the corpus bytes already go through, so a number reads here exactly
982
+ * as it reads on the wire.
983
+ */
984
+ declare const identityFormat: (v: unknown) => string;
985
+ /**
986
+ * The JSON number grammar, and nothing wider. Surrounding ASCII whitespace is
987
+ * trimmed first — a reader's trailing space is not a type error — but a leading
988
+ * `+`, a bare `.5`, a hex literal and a thousands separator are all refused,
989
+ * because a grammar each host guesses at is a grammar each host guesses at
990
+ * differently.
991
+ *
992
+ * `Number(text)` is emphatically NOT the implementation: it accepts `0x10`,
993
+ * `Infinity`, `1_0` and the empty string, all of which this must refuse.
994
+ */
995
+ declare const tryNumberText: (text: string) => number | undefined;
996
+ /**
997
+ * The scalar a piece of buffer text denotes, when it denotes one. `true` /
998
+ * `false` and the JSON number grammar only — an empty string is NOT null here,
999
+ * because a cleared text field is an empty string and reading it as "no value"
1000
+ * would make the buffer lie about what the reader did.
1001
+ */
1002
+ declare const scalarOfText: (text: string) => string | number | boolean | undefined;
1003
+ /**
1004
+ * Fixed-point text: sign, integer part, and EXACTLY `decimals` fraction digits,
1005
+ * `.` as the point, no grouping. Rounding is half-away-from-zero, spelled as
1006
+ * `floor(x + 0.5)` on the absolute value rather than as `toFixed` — `toFixed`
1007
+ * rounds half-to-even on some values through the double's binary expansion, and
1008
+ * a rounding mode each host picks a default for is a divergence waiting to be
1009
+ * found by a fixture.
1010
+ */
1011
+ declare const fixedText: (decimals: number, v: number) => string;
1012
+ /**
1013
+ * The `Format.Number` codec's rendition: fixed-point at the declared decimals,
1014
+ * or the identity when the codec declares none.
1015
+ *
1016
+ * A NON-numeric buffered value falls back to the identity text rather than
1017
+ * failing. The codec is a declaration about presentation, and a value of the
1018
+ * wrong shape underneath it is the slot's problem, reported by the slot — a
1019
+ * format function that threw here would take out the render of a tree whose only
1020
+ * defect is a mistyped binding.
1021
+ */
1022
+ declare const numberText: (decimals: number | undefined, v: unknown) => string;
1023
+ /**
1024
+ * The wire-survivability failure a DECODED host-only projection throws when a
1025
+ * reader tries to run it.
1026
+ *
1027
+ * It exists because the alternative — returning a default — is
1028
+ * indistinguishable from a real answer at the slot: a decoded `Binding.Computed`
1029
+ * used to hand back `undefined` and render it as though the computation had run.
1030
+ *
1031
+ * The `name` is set explicitly and consumers match on IT rather than on
1032
+ * `instanceof`: the renderer lives in another package with its own bundle, so an
1033
+ * identity check across the boundary is a check on which copy of the class was
1034
+ * loaded, which is not the question being asked.
1035
+ */
1036
+ declare class WireSurvivabilityError extends Error {
1037
+ constructor(message: string);
1038
+ }
1039
+ /**
1040
+ * The one message a decoded `Binding.Computed` carries. A constant rather than a
1041
+ * template at each site so the resolver can recognise it, the corpus can pin it,
1042
+ * and every host can render the same sentence.
1043
+ */
1044
+ declare const DECODED_COMPUTED_MESSAGE = "Binding.Computed has no wire projection (decoded from a '<closure>' sentinel) \u2014 use Binding.Expr / Transform / State";
1045
+
1046
+ export { type Answer, type AnswerContract, type AnswerField, type AnswerSpace, type AnswerValue, type ApplyError, type ApplyErrorCode, type ApplyResult, type CapabilityDeclResult, type Compatibility, DECODED_COMPUTED_MESSAGE, type DagActor, type DagOpRecord, type DagResultEnvelope, type DecodeError, type DecodeErrorCode, type Decoded, ELICITATION_KEY, ELICITATION_VERSION, type ElicitationEnvelope, type ElicitationError, type ElicitationErrorCode, type ElicitationOutcome, type ElicitationOutcomeEnvelope, type Envelope, type EnvelopeError, type EnvelopeErrorCode, type EvalEnv, type FreshIds, HOST_RESERVED_PREFIX, type JsonAst, type MergeConflict, type MergeConflictClass, type MergeResult, type MergeSide, type OpApplyTelemetryRecord, PAYLOAD_KEY, PROFILE_KEY, type ParseError, type PlaceError, type PlaceTarget, type Placement, type Profile, REQUIRED_PROFILE_KEY, type SourceResolver, type TreeOp, type UnknownKind, WireSurvivabilityError, apply, canPlace, cellString, coerce, collectStateSeeds, coreV1, decodeCapabilityDeclaration, decodeDagRecord, decodeDataSource, decodeElicitation, decodeElicitationOutcome, decodeEnvelope, decodeEnvelopeAst, decodeNode, decodeNodeTolerant, decodeOp, decodeOps, decodePipelineCore as decodePipeline, decodeTolerant, derivedFreshIds, duplicateOp, duplicateOpWith, encodeCapabilityDeclaration, encodeCell, encodeColExpr, encodeDagRecord, encodeDataSource, encodeElicitation, encodeElicitationOutcome, encodeEnvelope, encodeMergeEnvelope, encodeNode, encodeOp, encodePipeline, evalErrorString, evalPipeline, evalPipelineInEnv, evalPipelineWith, evalPipelineWithInEnv, evalSource, fixedText, identityFormat, field as jsonField, liveValueToTable, merge3Way, moveOp, negotiate, negotiateEnvelope, noResolve, nudgeOp, numberText, parse, pasteOp, pasteOpWith, pipelineParams, placeOp, reencodeNode, renderAstCanonical, renderProfile, scalarOfText, sequentialFreshIds, sortConflictsCanonical, stepParams, substituteListParams, tryNumberText, tryParseProfile, validateAnswer, validateAnswerAt, validateAnswerDocument, withStateSeeds };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Result, NodeId, NodeKind, JsonValue, Binding, SemanticStyle, StateBehaviour, Node, Cell, ColExpr, DataSource, Transform, EvalError, Table, TextSource, CellFormat, ColumnWidth, Orientation, ToneVariant, StyleWeight, Emphasis, TrendPolarity, HeadingVariant, BadgeVariant, IconSource, IconSize, DecodePolicy } from '@fuaran-ui/schema';
1
+ import { Result, NodeId, NodeKind, JsonValue, Binding, SemanticStyle, StateBehaviour, Node, Cell, ColExpr, DataSource, Transform, EvalError, Table, TextSource, CellFormat, ColumnWidth, Orientation, ToneVariant, StyleWeight, Emphasis, TrendPolarity, HeadingVariant, BadgeVariant, IconSource, IconSize, DecodePolicy, Capability } from '@fuaran-ui/schema';
2
+ export { Capability, Placement as CapabilityPlacement, CapabilitySigEntry, CapabilitySignature, IslandKind } from '@fuaran-ui/schema';
2
3
 
3
4
  /** Local JSON AST. Shape-for-shape port of the F# decoder's private `Json` DU. */
4
5
  type JsonAst = {
@@ -35,9 +36,11 @@ interface ParseError {
35
36
  }
36
37
  /**
37
38
  * Parse a JSON document into the local AST. Mirrors the F# `tryParse`: empty /
38
- * whitespace-only input is a structural error; otherwise a single top-level
39
- * value is parsed (trailing content after the first value is not inspected,
40
- * matching the F# parser).
39
+ * whitespace-only input is a structural error; a document past the §21.7
40
+ * ceiling is refused BEFORE the parse; and per §20.2 row 2 the root value must
41
+ * be followed by nothing but whitespace — §1 makes a wire artefact a single
42
+ * JSON document, and this parser used to stop at the first value and ignore the
43
+ * remainder, which is a framing ambiguity rather than a tolerance.
41
44
  */
42
45
  declare const parse: (input: string) => Result<JsonAst, ParseError>;
43
46
  /** Look up an object field by key (key-order tolerant per WIRE_FORMAT.md §2). */
@@ -179,6 +182,17 @@ interface DecodeError {
179
182
  readonly expectedShape?: string;
180
183
  }
181
184
  type R<T> = Result<T, DecodeError>;
185
+ type CR<T> = {
186
+ readonly ok: true;
187
+ readonly value: T;
188
+ } | {
189
+ readonly ok: false;
190
+ readonly error: string;
191
+ };
192
+ /** A `DataSource` — port of F# `ColumnCodec.decodeJson` (Phase 88: `schema`
193
+ * may be omitted on an embedded source; inferred in Ordinal column order). */
194
+ declare const decodeDataSource: (j: JsonAst) => CR<DataSource>;
195
+ declare const decodePipelineCore: (j: JsonAst) => CR<Transform[]>;
182
196
  /**
183
197
  * Phase 818 — materialise a LIVE Transform source's resolved store value as the
184
198
  * evaluation input table: row-major rows transpose through the same 815
@@ -256,6 +270,54 @@ declare const decodeNode: (json: string, policy?: DecodePolicy) => R<Node<unknow
256
270
  * only rather than a closure.
257
271
  */
258
272
  declare const decodeOp: (json: string, policy?: DecodePolicy) => R<TreeOp<unknown>>;
273
+ /**
274
+ * Decode a canonical-JSON payload holding EITHER one `TreeOp` or an array of
275
+ * them — the shape a host sends down an op channel.
276
+ *
277
+ * It exists because the obvious composition is unsound. A consumer holding
278
+ * "one op or an array" reaches for `JSON.parse`, walks the array, and calls
279
+ * `decodeOp(JSON.stringify(entry))` per element; the embedded standalone bundle
280
+ * did exactly that. Three things go wrong, and none of them is visible from the
281
+ * call site:
282
+ *
283
+ * - The whole payload passes through `JSON.parse` BEFORE any bound applies, so
284
+ * the §21 depth gate never sees it. `JSON.parse` is native and will not
285
+ * overflow, but the document it hands back is unbounded, and the per-op
286
+ * decode that follows measures each op against a limit the batch already
287
+ * escaped.
288
+ * - `JSON.parse` is a different parser with different §20 answers: it keeps the
289
+ * LAST duplicate member, reads `1e999` as `Infinity`, and accepts nothing
290
+ * this parser refuses. Re-stringifying its output launders every one of those
291
+ * differences into bytes this decoder then accepts without complaint — the
292
+ * §20.1 undeclared-entry-point defect, arrived at by composition.
293
+ * - `MAX_NODES` is reset per `decodeOp` call, so it bounded ONE op rather than
294
+ * the batch: 1 000 ops of 100 000 nodes each passed every check.
295
+ *
296
+ * So the batch is parsed ONCE through this package's own parser, the array is
297
+ * split at the AST, and the node budget is carried ACROSS the ops rather than
298
+ * reset per op.
299
+ */
300
+ declare const decodeOps: (json: string, policy?: DecodePolicy) => R<readonly TreeOp<unknown>[]>;
301
+
302
+ /** A total codec result — a value, or a named refusal. Never a throw. */
303
+ type CapabilityDeclResult<T> = {
304
+ readonly ok: true;
305
+ readonly value: T;
306
+ } | {
307
+ readonly ok: false;
308
+ readonly error: string;
309
+ };
310
+ /**
311
+ * Encode a capability declaration to its canonical-JSON string. Total: every
312
+ * field of every case is representable, so there is nothing to refuse.
313
+ */
314
+ declare const encodeCapabilityDeclaration: (cap: Capability) => string;
315
+ /**
316
+ * Decode a canonical capability-declaration string. Total — a malformed or
317
+ * un-carryable declaration yields `{ ok: false, error }` naming what was wrong,
318
+ * never an exception and never a partially-built capability.
319
+ */
320
+ declare const decodeCapabilityDeclaration: (json: string) => CapabilityDeclResult<Capability>;
259
321
 
260
322
  type ApplyErrorCode = 'NodeNotFound' | 'ParentNotFound' | 'ChildlessKind' | 'PositionOutOfRange' | 'DuplicateNodeId' | 'FieldNotFound' | 'SlotNotFound' | 'KindMismatch' | 'PathInvalid' | 'PathNotSupportedYet' | 'OrderingMismatch' | 'BatchAborted';
261
323
  interface ApplyError {
@@ -509,10 +571,62 @@ type DecodeResult<T> = {
509
571
  declare const decodeDagRecord: (json: string) => DecodeResult<DagOpRecord<unknown>>;
510
572
 
511
573
  type N = Node<unknown>;
512
- /** A `(nodeId, facet)` cell that could not be auto-merged. */
574
+ /**
575
+ * The class of a merge refusal. Mirrors the reference host's
576
+ * `MergeConflictClass` spelling-for-spelling — these strings are the `class`
577
+ * member of the committed refusal envelope, so they are wire, not prose.
578
+ */
579
+ type MergeConflictClass = 'ConcurrentEdit' | 'ConcurrentMove' | 'DeleteModify' | 'KindSwapOrphansPin' | 'ReorderVsStructural' | 'CombinedCycle';
580
+ /**
581
+ * One SIDE of a two-sided refusal: that branch's value for the contended cell,
582
+ * plus the branch's own opaque provenance tag.
583
+ *
584
+ * The `value` is the contended cell's canonical encoding, EXCEPT for the
585
+ * `style.*` sub-facets, whose value is the sub-field's case name — which
586
+ * coincides with its wire spelling only because every style sub-field is
587
+ * enum-shaped. Do not generalise it to a compound cell.
588
+ */
589
+ interface MergeSide {
590
+ readonly value: string;
591
+ readonly tag?: string;
592
+ }
593
+ /**
594
+ * A `(nodeId, facet)` cell that could not be auto-merged, as a two-sided
595
+ * recovery envelope.
596
+ *
597
+ * `a` and `b` are the SIDES view: the first- and second-argument branches'
598
+ * values for the contended cell, populated on EVERY refusal. Swapping the
599
+ * branches TRANSPOSES them and changes nothing else — which is what lets two
600
+ * replicas that merged the same pair in opposite orders agree about what the
601
+ * other side wanted. `base` is the LCA value (the empty string for a cell that
602
+ * exists on neither side of the LCA, such as an `insert`).
603
+ *
604
+ * `primacyHeld` is the precedence view, and on this tier it is always `false`:
605
+ * `merge3Way` is the author-agnostic entry point, so neither side is pinned.
606
+ * The reference host's precedence slots (`primary` / `secondary` /
607
+ * `secondaryTag`) are deliberately NOT mirrored here — they are populated
608
+ * exactly when a pin is held, and this tier has no author classifier to hold one
609
+ * with, so they could only ever be absent. They arrive with the entry point that
610
+ * supplies an author, not before.
611
+ *
612
+ * The reference host's `choices` menu is not mirrored either, but for a
613
+ * different reason, and the difference matters to whoever ports it. It is NOT
614
+ * empty on this tier: every choice the reference offers names a slot the
615
+ * envelope populated, so an unpinned refusal offers `KeepBase` / `KeepA` /
616
+ * `KeepB` — all three well-defined here — rather than the `KeepSecondary` that
617
+ * would name the empty precedence slot. The menu is left out because it is
618
+ * derivable from the sides plus the pin, which is also why the shared refusal
619
+ * corpus does not encode it; a port that wants it computes it, and must not
620
+ * infer from the absence that this tier has no menu to offer.
621
+ */
513
622
  interface MergeConflict {
514
623
  readonly nodeId: string;
515
624
  readonly facet: string;
625
+ readonly class: MergeConflictClass;
626
+ readonly base: string;
627
+ readonly a: MergeSide;
628
+ readonly b: MergeSide;
629
+ readonly primacyHeld: boolean;
516
630
  }
517
631
  /** Outcome of a 3-way merge: the merged tree, or the conflicting cells. */
518
632
  type MergeResult = {
@@ -529,6 +643,25 @@ type MergeResult = {
529
643
  * tie-break, no wall-clock) — byte-identical to the F# `TreeMerge.merge3Way`.
530
644
  */
531
645
  declare const merge3Way: (base: N, a: N, b: N) => MergeResult;
646
+ /**
647
+ * Order a refusal set deterministically. `(nodeId, facet)` is unique within one
648
+ * merge — a facet of a node is merged once — so this totally orders an envelope
649
+ * regardless of the fold's internal emission order.
650
+ */
651
+ declare const sortConflictsCanonical: (conflicts: readonly MergeConflict[]) => readonly MergeConflict[];
652
+ /**
653
+ * Canonical JSON of a REFUSAL envelope: the conflict set as a sorted array of
654
+ * `{a,b,base,class,facet,nodeId,primacyHeld}` objects (object keys alphabetical,
655
+ * array entries in `(nodeId, facet)` order). Byte-stable across hosts, so a
656
+ * sha256 over it is the cross-host refusal hash — the determinism artefact for a
657
+ * REFUSED structural merge, the analogue of the outcome hash for an auto-merge.
658
+ *
659
+ * The precedence view is deliberately projected as `primacyHeld` alone: the
660
+ * pinned winner and loser are derivable from the sides plus the pin, and a
661
+ * corpus that committed both would pin the same value twice and go red on a host
662
+ * that agreed about the merge.
663
+ */
664
+ declare const encodeMergeEnvelope: (conflicts: readonly MergeConflict[]) => string;
532
665
 
533
666
  /**
534
667
  * A wire profile id — `<name>@<major>.<minor>` (e.g. `core@1.0`). `name` is the
@@ -840,4 +973,74 @@ declare const withStateSeeds: <TMsg, S extends {
840
973
  readonly state?: Readonly<Record<string, unknown>>;
841
974
  }>(tree: Node<TMsg>, sources: S) => S;
842
975
 
843
- export { type Answer, type AnswerContract, type AnswerField, type AnswerSpace, type AnswerValue, type ApplyError, type ApplyErrorCode, type ApplyResult, type Compatibility, type DagActor, type DagOpRecord, type DagResultEnvelope, type DecodeError, type DecodeErrorCode, type Decoded, ELICITATION_KEY, ELICITATION_VERSION, type ElicitationEnvelope, type ElicitationError, type ElicitationErrorCode, type ElicitationOutcome, type ElicitationOutcomeEnvelope, type Envelope, type EnvelopeError, type EnvelopeErrorCode, type EvalEnv, type FreshIds, HOST_RESERVED_PREFIX, type JsonAst, type MergeConflict, type MergeResult, type OpApplyTelemetryRecord, PAYLOAD_KEY, PROFILE_KEY, type ParseError, type PlaceError, type PlaceTarget, type Placement, type Profile, REQUIRED_PROFILE_KEY, type SourceResolver, type TreeOp, type UnknownKind, apply, canPlace, cellString, coerce, collectStateSeeds, coreV1, decodeDagRecord, decodeElicitation, decodeElicitationOutcome, decodeEnvelope, decodeEnvelopeAst, decodeNode, decodeNodeTolerant, decodeOp, decodeTolerant, derivedFreshIds, duplicateOp, duplicateOpWith, encodeCell, encodeColExpr, encodeDagRecord, encodeDataSource, encodeElicitation, encodeElicitationOutcome, encodeEnvelope, encodeNode, encodeOp, encodePipeline, evalErrorString, evalPipeline, evalPipelineInEnv, evalPipelineWith, evalPipelineWithInEnv, evalSource, field as jsonField, liveValueToTable, merge3Way, moveOp, negotiate, negotiateEnvelope, noResolve, nudgeOp, parse, pasteOp, pasteOpWith, pipelineParams, placeOp, reencodeNode, renderAstCanonical, renderProfile, sequentialFreshIds, stepParams, substituteListParams, tryParseProfile, validateAnswer, validateAnswerAt, validateAnswerDocument, withStateSeeds };
976
+ /**
977
+ * The identity text rendition of a buffered value.
978
+ *
979
+ * `String(v)` is deliberately not used for numbers: it spells `1e21` as `1e+21`
980
+ * here and `1E+21` on .NET, and the hosts have to agree. `formatFiniteDouble` is
981
+ * the layout the corpus bytes already go through, so a number reads here exactly
982
+ * as it reads on the wire.
983
+ */
984
+ declare const identityFormat: (v: unknown) => string;
985
+ /**
986
+ * The JSON number grammar, and nothing wider. Surrounding ASCII whitespace is
987
+ * trimmed first — a reader's trailing space is not a type error — but a leading
988
+ * `+`, a bare `.5`, a hex literal and a thousands separator are all refused,
989
+ * because a grammar each host guesses at is a grammar each host guesses at
990
+ * differently.
991
+ *
992
+ * `Number(text)` is emphatically NOT the implementation: it accepts `0x10`,
993
+ * `Infinity`, `1_0` and the empty string, all of which this must refuse.
994
+ */
995
+ declare const tryNumberText: (text: string) => number | undefined;
996
+ /**
997
+ * The scalar a piece of buffer text denotes, when it denotes one. `true` /
998
+ * `false` and the JSON number grammar only — an empty string is NOT null here,
999
+ * because a cleared text field is an empty string and reading it as "no value"
1000
+ * would make the buffer lie about what the reader did.
1001
+ */
1002
+ declare const scalarOfText: (text: string) => string | number | boolean | undefined;
1003
+ /**
1004
+ * Fixed-point text: sign, integer part, and EXACTLY `decimals` fraction digits,
1005
+ * `.` as the point, no grouping. Rounding is half-away-from-zero, spelled as
1006
+ * `floor(x + 0.5)` on the absolute value rather than as `toFixed` — `toFixed`
1007
+ * rounds half-to-even on some values through the double's binary expansion, and
1008
+ * a rounding mode each host picks a default for is a divergence waiting to be
1009
+ * found by a fixture.
1010
+ */
1011
+ declare const fixedText: (decimals: number, v: number) => string;
1012
+ /**
1013
+ * The `Format.Number` codec's rendition: fixed-point at the declared decimals,
1014
+ * or the identity when the codec declares none.
1015
+ *
1016
+ * A NON-numeric buffered value falls back to the identity text rather than
1017
+ * failing. The codec is a declaration about presentation, and a value of the
1018
+ * wrong shape underneath it is the slot's problem, reported by the slot — a
1019
+ * format function that threw here would take out the render of a tree whose only
1020
+ * defect is a mistyped binding.
1021
+ */
1022
+ declare const numberText: (decimals: number | undefined, v: unknown) => string;
1023
+ /**
1024
+ * The wire-survivability failure a DECODED host-only projection throws when a
1025
+ * reader tries to run it.
1026
+ *
1027
+ * It exists because the alternative — returning a default — is
1028
+ * indistinguishable from a real answer at the slot: a decoded `Binding.Computed`
1029
+ * used to hand back `undefined` and render it as though the computation had run.
1030
+ *
1031
+ * The `name` is set explicitly and consumers match on IT rather than on
1032
+ * `instanceof`: the renderer lives in another package with its own bundle, so an
1033
+ * identity check across the boundary is a check on which copy of the class was
1034
+ * loaded, which is not the question being asked.
1035
+ */
1036
+ declare class WireSurvivabilityError extends Error {
1037
+ constructor(message: string);
1038
+ }
1039
+ /**
1040
+ * The one message a decoded `Binding.Computed` carries. A constant rather than a
1041
+ * template at each site so the resolver can recognise it, the corpus can pin it,
1042
+ * and every host can render the same sentence.
1043
+ */
1044
+ declare const DECODED_COMPUTED_MESSAGE = "Binding.Computed has no wire projection (decoded from a '<closure>' sentinel) \u2014 use Binding.Expr / Transform / State";
1045
+
1046
+ export { type Answer, type AnswerContract, type AnswerField, type AnswerSpace, type AnswerValue, type ApplyError, type ApplyErrorCode, type ApplyResult, type CapabilityDeclResult, type Compatibility, DECODED_COMPUTED_MESSAGE, type DagActor, type DagOpRecord, type DagResultEnvelope, type DecodeError, type DecodeErrorCode, type Decoded, ELICITATION_KEY, ELICITATION_VERSION, type ElicitationEnvelope, type ElicitationError, type ElicitationErrorCode, type ElicitationOutcome, type ElicitationOutcomeEnvelope, type Envelope, type EnvelopeError, type EnvelopeErrorCode, type EvalEnv, type FreshIds, HOST_RESERVED_PREFIX, type JsonAst, type MergeConflict, type MergeConflictClass, type MergeResult, type MergeSide, type OpApplyTelemetryRecord, PAYLOAD_KEY, PROFILE_KEY, type ParseError, type PlaceError, type PlaceTarget, type Placement, type Profile, REQUIRED_PROFILE_KEY, type SourceResolver, type TreeOp, type UnknownKind, WireSurvivabilityError, apply, canPlace, cellString, coerce, collectStateSeeds, coreV1, decodeCapabilityDeclaration, decodeDagRecord, decodeDataSource, decodeElicitation, decodeElicitationOutcome, decodeEnvelope, decodeEnvelopeAst, decodeNode, decodeNodeTolerant, decodeOp, decodeOps, decodePipelineCore as decodePipeline, decodeTolerant, derivedFreshIds, duplicateOp, duplicateOpWith, encodeCapabilityDeclaration, encodeCell, encodeColExpr, encodeDagRecord, encodeDataSource, encodeElicitation, encodeElicitationOutcome, encodeEnvelope, encodeMergeEnvelope, encodeNode, encodeOp, encodePipeline, evalErrorString, evalPipeline, evalPipelineInEnv, evalPipelineWith, evalPipelineWithInEnv, evalSource, fixedText, identityFormat, field as jsonField, liveValueToTable, merge3Way, moveOp, negotiate, negotiateEnvelope, noResolve, nudgeOp, numberText, parse, pasteOp, pasteOpWith, pipelineParams, placeOp, reencodeNode, renderAstCanonical, renderProfile, scalarOfText, sequentialFreshIds, sortConflictsCanonical, stepParams, substituteListParams, tryNumberText, tryParseProfile, validateAnswer, validateAnswerAt, validateAnswerDocument, withStateSeeds };