Skip to main content

Deterministic Three-Way Merge for JSON Values
draft-jurkovikj-json-three-way-merge-00

Document Type Active Internet-Draft (individual)
Author Antun Jurkovikj
Last updated 2026-08-04
RFC stream (None)
Intended RFC status (None)
Formats
Additional resources Demo source, vectors, and tests
Interactive browser demonstration
libdualnative v0.1.0-alpha.1 reference implementation
Stream Stream state (No stream defined)
Consensus boilerplate Unknown
RFC Editor Note (None)
IESG IESG state I-D Exists
Telechat date (None)
Responsible AD (None)
Send notices to (None)
draft-jurkovikj-json-three-way-merge-00
Network Working Group                                       A. Jurkovikj
Internet-Draft                                             4 August 2026
Intended status: Experimental                                           
Expires: 5 February 2027

             Deterministic Three-Way Merge for JSON Values
                draft-jurkovikj-json-three-way-merge-00

Abstract

   For a fixed, disclosed resource policy, this document defines a
   deterministic three-way merge operation for a restricted JSON value
   domain.  Given a shared base value and two independently derived
   values, called source and target, the operation produces either one
   complete merged JSON value or an ordered set of structured conflicts.

   The operation defines strict JSON input processing, finite binary64
   number normalization, scalar and object merge laws, explicit missing-
   member semantics, RFC 6901 conflict paths, typed conflict kinds, a
   fail-closed result for arrays, and bounded failure behavior.  It is
   independent of HTTP and does not define array merge semantics,
   application-specific semantic resolution, content identity, or
   authorization policy.

Status of This Memo

   This Internet-Draft is submitted in full conformance with the
   provisions of BCP 78 and BCP 79.

   Internet-Drafts are working documents of the Internet Engineering
   Task Force (IETF).  Note that other groups may also distribute
   working documents as Internet-Drafts.  The list of current Internet-
   Drafts is at https://datatracker.ietf.org/drafts/current/.

   Internet-Drafts are draft documents valid for a maximum of six months
   and may be updated, replaced, or obsoleted by other documents at any
   time.  It is inappropriate to use Internet-Drafts as reference
   material or to cite them other than as "work in progress."

   This Internet-Draft will expire on 5 February 2027.

Copyright Notice

   Copyright (c) 2026 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

Jurkovikj                Expires 5 February 2027                [Page 1]
Internet-Draft            JSON Three-Way Merge               August 2026

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents (https://trustee.ietf.org/
   license-info) in effect on the date of publication of this document.
   Please review these documents carefully, as they describe your rights
   and restrictions with respect to this document.  Code Components
   extracted from this document must include Revised BSD License text as
   described in Section 4.e of the Trust Legal Provisions and are
   provided without warranty as described in the Revised BSD License.

Table of Contents

   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   3
     1.1.  Goals . . . . . . . . . . . . . . . . . . . . . . . . . .   4
     1.2.  Non-Goals . . . . . . . . . . . . . . . . . . . . . . . .   4
   2.  Relationship to Existing JSON Standards . . . . . . . . . . .   5
   3.  Conventions and Terminology . . . . . . . . . . . . . . . . .   6
   4.  Input Domain and Normalization  . . . . . . . . . . . . . . .   7
     4.1.  One Complete JSON Value . . . . . . . . . . . . . . . . .   7
     4.2.  Binary64 Number Model . . . . . . . . . . . . . . . . . .   7
     4.3.  Merge Value Kinds . . . . . . . . . . . . . . . . . . . .   8
     4.4.  Normalized Equality and Kind  . . . . . . . . . . . . . .   8
   5.  Validation and Failure Precedence . . . . . . . . . . . . . .   9
     5.1.  Input-Side Order  . . . . . . . . . . . . . . . . . . . .   9
     5.2.  Within One Input Side . . . . . . . . . . . . . . . . . .   9
     5.3.  Unsupported Arrays  . . . . . . . . . . . . . . . . . . .  10
     5.4.  Invalid Input . . . . . . . . . . . . . . . . . . . . . .  11
     5.5.  Resource Policy . . . . . . . . . . . . . . . . . . . . .  11
   6.  Deterministic Merge Operation . . . . . . . . . . . . . . . .  14
     6.1.  Equality Shortcuts and Merge Laws . . . . . . . . . . . .  14
     6.2.  Scalar and Type Merge . . . . . . . . . . . . . . . . . .  15
     6.3.  Object Merge  . . . . . . . . . . . . . . . . . . . . . .  15
     6.4.  Object-Member Presence Matrix . . . . . . . . . . . . . .  16
     6.5.  No Partial Result . . . . . . . . . . . . . . . . . . . .  17
   7.  Conflicts and Diagnostic Ordering . . . . . . . . . . . . . .  17
     7.1.  Conflict Kinds  . . . . . . . . . . . . . . . . . . . . .  17
     7.2.  Presence Encoding . . . . . . . . . . . . . . . . . . . .  18
     7.3.  Path and Conflict Ordering  . . . . . . . . . . . . . . .  18
   8.  JSON Result Form  . . . . . . . . . . . . . . . . . . . . . .  19
     8.1.  Merged  . . . . . . . . . . . . . . . . . . . . . . . . .  19
     8.2.  Conflict  . . . . . . . . . . . . . . . . . . . . . . . .  20
     8.3.  Unsupported . . . . . . . . . . . . . . . . . . . . . . .  20
     8.4.  Invalid Input . . . . . . . . . . . . . . . . . . . . . .  20
     8.5.  Resource Limit  . . . . . . . . . . . . . . . . . . . . .  20
   9.  Examples  . . . . . . . . . . . . . . . . . . . . . . . . . .  21
     9.1.  Independent Object Changes  . . . . . . . . . . . . . . .  21
     9.2.  Missing Is Not Null . . . . . . . . . . . . . . . . . . .  22
     9.3.  Delete Versus Edit  . . . . . . . . . . . . . . . . . . .  22
     9.4.  Divergent Additions Are Conservative  . . . . . . . . . .  22

Jurkovikj                Expires 5 February 2027                [Page 2]
Internet-Draft            JSON Three-Way Merge               August 2026

     9.5.  Binary64 Equality . . . . . . . . . . . . . . . . . . . .  23
   10. Informative HTTP Reconciliation Example . . . . . . . . . . .  23
   11. Security Considerations . . . . . . . . . . . . . . . . . . .  23
     11.1.  Resource Exhaustion  . . . . . . . . . . . . . . . . . .  23
     11.2.  Conflict-Value Disclosure  . . . . . . . . . . . . . . .  24
     11.3.  Merge Success Is Not Semantic Safety . . . . . . . . . .  24
     11.4.  Base Authenticity and Current State  . . . . . . . . . .  24
     11.5.  Binary64 Rounding  . . . . . . . . . . . . . . . . . . .  24
     11.6.  Unicode and Path Presentation  . . . . . . . . . . . . .  25
     11.7.  Unsupported Arrays . . . . . . . . . . . . . . . . . . .  25
   12. IANA Considerations . . . . . . . . . . . . . . . . . . . . .  25
   13. Implementation Status . . . . . . . . . . . . . . . . . . . .  25
   14. References  . . . . . . . . . . . . . . . . . . . . . . . . .  26
     14.1.  Normative References . . . . . . . . . . . . . . . . . .  26
     14.2.  Informative References . . . . . . . . . . . . . . . . .  26
   Appendix A.  Merge Pseudocode . . . . . . . . . . . . . . . . . .  27
   Appendix B.  Conformance Vectors  . . . . . . . . . . . . . . . .  28
     B.1.  Successful Merge Vectors  . . . . . . . . . . . . . . . .  29
     B.2.  Conflict Vectors  . . . . . . . . . . . . . . . . . . . .  30
     B.3.  Unsupported-Array Vectors . . . . . . . . . . . . . . . .  32
     B.4.  Invalid-Input Precedence Vectors  . . . . . . . . . . . .  32
   Appendix C.  Design Rationale . . . . . . . . . . . . . . . . . .  32
     C.1.  Why Arrays Fail Closed  . . . . . . . . . . . . . . . . .  33
     C.2.  Why Divergent Additions Do Not Recurse  . . . . . . . . .  33
     C.3.  Why Merge Is Value-Deterministic Rather Than
           Byte-Canonical  . . . . . . . . . . . . . . . . . . . . .  33
     C.4.  Why UTF-16 Code-Unit Ordering . . . . . . . . . . . . . .  33
   Author's Address  . . . . . . . . . . . . . . . . . . . . . . . .  33

1.  Introduction

   Systems commonly retain a JSON value, allow two parties to edit
   copies of that value independently, and later need to reconcile both
   edits.  Existing standardized JSON patch formats describe a change to
   one target value, but do not define how to reconcile two complete
   edited values derived from one shared base.

   Without a common operation, implementations often use textual merge,
   last-writer-wins replacement, or application-specific rules.  Those
   approaches can produce different outcomes across runtimes or silently
   discard one party's change.

   This document defines an implementation-independent three-way merge
   operation:

Jurkovikj                Expires 5 February 2027                [Page 3]
Internet-Draft            JSON Three-Way Merge               August 2026

   Merge(base, source, target)
     -> merged value
      | ordered conflicts
      | typed input, unsupported-domain, or resource failure

   The operation is deliberately conservative.  It merges normalized
   scalar values and recursively array-free objects.  Arrays are outside
   the initial merge domain and cause a typed fail-closed result even
   when all three arrays are equal.  A future specification can define
   array behavior under a separate semantic identity and separate
   conformance vectors.

1.1.  Goals

   This document provides:

   *  deterministic normalized equality for JSON values in the merge
      domain;

   *  merge laws that preserve one-sided and agreed changes;

   *  recursive combination of independent object-member changes;

   *  explicit distinction between a missing member and a present JSON
      null;

   *  conflicts for divergent edits, incompatible type changes,
      divergent additions, and edit-versus-delete cases;

   *  unambiguous RFC 6901 paths and deterministic conflict ordering;
      and

   *  bounded, complete outcomes without partial merged values or
      conflict prefixes.

1.2.  Non-Goals

   This document does not define:

   *  array merge, sequence alignment, keyed collections, or reordering
      rules;

   *  a JSON diff or patch-generation algorithm;

   *  a CRDT, operational transformation, consensus, or replication
      protocol;

Jurkovikj                Expires 5 February 2027                [Page 4]
Internet-Draft            JSON Three-Way Merge               August 2026

   *  application-specific semantic merge or automatic conflict
      resolution;

   *  provenance receipts, redaction formats, or migration between merge
      engines;

   *  JSON canonicalization or content identifiers as a merge
      prerequisite;

   *  an HTTP method, field, status code, Link relation, or media type;

   *  persistence, atomic commit, authentication, authorization, or
      licensing; or

   *  a determination that a structurally merged value is valid for an
      application's schema or policy.

2.  Relationship to Existing JSON Standards

   JSON Patch [RFC6902] expresses an ordered sequence of operations to
   apply to one target document.  JSON Merge Patch [RFC7396] expresses,
   by example, a patch to apply to one target value.  Both are useful
   mutation formats, but neither takes a shared base and two
   independently edited complete values or defines the conflicts
   specified here.

   JSON Pointer [RFC6901] defines the path syntax used by this document.
   JSON [RFC8259] defines the underlying interchange syntax and data
   model, subject to the additional input and number restrictions in
   this document.

   Internet JSON (I-JSON) [RFC7493] has related interoperability goals.
   Both I-JSON and this document require UTF-8, reject duplicate decoded
   object names, exclude lone surrogates, and account for binary64
   interoperability.  This document is not an I-JSON profile, however:
   it recursively excludes arrays, normatively converts admitted numbers
   to finite binary64 values, and does not import every I-JSON
   restriction, including its prohibition on Unicode noncharacters.

Jurkovikj                Expires 5 February 2027                [Page 5]
Internet-Draft            JSON Three-Way Merge               August 2026

   The JSON Canonicalization Scheme (JCS) [RFC8785] defines
   deterministic JSON serialization for hashing and signing.  This merge
   operation is deterministic at the normalized JSON-value and ordered-
   diagnostic levels and does not require JCS, a digest, or a content
   identifier.  An application that independently needs deterministic
   bytes can apply a compatible canonicalization contract after a
   successful merge.  The unsigned UTF-16 comparator used here for one
   decoded pointer token aligns with JCS property-name sorting, but this
   document's ordering of complete pointer-token sequences is separate
   and does not make JCS a merge prerequisite.

3.  Conventions and Terminology

   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
   "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
   "OPTIONAL" in this document are to be interpreted as described in
   BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all
   capitals, as shown here.

   *Base*  The shared JSON value from which source and target were
      independently derived.

   *Source*  One edited JSON value derived from the base.  The name does
      not grant this side authority over target.

   *Target*  The other edited JSON value derived from the base.  The
      name does not grant this side authority over source.

   *Normalized value*  A value admitted by the input-domain rules
      (Section 4), with every number represented by its normalized
      finite binary64 value.

   *Present*  An object member exists and has a JSON value, including
      JSON null.

   *Missing*  An object member does not exist.  Missing is an operation-
      internal state and is not a seventh JSON value kind.

   The base, source, and target root values are always present.  Missing
   can arise only while processing an object member.

   *Resource-policy identifier*  A stable identifier for one fixed set
      of resource ceilings, deterministic measurement encodings, and any
      policy-defined limit precedence.  The generic JSON result form
      carries this identifier in error.budget.

   *Merge processor*  An implementation of the operation defined by this
      document.

Jurkovikj                Expires 5 February 2027                [Page 6]
Internet-Draft            JSON Three-Way Merge               August 2026

   In pseudocode, B, S, and T denote base, source, and target values.
   M(B,S,T) denotes the merge operation. == denotes normalized equality
   as defined in Normalized Equality and Kind (Section 4.4), not textual
   equality of input JSON.

4.  Input Domain and Normalization

4.1.  One Complete JSON Value

   Each input is a UTF-8 sequence containing exactly one JSON value as
   defined by [RFC8259].  Leading and trailing JSON whitespace are
   permitted.  After the value and trailing whitespace, any additional
   octet is invalid.

   A leading UTF-8 byte order mark (the octet sequence encoding U+FEFF)
   MUST be rejected with code invalid_json.  This profile does not use
   the permissive RFC 8259 option to ignore a byte order mark while
   parsing.

   A merge processor MUST reject malformed JSON, invalid UTF-8, and
   escaped or unescaped lone surrogate code points.  It MUST preserve
   admitted Unicode scalar values exactly and MUST NOT apply Unicode
   normalization.

   An object MUST NOT contain the same decoded member name more than
   once.  Escape spelling does not make names distinct; for example, "a"
   and "\u0061" are duplicates in the same object and MUST be rejected
   before merge processing.

   Many general-purpose JSON parsers silently retain only the first or
   last occurrence of a duplicate member name.  A processor cannot
   satisfy the requirement above by inspecting a post-parse object after
   such a collapse.  It MUST use a parser mode or input front end that
   exposes every decoded member-name occurrence or rejects duplicates
   before collapsing them.

4.2.  Binary64 Number Model

   Every JSON number token MUST be converted to the nearest IEEE 754
   binary64 value [IEEE754] using round-to-nearest, ties-to-even.  A
   token whose conversion overflows to positive or negative infinity
   MUST be rejected.  NaN and infinity are not JSON number tokens and
   are invalid input.

   All finite binary64 values, including subnormal values, are admitted.
   Positive and negative zero compare equal and are normalized to
   positive zero.  Thus, the input tokens -0, 0, and 0.0 denote the same
   normalized number.

Jurkovikj                Expires 5 February 2027                [Page 7]
Internet-Draft            JSON Three-Way Merge               August 2026

   Different decimal tokens that convert to the same binary64 value are
   equal.  For example, 1, 1.0, and 1e0 are equal.  The token
   9007199254740993 converts to the binary64 value also denoted by
   9007199254740992; those tokens therefore compare equal under this
   profile.  Applications that require exact integers beyond binary64
   precision MUST encode them under an application-defined
   representation, such as JSON strings, rather than rely on this merge
   operation to preserve their decimal-token distinction.

   This document's acceptance and normalization of negative zero are
   explicit.  They do not impose the stricter negative-zero parser
   behavior discussed by verified Erratum 7920 for [RFC8785], because
   JCS conformance is not a merge input requirement here.

4.3.  Merge Value Kinds

   The merge domain contains these normalized JSON value kinds:

   *  null;

   *  boolean;

   *  number, represented by the binary64 number model (Section 4.2);

   *  string; and

   *  object whose values are recursively in this domain.

   Arrays are recursively unsupported.  An array at the root or below an
   object member prevents that complete input from entering the merge
   engine.

4.4.  Normalized Equality and Kind

   Two normalized values are equal when:

   *  both are null;

   *  both are the same boolean;

   *  both are the same normalized binary64 value;

   *  both are strings containing the same sequence of Unicode scalar
      values; or

   *  both are objects with the same decoded member-name set and
      recursively equal values for every member.

Jurkovikj                Expires 5 February 2027                [Page 8]
Internet-Draft            JSON Three-Way Merge               August 2026

   Object member order in input JSON does not affect equality.  No
   Unicode normalization, case folding, locale comparison, or
   application-specific coercion is performed.

   The five merge value kinds above are distinct.  In particular, a
   number is not equal to a string containing its decimal spelling, and
   null is not equal to a missing object member.

5.  Validation and Failure Precedence

5.1.  Input-Side Order

   The merge processor MUST process complete inputs in this order:

   1.  base;

   2.  source; and

   3.  target.

   It MUST stop at the first side that produces an invalid-input,
   unsupported-domain, or resource-limit result.  The semantic merge
   engine runs only after all three inputs are valid, normalized,
   recursively array-free, and admitted by the active resource policy.

   For example, an invalid base wins over an unsupported array in
   source, and an unsupported array in source wins over an invalid
   target.

5.2.  Within One Input Side

   Within one side, processing is bounded and left-to-right.  The
   processor MUST use this precedence at each parser event:

   1.  Check the raw input-octet ceiling before scanning.

   2.  Before entering a recognized container, check nesting depth and
       node count.

   3.  Before constructing a recognized scalar, check node count.

   4.  When an object member begins, check member count before decoding
       and retaining its name; check decoded name size while decoding
       it.

   5.  Detect a duplicate decoded member name before parsing that
       member's value.

Jurkovikj                Expires 5 February 2027                [Page 9]
Internet-Draft            JSON Three-Way Merge               August 2026

   6.  Before parsing an array child, check any per-array element
       ceiling and the document node ceiling.

   7.  Check decoded string size and raw number-token size while
       scanning.

   8.  Report syntax, Unicode, and numeric-domain failures at the first
       point they become knowable.

   9.  If the active policy defines a complete normalized-input byte
       ceiling, compute it using the deterministic encoding named by
       that policy and check it before admitting the side.

   If malformed syntax becomes knowable before a resource boundary is
   crossed, status invalid_input wins.  If a bounded token reaches the
   first prohibited octet before a defect in an unread suffix becomes
   knowable, status resource_limit wins.  Unsupported-array selection
   occurs only after the side is otherwise completely valid and
   admitted.

5.3.  Unsupported Arrays

   For one side, the processor first completes bounded JSON and number
   validation.  If the valid side contains one or more arrays, it MUST
   return status unsupported with:

   *  code arrays_not_supported;

   *  side base, source, or target; and

   *  the RFC 6901 path of the least array under the path ordering
      (Section 7.3).

   The root array path is the empty string.  Equal, unchanged, or empty
   arrays are still unsupported.  A processor MUST NOT apply an equality
   shortcut before array-domain validation.

   The selected path is the least path under that ordering, not
   necessarily the first array encountered in the raw JSON text.  A
   processor MAY maintain a running minimum during validation or compare
   retained candidate paths after validation; it MUST NOT substitute raw
   encounter order for the required path ordering.

Jurkovikj                Expires 5 February 2027               [Page 10]
Internet-Draft            JSON Three-Way Merge               August 2026

   The least selected array cannot be reached through an array element.
   If an array had an ancestor array, the ancestor's pointer-token
   sequence would be a strict prefix and would sort first.
   Consequently, the selected array is either the root or is reached
   only through object-member steps.  Numeric-looking object member
   names remain ordinary pointer tokens and are compared by the same
   token rules.

   An implementation MAY validate unsupported array content without
   retaining the complete array, but it MUST still detect malformed
   content, duplicate object names, invalid numbers, and resource
   failures according to the rules above.

5.4.  Invalid Input

   An invalid-input result uses one of these codes:

   *  invalid_json for malformed JSON, invalid UTF-8, trailing content,
      or an invalid Unicode scalar sequence;

   *  duplicate_object_name for a repeated decoded name in one object;
      or

   *  number_out_of_binary64_range for a number that cannot be
      represented as a finite binary64 value.

   The result MUST identify the failing input side.  It MUST contain an
   RFC 6901 path when the affected value or containing object is known.
   For duplicate_object_name, the path MUST identify the containing
   object; the duplicated name is not appended as a child token.  For
   number_out_of_binary64_range, the path MUST identify the offending
   number value itself.  A processor MAY include a zero-based UTF-8 byte
   offset and derived line and column information.  Such optional
   source-location details do not alter the merge outcome.

5.5.  Resource Policy

   Every merge execution MUST be subject to a finite resource policy.
   The policy MUST bound, at minimum:

   *  raw octets per input side;

   *  container nesting depth;

   *  total value nodes and object-member occurrences per input;

   *  array elements processed while validating an unsupported array;

Jurkovikj                Expires 5 February 2027               [Page 11]
Internet-Draft            JSON Three-Way Merge               August 2026

   *  decoded member-name and string-value octets;

   *  number-token octets;

   *  retained conflicts; and

   *  any serialized result retained by the processor.

   A policy MAY additionally bound the bytes in each complete normalized
   input.  Such a bound is distinct from raw-input, token, node, member,
   and serialized result limits.  The policy MUST identify the
   deterministic normalized-value encoding used for that measurement and
   the corresponding limit code.  This document neither selects nor
   requires a JSON canonicalization scheme for that purpose.

   A processor MUST associate its fixed ceilings with a stable resource-
   policy identifier.  If any ceiling changes, the identifier MUST also
   change.  Limits are implementation policy rather than universal
   ceilings defined by this document, but they are an explicit input to
   reproducible operation behavior.  An invocation MUST NOT silently
   select an unbounded path or raise a ceiling based on attacker-
   controlled input.

   A resource-limit result MUST identify the resource-policy identifier,
   limit code, charged side, path, inclusive limit, and observed value.
   To permit early failure without scanning or retaining a hostile
   suffix, observed SHOULD be reported as the first prohibited value,
   limit + 1.

   A processor can define additional limit codes.  The following code
   spellings SHOULD be used when the corresponding resource is bounded:

   input_bytes                 nesting_depth
   total_nodes                 total_object_members
   array_length                key_bytes
   string_bytes                number_token_bytes
   normalized_document_bytes   result_bytes
   merge_response_bytes
   conflict_count

   When a policy uses one of those suggested codes, the resource-limit
   path MUST be selected as follows:

Jurkovikj                Expires 5 February 2027               [Page 12]
Internet-Draft            JSON Three-Way Merge               August 2026

    +===========================+====================================+
    | Code                      | Failure path                       |
    +===========================+====================================+
    | input_bytes               | Root, because the check occurs     |
    |                           | before input scanning.             |
    +---------------------------+------------------------------------+
    | nesting_depth             | The would-be container path.       |
    +---------------------------+------------------------------------+
    | total_nodes               | The would-be value path.           |
    +---------------------------+------------------------------------+
    | total_object_members      | The containing object path.        |
    +---------------------------+------------------------------------+
    | array_length              | The would-be element path,         |
    |                           | including its zero-based array     |
    |                           | index.                             |
    +---------------------------+------------------------------------+
    | key_bytes                 | The containing object path; the    |
    |                           | oversized name is not admitted.    |
    +---------------------------+------------------------------------+
    | string_bytes              | The string value path.             |
    +---------------------------+------------------------------------+
    | number_token_bytes        | The number value path.             |
    +---------------------------+------------------------------------+
    | normalized_document_bytes | The first value or member, in the  |
    |                           | policy's deterministic normalized- |
    |                           | value encoding order, whose byte   |
    |                           | contribution crosses the ceiling;  |
    |                           | container punctuation uses the     |
    |                           | containing-container path.         |
    +---------------------------+------------------------------------+
    | result_bytes              | The first value or member, in      |
    |                           | deterministic merge-result         |
    |                           | encoding order, whose byte         |
    |                           | contribution crosses the ceiling;  |
    |                           | container punctuation uses the     |
    |                           | containing-container path.         |
    +---------------------------+------------------------------------+
    | merge_response_bytes      | The first unadmitted conflict      |
    |                           | path; root if a fixed envelope or  |
    |                           | a non-conflict response component  |
    |                           | crosses the ceiling.               |
    +---------------------------+------------------------------------+
    | conflict_count            | The first unadmitted conflict      |
    |                           | path.                              |
    +---------------------------+------------------------------------+

                                 Table 1

Jurkovikj                Expires 5 February 2027               [Page 13]
Internet-Draft            JSON Three-Way Merge               August 2026

   The path-attribution rule for an additional limit code MUST be
   defined by the resource policy.  Encoding-derived measurements MUST
   attribute each measured byte contribution in a deterministic order.
   Path-attribution rules are part of the resource policy; changing one
   requires a new resource-policy identifier.

   result_bytes measures the deterministic policy-selected serialization
   of a complete normalized merged value, excluding the generic result
   envelope.  It applies only after conflict discovery has established
   that the operation is conflict-free. merge_response_bytes measures
   the deterministic policy-selected serialization of the complete
   generic result form or of a fixed adopting envelope, including
   status-specific conflicts or error detail.  A policy that uses either
   code MUST identify the exact encoding being measured and, for
   merge_response_bytes, the exact envelope and included members.  If
   both codes apply to one successful operation, result_bytes MUST be
   checked before merge_response_bytes.  Output-side failures use side
   merge_result.

   Depth counts open object and array containers: a scalar root has
   depth zero, a root container has depth one, and entering a child
   container increments depth.  Every JSON value, including the root and
   every unsupported array container, counts as one node.  Each
   syntactically started object-member occurrence counts once, including
   a later-rejected duplicate.  Name and string ceilings count decoded
   UTF-8 octets; number-token ceilings count the raw token from an
   optional minus sign through the final digit of the token, excluding
   whitespace.

   Resource limits MUST be checked before the allocation, insertion,
   recursive descent, clone, or output append that would exceed the
   active ceiling.  Integer accounting MUST detect overflow rather than
   wrap.

   A processor claiming conformance to this document MUST configure
   limits high enough to execute every applicable conformance vector
   (Appendix B).

6.  Deterministic Merge Operation

6.1.  Equality Shortcuts and Merge Laws

   After all three inputs are admitted, each value merge applies these
   normalized equality shortcuts in order:

   if S == T: return S
   if B == S: return T
   if B == T: return S

Jurkovikj                Expires 5 February 2027               [Page 14]
Internet-Draft            JSON Three-Way Merge               August 2026

   Consequently, over the recursively array-free domain, the operation
   satisfies:

   M(B, B, B) = B
   M(B, S, B) = S
   M(B, B, T) = T
   M(B, X, X) = X

   Swapping source and target preserves a successful merged value and
   every conflict path.  The two oriented edit-versus-delete conflict
   kinds swap as described in Conflict Kinds (Section 7.1).

6.2.  Scalar and Type Merge

   If no equality shortcut applies and all three values are objects, the
   object operation in Object Merge (Section 6.3) applies.

   Otherwise, the processor MUST emit one conflict at the current path:

   *  concurrent_value_change when all three values have the same merge
      value kind and they are not all objects; or

   *  concurrent_type_change when their merge value kinds are not all
      the same.

   The processor MUST NOT choose source or target based on role name,
   textual ordering, timestamp, input member order, or serialization
   length.

6.3.  Object Merge

   If all three values at a path are objects and no equality shortcut
   applies, the processor considers the union of their decoded member
   names.  It processes those names in the unsigned UTF-16 order defined
   in Path and Conflict Ordering (Section 7.3) and applies the presence
   matrix (Section 6.4) to each member.

   When all three member states are present, the processor recursively
   applies the value merge at the child path.  Independent changes under
   different member names therefore combine into one object.

   The processor constructs a complete merged object only when no
   conflict exists anywhere in the operation.  If any conflict exists,
   non-conflicting provisional work is discarded and the result contains
   only the complete ordered conflict set.

Jurkovikj                Expires 5 February 2027               [Page 15]
Internet-Draft            JSON Three-Way Merge               August 2026

   Conflict discovery and conflict-diagnostic limit checks MUST precede
   construction or certification of a merged result.  A processor MUST
   NOT return a merged-result node, member, depth, encoding, or
   serialized-size limit while a semantic conflict remains to be
   discovered.  Those merged-result limits apply only after the
   operation is known to be conflict-free.  Conflict-count and conflict-
   response limits can still produce a complete resource_limit result
   during bounded conflict discovery.

6.4.  Object-Member Presence Matrix

   Presence equality compares both state and, for present states,
   normalized value.  The following matrix summarizes complete object-
   member behavior, including outcomes selected by the same three
   equality shortcuts:

   In the table, present B in any column means present with a normalized
   value equal to the base value denoted by B; it does not require
   identical input spelling.  S, T, and X likewise denote normalized
   values, and != denotes normalized inequality.

     +===========+===========+===========+===========================+
     | Base      | Source    | Target    | Outcome                   |
     +===========+===========+===========+===========================+
     | present B | missing   | present B | missing                   |
     +-----------+-----------+-----------+---------------------------+
     | present B | present B | missing   | missing                   |
     +-----------+-----------+-----------+---------------------------+
     | present B | missing   | present   | source_delete_target_edit |
     |           |           | T, T != B | conflict                  |
     +-----------+-----------+-----------+---------------------------+
     | present B | present   | missing   | source_edit_target_delete |
     |           | S, S != B |           | conflict                  |
     +-----------+-----------+-----------+---------------------------+
     | present B | missing   | missing   | missing                   |
     +-----------+-----------+-----------+---------------------------+
     | missing   | present S | missing   | present S                 |
     +-----------+-----------+-----------+---------------------------+
     | missing   | missing   | present T | present T                 |
     +-----------+-----------+-----------+---------------------------+
     | missing   | present X | present X | present X                 |
     +-----------+-----------+-----------+---------------------------+
     | missing   | present S | present   | add_add conflict          |
     |           |           | T, S != T |                           |
     +-----------+-----------+-----------+---------------------------+

                                  Table 2

Jurkovikj                Expires 5 February 2027               [Page 16]
Internet-Draft            JSON Three-Way Merge               August 2026

   If all three states are present, the merge operation (Section 6)
   recursively merges their values.  If base is missing and source and
   target add different objects under the same member name, the
   processor reports add_add at that member path.  It MUST NOT invent an
   empty base object and combine the divergent additions recursively.

6.5.  No Partial Result

   Every invocation produces exactly one complete status.  A conflict
   result MUST NOT contain a merged value.  A merged result MUST NOT
   contain conflicts.  An invalid-input, unsupported, or resource-limit
   result MUST NOT contain a merged value, a conflict prefix, parser
   output, or partially normalized value.

   If conflict accumulation or conflict-response measurement reaches a
   resource ceiling during conflict discovery, the processor MUST
   discard the partial collection and return one complete resource-limit
   result.  If the operation is conflict-free and merged-result
   construction or certification reaches a resource ceiling, it MUST
   discard the provisional result and return one complete resource-limit
   result.

7.  Conflicts and Diagnostic Ordering

7.1.  Conflict Kinds

   The conflict kinds are:

   *concurrent_value_change*  Base, source, and target are present with
      the same merge value kind, they are not all objects, and both
      sides differ from base and from each other.

   *concurrent_type_change*  Base, source, and target are present; no
      equality shortcut applies; and the three merge value kinds are not
      all the same.

   *add_add*  The member is missing from base and both sides add unequal
      normalized values.

   *source_delete_target_edit*  The member is present in base, source
      deletes it, and target changes it to a value unequal to base.

   *source_edit_target_delete*  The member is present in base, source
      changes it to a value unequal to base, and target deletes it.

   Every conflict contains one kind, one RFC 6901 path, and explicit
   base, source, and target presence states.  It MUST contain the
   complete normalized value for each present state unless an adopting

Jurkovikj                Expires 5 February 2027               [Page 17]
Internet-Draft            JSON Three-Way Merge               August 2026

   application defines a separate, non-conforming redacted diagnostic
   view.  A redacted view cannot be used as the input to automatic
   conflict resolution without application-specific rules.

7.2.  Presence Encoding

   In the JSON result form, presence is encoded as:

   {"state":"missing"}

   or:

   {"state":"present","value":<normalized-json-value>}

   The value member MUST be absent when state is missing and MUST be
   present when state is present.  A present value can be JSON null.

7.3.  Path and Conflict Ordering

   Paths are RFC 6901 JSON Pointers.  The document root is the empty
   string.  For a member name, ~ is escaped as ~0 and / is escaped as
   ~1.

   Ordering compares decoded pointer-token sequences, not rendered
   pointer strings.  Each token is compared lexicographically as a
   sequence of unsigned UTF-16 code units.  If all units in the shorter
   token are equal to the prefix of the longer token, the shorter token
   sorts first.  If all shared tokens are equal, the pointer with fewer
   tokens sorts first.

   This comparison is locale-independent and does not apply Unicode
   normalization.  A supplementary Unicode scalar value is compared
   through its UTF-16 surrogate pair.  Lone surrogates cannot occur
   because they are rejected during input processing.

   UTF-16 code-unit order deliberately differs from Unicode scalar-value
   order for some comparisons between supplementary and Basic
   Multilingual Plane characters.  An implementation whose native string
   ordering uses scalar values or UTF-8 bytes therefore MUST explicitly
   derive the UTF-16 code-unit order rather than use its runtime's
   default string comparison.

   The operation defined by this document produces at most one conflict
   for one logical path.  The conflict array MUST be sorted by the path
   ordering above.  Object-member traversal and least-array selection
   MUST use the same ordering.

Jurkovikj                Expires 5 February 2027               [Page 18]
Internet-Draft            JSON Three-Way Merge               August 2026

8.  JSON Result Form

   This section defines a generic JSON representation of an operation
   result.  It does not register a media type or require an application
   to transfer results over a network.  An adopting specification MAY
   wrap these members in a versioned envelope, but MUST preserve their
   meanings.  The ordering requirement applies to entries in the
   conflicts array, not to JSON object members.

   JSON object member order and insignificant whitespace in this result
   form are not significant.  Every emitted number value MUST represent
   the corresponding normalized binary64 value when parsed under the
   binary64 number model (Section 4.2), and negative zero MUST NOT be
   emitted.  When serialized, this result form MUST use UTF-8 and MUST
   NOT begin with a byte order mark.

   Every generic result MUST contain the string member status and
   exactly the status-specific top-level member shown below.  The other
   two status-specific members in the table MUST be absent.  Additional
   envelope or error-detail members remain permitted as described below.

      +================+===========================+================+
      | status         | Required top-level member | Forbidden top- |
      |                |                           | level members  |
      +================+===========================+================+
      | merged         | merged                    | conflicts,     |
      |                |                           | error          |
      +----------------+---------------------------+----------------+
      | conflict       | conflicts                 | merged, error  |
      +----------------+---------------------------+----------------+
      | unsupported    | error                     | merged,        |
      |                |                           | conflicts      |
      +----------------+---------------------------+----------------+
      | invalid_input  | error                     | merged,        |
      |                |                           | conflicts      |
      +----------------+---------------------------+----------------+
      | resource_limit | error                     | merged,        |
      |                |                           | conflicts      |
      +----------------+---------------------------+----------------+

                                  Table 3

8.1.  Merged

   {
     "status": "merged",
     "merged": {"name":"ready","owner":"B"}
   }

Jurkovikj                Expires 5 February 2027               [Page 19]
Internet-Draft            JSON Three-Way Merge               August 2026

   The merged member MUST contain the complete normalized merged value.

8.2.  Conflict

   {
     "status": "conflict",
     "conflicts": [
       {
         "kind": "concurrent_value_change",
         "path": "/title",
         "base": {"state":"present","value":"Draft"},
         "source": {"state":"present","value":"Ready"},
         "target": {"state":"present","value":"Review"}
       }
     ]
   }

   The conflicts array MUST be non-empty and ordered as specified in
   Path and Conflict Ordering (Section 7.3).

8.3.  Unsupported

   {
     "status": "unsupported",
     "error": {
       "code": "arrays_not_supported",
       "side": "source",
       "path": "/items"
     }
   }

8.4.  Invalid Input

   {
     "status": "invalid_input",
     "error": {
       "code": "duplicate_object_name",
       "side": "base",
       "path": ""
     }
   }

8.5.  Resource Limit

Jurkovikj                Expires 5 February 2027               [Page 20]
Internet-Draft            JSON Three-Way Merge               August 2026

   {
     "status": "resource_limit",
     "error": {
       "budget": "example-resource-budget-v1",
       "code": "nesting_depth",
       "side": "target",
       "path": "/configuration",
       "limit": 64,
       "observed": 65
     }
   }

   The required error members are:

       +================+========================+=================+
       | status         | Required error members | Additional      |
       |                |                        | defined members |
       +================+========================+=================+
       | unsupported    | code, side, path       | none            |
       +----------------+------------------------+-----------------+
       | invalid_input  | code, side; path when  | byte_offset,    |
       |                | known                  | line, column    |
       +----------------+------------------------+-----------------+
       | resource_limit | budget, code, side,    | none            |
       |                | path, limit, observed  |                 |
       +----------------+------------------------+-----------------+

                                  Table 4

   For unsupported and invalid_input, side is base, source, or target.
   For resource_limit, those values identify an input-side charge and
   merge_result identifies conflict or result construction.  In a
   resource_limit error, budget is a string containing the
   resource-policy identifier (Section 5.5).  Both limit and observed
   are non-negative integers.  Status-specific codes and path rules are
   defined in Validation and Failure Precedence (Section 5).

   An implementation can add envelope or error-detail members, but MUST
   NOT use a defined status, conflict kind, or error code with a
   different meaning.

9.  Examples

9.1.  Independent Object Changes

   Given:

Jurkovikj                Expires 5 February 2027               [Page 21]
Internet-Draft            JSON Three-Way Merge               August 2026

   base   = {"name":"draft","owner":"A"}
   source = {"name":"ready","owner":"A"}
   target = {"name":"draft","owner":"B"}

   source changes name, target changes owner, and neither change
   overlaps.  The merged value is:

   {"name":"ready","owner":"B"}

9.2.  Missing Is Not Null

   Given an empty base, source adds a present null member and target
   remains unchanged:

   base   = {}
   source = {"reviewed":null}
   target = {}

   The merged value is {"reviewed":null}. The member is not deleted
   because a present JSON null is a value, not the missing state.

9.3.  Delete Versus Edit

   Given:

   base   = {"title":"Draft"}
   source = {}
   target = {"title":"Ready"}

   the result contains source_delete_target_edit at /title, with source
   encoded as missing and the other two states encoded as present.

9.4.  Divergent Additions Are Conservative

   Given:

   base   = {}
   source = {"settings":{"left":true}}
   target = {"settings":{"right":true}}

   the result is add_add at /settings.  Although the two added objects
   contain different member names, they have no shared base object.
   This operation does not invent one and silently combine both
   additions.

Jurkovikj                Expires 5 February 2027               [Page 22]
Internet-Draft            JSON Three-Way Merge               August 2026

9.5.  Binary64 Equality

   Given:

   base   = {"count":1}
   source = {"count":1.0}
   target = {"count":2}

   base and source are equal after number normalization, so the merged
   value is {"count":2}.

10.  Informative HTTP Reconciliation Example

   This merge operation does not depend on HTTP.  An HTTP application
   can use it after a conditional state-changing request fails because
   the resource changed [RFC9110]:

   1.  base is the representation state previously observed by the
       client;

   2.  source is the client's complete proposed state derived from base;

   3.  target is freshly obtained current state;

   4.  the application runs this merge operation;

   5.  conflicts are reviewed or resolved under application policy; and

   6.  a successful candidate is validated and submitted using a
       precondition for the newly observed current state.

   A successful structural merge does not authorize a blind retry.  The
   application still needs current-state validation, authorization,
   schema and policy checks, and an atomic commit mechanism.  Agentic
   State Transfer [I-D.jurkovikj-httpapi-agentic-state] is an example of
   an HTTP profile whose application layer can perform reconciliation
   after a precondition failure; it does not normatively depend on this
   document.

11.  Security Considerations

11.1.  Resource Exhaustion

   JSON depth, width, token size, and conflict count are attacker-
   controlled.  Processors MUST enforce the resource policy
   (Section 5.5) before prohibited recursion or allocation.  Rejecting
   arrays does not permit unbounded parsing merely to discover that an
   array is unsupported.

Jurkovikj                Expires 5 February 2027               [Page 23]
Internet-Draft            JSON Three-Way Merge               August 2026

11.2.  Conflict-Value Disclosure

   A conforming conflict contains values from base, source, and target.
   Those values can include credentials, personal information, deleted
   content, configuration secrets, or fields hidden from one
   participant.  Conflict results MUST be treated as sensitive
   application data.  Authentication and authorization checks MUST
   precede disclosure.  Implementations MUST NOT log, render, or
   transmit raw conflicts by default when doing so would expose data to
   an unauthorized party.  An authorized interface that presents
   conflict values MUST encode them as untrusted structured data for the
   destination context and MUST NOT interpret them as HTML, terminal
   controls, executable content, or format strings.

   An application can create a redacted human-facing view, but that view
   is not a conforming complete conflict and might omit information
   required for safe resolution.

11.3.  Merge Success Is Not Semantic Safety

   This operation detects structural overlap under its fixed rules.  It
   does not know application invariants.  Independent edits can merge
   structurally while violating a schema, cross-field constraint,
   business rule, authorization condition, or safety policy.
   Applications MUST validate the complete candidate under current
   policy before commit.

   Automated agents MUST NOT interpret status merged as approval or
   authority to perform an external action.

11.4.  Base Authenticity and Current State

   An incorrect or attacker-selected base can make concurrent changes
   appear one-sided.  Applications need an authenticated way to
   associate base with the states actually observed by both editors.  In
   concurrent systems, current-state validation and candidate commit
   need to be atomic or protected by an appropriate compare-and-swap
   mechanism.

11.5.  Binary64 Rounding

   Decimal tokens can collapse to one binary64 value, including large
   integers outside the exactly representable range.  Applications MUST
   NOT use this merge number model for identifiers, account balances,
   counters, or other values that require decimal or arbitrary-precision
   distinction unless their application representation encodes that
   distinction explicitly.

Jurkovikj                Expires 5 February 2027               [Page 24]
Internet-Draft            JSON Three-Way Merge               August 2026

11.6.  Unicode and Path Presentation

   No Unicode normalization is performed.  Visually similar member names
   can remain distinct.  Interfaces SHOULD display escaped paths and
   untrusted member names in a form that prevents control-character
   injection, bidirectional-text confusion, and HTML or terminal
   interpretation.  Processors MUST compare decoded logical pointer
   tokens, not presentation strings.

11.7.  Unsupported Arrays

   Treating an unsupported result as permission to fall back to last-
   writer-wins, index-wise merge, or an older unversioned array
   algorithm can silently discard data.  Applications SHOULD preserve
   the last known good state and require a separately identified array-
   aware profile or explicit review.

12.  IANA Considerations

   This document has no IANA actions.

13.  Implementation Status

   This section follows the guidance in [RFC7942].  It records
   implementation information as of 2026-08-04 and is expected to be
   removed before publication as an RFC.

   libdualnative provides a Rust implementation of the scalar/object
   operation, typed conflicts, array rejection, and fixed resource
   failures specified here.  Its policy-specific boundary suite
   exercises exact and first-prohibited ceilings, failure precedence,
   conflict atomicity, and no-partial-result behavior.  Its WebAssembly
   package is a facade over the same Rust engine and is parity evidence,
   not an independent implementation.  The experimental v0.1.0-alpha.1
   GitHub prerelease is available at https://github.com/DualNative/
   libdualnative-v1/releases/tag/v0.1.0-alpha.1
   (https://github.com/DualNative/libdualnative-v1/releases/tag/
   v0.1.0-alpha.1).  The release source commit is
   d986f1ab9b851dea8dd8d4a99dac6ef92acd8f25.

   The implementation also exposes a separate RFC 8785 and SHA-256
   content identity operation.  That operation is not required by this
   document and is not evidence of an independent merge implementation.

   At the time of writing, no complete, independently maintained
   implementation in another language is known.  The machine-readable
   companion suite contains 41 vectors and has SHA-256 digest
   13467e8bf70718bdf247da4b007730e4f0e560f538db6a86f92a88a457c35e62.

Jurkovikj                Expires 5 February 2027               [Page 25]
Internet-Draft            JSON Three-Way Merge               August 2026

   During draft preparation, the Rust reference implementation was run
   against all 41 companion vectors.  A temporary JavaScript cross-check
   independently exercised the 33 successful-merge, conflict, and
   unsupported-array vectors.  It did not implement strict input-error
   or resource-limit processing and is not a complete or maintained
   implementation.  A complete independent implementation and common-
   vector execution are desirable before seeking working-group adoption.

14.  References

14.1.  Normative References

   [IEEE754]  IEEE, "IEEE Standard for Floating-Point Arithmetic",
              IEEE 754-2019, DOI 10.1109/IEEESTD.2019.8766229, 18 July
              2019, <https://ieeexplore.ieee.org/document/8766229>.

   [RFC6901]  Bryan, P., Ed., Zyp, K., and M. Nottingham, Ed.,
              "JavaScript Object Notation (JSON) Pointer", RFC 6901,
              DOI 10.17487/RFC6901, April 2013,
              <https://www.rfc-editor.org/rfc/rfc6901>.

   [RFC8259]  Bray, T., Ed., "The JavaScript Object Notation (JSON) Data
              Interchange Format", STD 90, RFC 8259,
              DOI 10.17487/RFC8259, December 2017,
              <https://www.rfc-editor.org/rfc/rfc8259>.

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/rfc/rfc2119>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/rfc/rfc8174>.

14.2.  Informative References

   [RFC6902]  Bryan, P., Ed. and M. Nottingham, Ed., "JavaScript Object
              Notation (JSON) Patch", RFC 6902, DOI 10.17487/RFC6902,
              April 2013, <https://www.rfc-editor.org/rfc/rfc6902>.

   [RFC7396]  Hoffman, P. and J. Snell, "JSON Merge Patch", RFC 7396,
              DOI 10.17487/RFC7396, October 2014,
              <https://www.rfc-editor.org/rfc/rfc7396>.

   [RFC7493]  Bray, T., Ed., "The I-JSON Message Format", RFC 7493,
              DOI 10.17487/RFC7493, March 2015,
              <https://www.rfc-editor.org/rfc/rfc7493>.

Jurkovikj                Expires 5 February 2027               [Page 26]
Internet-Draft            JSON Three-Way Merge               August 2026

   [RFC8785]  Rundgren, A., Jordan, B., and S. Erdtman, "JSON
              Canonicalization Scheme (JCS)", RFC 8785,
              DOI 10.17487/RFC8785, June 2020,
              <https://www.rfc-editor.org/rfc/rfc8785>.

   [RFC9110]  Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke,
              Ed., "HTTP Semantics", STD 97, RFC 9110,
              DOI 10.17487/RFC9110, June 2022,
              <https://www.rfc-editor.org/rfc/rfc9110>.

   [I-D.jurkovikj-httpapi-agentic-state]
              Jurkovikj, A., "HTTP Profile for Conditional Updates to
              Shared Resource State (Agentic State Transfer)", Work in
              Progress, Internet-Draft, draft-jurkovikj-httpapi-agentic-
              state-02, 27 July 2026,
              <https://datatracker.ietf.org/doc/html/draft-jurkovikj-
              httpapi-agentic-state-02>.

   [RFC7942]  Sheffer, Y. and A. Farrel, "Improving Awareness of Running
              Code: The Implementation Status Section", BCP 205,
              RFC 7942, DOI 10.17487/RFC7942, July 2016,
              <https://www.rfc-editor.org/rfc/rfc7942>.

Appendix A.  Merge Pseudocode

   The following pseudocode is explanatory.  Normative behavior is
   defined in the preceding sections.

   MERGE-VALUE(B, S, T, path):
     if EQUAL(S, T): return VALUE(S)
     if EQUAL(B, S): return VALUE(T)
     if EQUAL(B, T): return VALUE(S)

     if OBJECT(B) and OBJECT(S) and OBJECT(T):
       return MERGE-OBJECT(B, S, T, path)

     if KIND(B) == KIND(S) and KIND(S) == KIND(T):
       return CONFLICT(concurrent_value_change, path,
                       PRESENT(B), PRESENT(S), PRESENT(T))

     return CONFLICT(concurrent_type_change, path,
                     PRESENT(B), PRESENT(S), PRESENT(T))

   MERGE-MEMBER(b, s, t, path):
     if EQUAL-PRESENCE(s, t): return s
     if EQUAL-PRESENCE(b, s): return t
     if EQUAL-PRESENCE(b, t): return s

Jurkovikj                Expires 5 February 2027               [Page 27]
Internet-Draft            JSON Three-Way Merge               August 2026

     match (b, s, t):
       (PRESENT(B), PRESENT(S), PRESENT(T)):
         return MERGE-VALUE(B, S, T, path)
       (MISSING, PRESENT(S), PRESENT(T)):
         return CONFLICT(add_add, path, MISSING,
                         PRESENT(S), PRESENT(T))
       (PRESENT(B), MISSING, PRESENT(T)):
         return CONFLICT(source_delete_target_edit, path,
                         PRESENT(B), MISSING, PRESENT(T))
       (PRESENT(B), PRESENT(S), MISSING):
         return CONFLICT(source_edit_target_delete, path,
                         PRESENT(B), PRESENT(S), MISSING)

   MERGE-OBJECT(B, S, T, path):
     provisional = empty object
     conflicts = empty sequence
     for key in SORT-UTF16(UNION-OF-NAMES(B, S, T)):
       child = MERGE-MEMBER(PRESENCE(B, key),
                            PRESENCE(S, key),
                            PRESENCE(T, key),
                            APPEND-POINTER(path, key))
       if child is conflict collection:
         append child conflicts
       else if child is present:
         provisional[key] = child.value

     if conflicts is not empty:
       discard provisional
       return SORT-BY-POINTER(conflicts)
     return provisional

Appendix B.  Conformance Vectors

   The inputs in this appendix are JSON texts.  Expected merged values
   and conflict presence values are compared after normalization, so
   insignificant whitespace, object member order, and equivalent
   binary64 number spelling do not alter a vector result.

Jurkovikj                Expires 5 February 2027               [Page 28]
Internet-Draft            JSON Three-Way Merge               August 2026

   A machine-readable companion suite maintained with the draft source
   is named draft-jurkovikj-json-three-way-merge-00-vectors.json and
   uses schema identifier draft-jurkovikj-json-three-way-merge-00-
   vectors-v1.  Its base, source, and target members are outer JSON
   strings whose decoded contents are the exact UTF-8 input text
   supplied to the processor.  This permits malformed JSON text, a
   leading byte order mark, and an escaped lone surrogate to remain
   expressible inside a valid suite file.  A truly invalid UTF-8 octet
   sequence requires a separate byte-oriented suite.  Each expected
   object uses the generic result-form names defined by this document;
   comparisons of merged and conflict values occur after normalization.

   Concrete resource ceilings are implementation policy, so this
   appendix does not assign one universal N/N+1 resource-limit vector.
   Policy-specific suites can add boundary vectors bound to their
   resource-policy identifier and exact ceilings.

B.1.  Successful Merge Vectors

   *  no-change: base {"x":1}; source {"x":1.0}; target {"x":1e0};
      merged {"x":1}.

   *  source-only: base {"x":1}; source {"x":2}; target {"x":1}; merged
      {"x":2}.

   *  target-only: base {"x":1}; source {"x":1}; target {"x":2}; merged
      {"x":2}.

   *  agreement: base {"x":1}; source {"x":2}; target {"x":2.0}; merged
      {"x":2}.

   *  independent-members: base {"a":0,"b":0}; source {"a":1,"b":0};
      target {"a":0,"b":2}; merged {"a":1,"b":2}.

   *  source-delete: base {"x":1}; source {}; target {"x":1}; merged {}.

   *  target-delete: base {"x":1}; source {"x":1}; target {}; merged {}.

   *  both-delete: base {"x":1}; source {}; target {}; merged {}.

   *  one-sided-add-null: base {}; source {"x":null}; target {}; merged
      {"x":null}.

   *  target-only-add: base {}; source {}; target {"x":1}; merged
      {"x":1}.

   *  agreed-add: base {}; source {"x":1}; target {"x":1.0}; merged
      {"x":1}.

Jurkovikj                Expires 5 February 2027               [Page 29]
Internet-Draft            JSON Three-Way Merge               August 2026

   *  agreed-type-change: base 1; source {"x":2}; target {"x":2}; merged
      {"x":2}.

   *  one-sided-type-change: base 1; source 1.0; target {"x":2}; merged
      {"x":2}.

   *  large-integer-normalization: base 9007199254740992; source
      9007199254740993; target 3; merged 3.

   *  negative-zero-normalization: base {"x":-0}; source {"x":0.0};
      target {"x":-0.0}; merged {"x":0}. A serialized merged result MUST
      NOT contain negative zero.

   *  subnormal-equality: base 5e-324; source 4.9e-324; target 1; merged
      1 because the first two tokens convert to the same binary64 value.

B.2.  Conflict Vectors

   Each expected conflict below is the sole conflict unless an ordered
   path list is shown.

   *  scalar-divergence: base 1; source 2; target 3; kind
      concurrent_value_change; path "".

   *  type-divergence: base 1; source "source"; target {"x":2}; kind
      concurrent_type_change; path "".

   *  add-add: base {}; source {"x":1}; target {"x":2}; kind add_add;
      path /x.

   *  delete-edit: base {"x":1}; source {}; target {"x":2}; kind
      source_delete_target_edit; path /x.

   *  edit-delete: base {"x":1}; source {"x":2}; target {}; kind
      source_edit_target_delete; path /x.

   *  divergent-added-objects: base {}; source {"x":{"a":1}}; target
      {"x":{"b":2}}; kind add_add; path /x.

   *  null-versus-delete: base {"x":1}; source {"x":null}; target {};
      kind source_edit_target_delete; path /x.

   *  add-add-null: base {}; source {"x":null}; target {"x":1}; kind
      add_add; path /x.

   *  nested-type-conflict: base {"a":{"b":1}}; source
      {"a":{"b":"source"}}; target {"a":{"b":true}}; kind
      concurrent_type_change; path /a/b.

Jurkovikj                Expires 5 February 2027               [Page 30]
Internet-Draft            JSON Three-Way Merge               August 2026

   The decoded-token-sequence-order vector distinguishes decoded token-
   sequence ordering from rendered-pointer-string ordering:

   {
     "base":   {"a":{"b":0},"a!b":0},
     "source": {"a":{"b":1},"a!b":1},
     "target": {"a":{"b":2},"a!b":2},
     "expected_status": "conflict",
     "expected_paths": ["/a/b","/a!b"]
   }

   The first decoded token a is a prefix of a!b, so /a/b sorts first.
   Comparing the rendered pointer strings would incorrectly put /a!b
   first because ! precedes /.

   The empty-member-name-order vector uses base {"":0,"a":0}, source
   {"":1,"a":1}, and target {"":2,"a":2}. Its expected conflict paths
   are ["/","/a"]; the first pointer contains one empty decoded token.

   The escaped-path-order vector checks escaped paths and deterministic
   ordering:

   {
     "base":   {"z":0,"a/b":0,"m~n":0},
     "source": {"z":1,"a/b":1,"m~n":1},
     "target": {"z":2,"a/b":2,"m~n":2},
     "expected_status": "conflict",
     "expected_paths": ["/a~1b","/m~0n","/z"]
   }

   All three conflicts have kind concurrent_value_change.

   The utf16-path-order vector makes the UTF-16 ordering rule visible
   when a supplementary character and a Basic Multilingual Plane
   character are compared:

   {
     "base":   {"\ue000":0,"\ud83d\ude00":0},
     "source": {"\ue000":1,"\ud83d\ude00":1},
     "target": {"\ue000":2,"\ud83d\ude00":2},
     "expected_status": "conflict",
     "expected_paths": ["/\ud83d\ude00","/\ue000"]
   }

   The grinning-face character sorts first because its leading UTF-16
   code unit is 0xD83D, which is less than 0xE000.

Jurkovikj                Expires 5 February 2027               [Page 31]
Internet-Draft            JSON Three-Way Merge               August 2026

B.3.  Unsupported-Array Vectors

   *  root-array: base []; source {}; target {}; side base; path "".

   *  nested-array: base {"items":null}; source {"items":[1]}; target
      {"items":null}; side source; path /items.

   *  equal-arrays: all three inputs are {"items":[1]}; side base; path
      /items.

   *  least-array-path: base {"z":[],"a":{"items":[]}}; source {};
      target {}; side base; path /a/items.

B.4.  Invalid-Input Precedence Vectors

   *  invalid-base-first: base {broken; source []; target {}; status
      invalid_input; side base; code invalid_json; path "".

   *  source-array-before-target: base {}; source []; target {broken;
      status unsupported; side source; code arrays_not_supported.

   *  decoded-duplicate: base {"a":1,"\u0061":2}; source {}; target {};
      status invalid_input; side base; code duplicate_object_name.

   *  number-overflow: base {}; source 1e309; target {}; status
      invalid_input; side source; code number_out_of_binary64_range.

   *  nested-decoded-duplicate-path: base {"outer":{"a":1,"\u0061":2}};
      source {}; target {}; status invalid_input; side base; code
      duplicate_object_name; path /outer.

   *  nested-number-overflow-path: base {}; source
      {"outer":{"n":1e309}}; target {}; status invalid_input; side
      source; code number_out_of_binary64_range; path /outer/n.

   *  leading-bom: base is U+FEFF followed by {}; source {}; target {};
      status invalid_input; side base; code invalid_json; path "".

   *  lone-surrogate-escape: base "\uD800"; source {}; target {}; status
      invalid_input; side base; code invalid_json; path "".

Appendix C.  Design Rationale

Jurkovikj                Expires 5 February 2027               [Page 32]
Internet-Draft            JSON Three-Way Merge               August 2026

C.1.  Why Arrays Fail Closed

   An array can represent a sequence, set, keyed collection, priority
   list, ordered log, tuple, or application-specific structure.  A
   generic processor cannot infer whether index identity, element
   identity, order, or movement is semantically significant.  Returning
   a typed unsupported result avoids standardizing an unsafe guess and
   leaves room for separately identified array profiles.

C.2.  Why Divergent Additions Do Not Recurse

   When a member is missing from base, there is no shared object at that
   path.  Recursively merging two divergent added objects against an
   invented empty base would turn independent creation into implicit
   agreement.  Reporting add_add keeps that decision visible to
   application policy.

C.3.  Why Merge Is Value-Deterministic Rather Than Byte-Canonical

   JSON permits insignificant whitespace, multiple number spellings, and
   unordered object members.  This document fixes normalized values and
   diagnostic sequence order, which are the interoperability
   requirements for merge.  It leaves byte canonicalization and content
   identity to specifications designed for those purposes.

C.4.  Why UTF-16 Code-Unit Ordering

   The comparator for one decoded pointer token aligns with the
   property-name comparator defined by JCS.  It also maps directly to
   string representations in ECMAScript, Java, and .NET.  Runtimes that
   natively compare Unicode scalar values or UTF-8 bytes need an
   explicit UTF-16 conversion or equivalent code-unit iterator.  This
   comparator alignment permits reuse of tested sorting routines, but
   complete paths are ordered as token sequences by this document and
   JCS remains unnecessary for merge processing.

Author's Address

   Antun Jurkovikj
   North Macedonia
   Email: antunjurkovic@gmail.com

Jurkovikj                Expires 5 February 2027               [Page 33]