Skip to content

M2-05: Implement quote collection and deterministic selection #26

Closed

Description

@pillowtalk-Qy
Type: Moss Adapter Feature
Priority: P0 / trust boundary
Initial status: Blocked
Owner: Moss Integration
Depends on: M2-04
Blocks: M2-06, M2-12
Writable: quote*, selection*, asset-catalog*, tests/fixtures, rebased export

唯一目标

并行获取协议 Quote,保留每个成功/失败,并按公开确定性规则选择一个候选协议。

安全语义

Quote 只是协议选择上下文,不是 Capability/execution evidence,也没有与后续 Capability 的密码学绑定。实际安全判断仍由 Capability、simulation 和 alignment 完成。

选择规则

Promise.allSettled
per protocol timeout = 8 seconds
eligible = success + asset direction match + amount basis match + allowlist
winner = highest normalized amountOut by BigInt
tie = protocolId byte-stable ascending
no eligible quote = explicit acquisition failure

验收

[ ] asset identity 用 address/native,不依赖 symbol
[ ] token amounts 全程 integer string/BigInt
[ ] raw/normalized quote 分开
[ ] decimals source 和无损转换记录
[ ] acquiredAt、observable block window 记录
[ ] selected quote 用 RFC8785/SHA-256 digest
[ ] quote-to-action elapsed 可记录
[ ] allowlist enforced
[ ] 单协议失败不抹掉另一协议成功
[ ] 文案为 deterministic candidate selection,不称 best trade
[ ] 不声称 Capability 来自 exact Quote snapshot

测试矩阵

both success/first wins; both success/second wins; tie;
one timeout; malformed; direction mismatch; amount mismatch;
outside allowlist; all fail; uint256-scale value

验证

pnpm vitest run packages/moss-adapter/test/quote.test.ts
pnpm vitest run packages/moss-adapter/test/selection.test.ts
pnpm --filter @moss-mini-demo/moss-adapter build
pnpm check

Codex 指令

严格实现 deterministic selection 和全失败保留。禁止 Number 处理金额,
禁止把 quote 称为 execution binding。输出每个测试案例的 expected selected/failure。

Activity

  1. self-assigned this
    on Aug 6, 2026
  2. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    DEPENDENCY_GATE_CLEARED / PLANNING_SLOT_ASSIGNED

    Issue #25 has been squash-merged through PR #72 into main@a0f3a3e8605812c9f0997059b4a30ea7a736022c.

    Merged-main quality-gate is SUCCESS: https://github.com/Moss-Mini-Demo/moss-mini-demo/actions/runs/31070657110/job/92517725619

    Issue #26 is assigned to pillowtalk-Qy and moved to Project In Progress solely to begin planning from this exact main baseline. The first required artifact is a closed Work Plan covering writable paths, public contracts, trust boundaries, tests, non-goals, and verification.

    No implementation branch, worktree, commit or PR is authorized before Maintainer SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED. In Progress is not QA PASS, Review, READY_TO_MERGE, Merge Gate, or merge authorization. Do not absorb #27 or later dependent behavior into this plan.

    The single Builder slot is #26. Issue #23 remains Ready/unassigned and #27 plus later dependent issues remain Blocked.

  3. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    Final Implementation Work Plan - M2-05 Quote Collection and Deterministic Selection - Awaiting Maintainer Scope Confirmation

    0. Status, authority, and exact baseline

    Source:

    This comment is a planning artifact only. It requests a new Maintainer Scope
    Gate. It is not implementation authorization, QA PASS, Review, Merge Gate, or
    merge authorization.

    Exact planning baseline:

    No branch, worktree, repository file, dependency, lockfile, commit, push, or PR
    is created by this comment. If origin/main changes before branch creation,
    Builder stops, rescans the new main, and requests a revised Scope Gate.

    1. Goal and trust boundary

    Add one server-only, deterministic quote collection and candidate-selection
    operation to the existing @moss-mini-demo/moss-adapter public entry.

    The operation:

    1. validates one chain-143 comparison request and its Maintainer-approved asset
      metadata;
    2. requests candidate protocol quotes concurrently;
    3. records one terminal outcome for every candidate protocol;
    4. preserves every acquired RawQuote separately from Mini-Demo-derived
      normalization;
    5. selects only among eligible normalized outcomes using the disclosed rule;
    6. returns either one selected candidate or an explicit no-eligible-candidate
      result.

    A Quote is protocol-selection context only. It is not Capability, simulation,
    execution evidence, a best-trade claim, a safety conclusion, authorization, or
    a cryptographic binding to any later Capability.

    The only approved wording/identifier for the selection rule is
    DETERMINISTIC_CANDIDATE_SELECTION_V0_1. Public output and tests must not use
    “best”, “optimal”, “safe”, “approved”, “executable”, or equivalent language.

    2. Exact writable paths

    Implementation may modify exactly these 16 repository paths and no others:

    1. pnpm-lock.yaml
    2. packages/moss-adapter/package.json
    3. packages/moss-adapter/src/types.ts
    4. packages/moss-adapter/src/source-bindings.ts
    5. packages/moss-adapter/src/production.ts
    6. packages/moss-adapter/src/fake.ts
    7. packages/moss-adapter/src/asset-catalog.ts (new)
    8. packages/moss-adapter/src/quote.ts (new)
    9. packages/moss-adapter/src/selection.ts (new)
    10. packages/moss-adapter/src/index.ts
    11. packages/moss-adapter/test/contracts.test.ts
    12. packages/moss-adapter/test/production.test.ts
    13. packages/moss-adapter/test/fake.test.ts
    14. packages/moss-adapter/test/quote.test.ts (new)
    15. packages/moss-adapter/test/selection.test.ts (new)
    16. packages/moss-adapter/test/public-entry.smoke.mjs

    Explicit metadata decisions:

    • packages/moss-adapter/src/types.ts: modified for the exact public and
      internal contracts in this plan.
    • packages/moss-adapter/src/index.ts: modified only to add the exact exports
      listed in Section 3.
    • packages/moss-adapter/package.json: modified only for the two exact
      dependencies in Section 4; the package name and sole . export stay
      unchanged.
    • pnpm-lock.yaml: modified only for the moss-adapter importer and
      canonicalize@3.0.0.
    • root package.json: no change.
    • pnpm-workspace.yaml: no change.
    • packages/moss-adapter/src/errors.ts: no change; per-candidate acquisition
      failures are data outcomes, not new thrown MossAdapterError variants.
    • adapter tsconfig files: no change.
    • no fixture file is added; tests use inline synthetic data.

    Any need for a seventeenth path, a report-schema source edit, a root script
    change, a workflow edit, or another dependency is a stop condition requiring a
    new Scope Gate.

    3. Sole public entry and exact new API

    The package keeps exactly one public entry:

    @moss-mini-demo/moss-adapter

    There is no subpath export and no import from a private src path.

    Existing runtime and type exports remain. The only new runtime export is:

    collectAndSelectQuotesV0_1

    Exact runtime signature:

    function collectAndSelectQuotesV0_1(
      port: MossPort,
      request: QuoteCollectionRequestV0_1,
    ): Promise<QuoteCollectionResultV0_1>;

    The only new type exports are:

    • QuoteRequestOptionsV0_1;
    • QuoteAssetV0_1;
    • AssetCatalogV0_1;
    • AssetCatalogEntryV0_1;
    • DecimalsRecordV0_1;
    • QuoteCollectionRequestV0_1;
    • QuoteRawProjectionV0_1;
    • NormalizedQuoteV0_1;
    • QuoteCandidateFailureCodeV0_1;
    • QuoteCandidateOutcomeV0_1;
    • SelectedQuoteDigestV0_1;
    • QuoteCollectionResultV0_1.

    No public canonicalization helper, hash helper, comparator, asset lookup helper,
    timeout helper, second port, runtime Schema, or source-binding bypass is added.

    4. Exact dependency and supply-chain decision

    packages/moss-adapter/package.json adds exactly:

    • @moss-mini-demo/report-schema: workspace:*;
    • canonicalize: 3.0.0.

    The existing server-only: 0.0.1 dependency remains unchanged.

    The adapter consumes only the report-schema public package entry and reuses its
    accepted AssetSchema, EvmAddressSchema, ProtocolIdSchema,
    PositiveAmountSchema, and UnsignedAmountSchema. It does not import
    report-schema private source paths and does not copy those validators.

    RFC 8785 canonicalization uses the pinned canonicalize@3.0.0 implementation:

    The specification source is RFC 8785:
    https://www.rfc-editor.org/info/rfc8785

    SHA-256 uses Node 22's built-in node:crypto over the canonical UTF-8 bytes.
    No second hashing package is added. No hand-written or approximate JSON
    canonicalizer is permitted.

    pnpm-lock.yaml may add only the two adapter importer entries and the exact
    canonicalize@3.0.0 package record. It must not refresh unrelated versions.
    pnpm-workspace.yaml and the sole 'sharp@0.34.5': true build permission
    remain byte-for-byte unchanged. No second build-script permission may be
    created or auto-added.

    5. Exact request contract

    QuoteCollectionRequestV0_1 is an adapter-owned, reject-only input:

    chainId: 143
    candidateProtocols: non-empty, unique ProtocolId[]
    allowedProtocols: non-empty, unique ProtocolId[]
    quoteInput:
      method: existing bounded method identifier
      account: canonical EIP-55 address
      params: JSON object containing at least:
        inputAsset: QuoteAssetV0_1
        outputAsset: QuoteAssetV0_1
        amountIn: canonical positive integer string
    inputAsset: QuoteAssetV0_1
    outputAsset: QuoteAssetV0_1
    amountIn: canonical positive integer string
    assetCatalog: AssetCatalogV0_1
    

    The duplicated comparison basis is intentional. Before any binding call,
    quoteInput.params.inputAsset, outputAsset, and amountIn must exactly
    match the top-level request values. Additional protocol-neutral JSON fields in
    quoteInput.params are preserved and passed through, but they cannot replace
    or alter the three required comparison fields.

    The request is cloned once inside a sanitized input boundary. Getter, Proxy,
    revoked Proxy, cyclic, non-JSON, duplicate protocol, invalid address, invalid
    amount, non-143 chain, or inconsistent basis input throws a fresh
    MossAdapterError with code: INVALID_INPUT, operation: quote, no
    cause, no original message, and zero Quote delegations.

    Amounts are canonical positive base-10 integer strings and must be at most
    2^256 - 1. JavaScript Number, floats, exponent notation, signs,
    whitespace, separators, leading zeroes, zero, and values above uint256 are
    rejected. Conversion for comparison uses only BigInt.

    6. Asset identity and catalog contract

    QuoteAssetV0_1 is exactly the accepted report-schema asset union:

    { kind: "NATIVE" }
    |
    { kind: "ERC20", address: canonical EIP-55 EvmAddress }
    

    Rules:

    • chain id is the literal 143;
    • native asset has no address;
    • ERC-20 identity is the exact canonical EIP-55 address;
    • zero/sentinel addresses are rejected;
    • symbol, name, icon, ticker, display label, and decimals never establish asset
      identity;
    • no normalization, lowercasing, checksum repair, symbol lookup, or
      native-address substitution occurs.

    AssetCatalogV0_1 is supplied as Maintainer-approved configuration data; #26
    does not commit real token addresses. It contains:

    chainId: 143
    entries: unique AssetCatalogEntryV0_1[]
    
    AssetCatalogEntryV0_1:
      asset: QuoteAssetV0_1
      decimals:
        { status: "KNOWN",
          value: integer 0..255,
          authority: "MAINTAINER_APPROVED",
          sourceVersion: non-empty bounded ASCII identifier,
          provenance: "CONFIGURATION",
          sourceReference: non-empty bounded ASCII identifier }
        |
        { status: "UNKNOWN" }
    

    Duplicate asset identities invalidate the catalog. An absent asset produces
    UNKNOWN_ASSET; an explicit UNKNOWN decimals record produces
    UNKNOWN_DECIMALS. Both fail closed before that candidate can become
    eligible. No default such as native=18 is inferred. No RPC, token contract,
    symbol registry, or display metadata is consulted.

    7. Exact raw Quote projection and preservation

    Current RawQuote.mossOriginal.value remains the complete opaque
    JsonValue. #26 does not rewrite that source value or weaken its identity
    boundary.

    For normalization, the collector makes one adapter-owned structured clone of
    the acquired raw value and extracts this required projection:

    QuoteRawProjectionV0_1:
      chainId: 143
      inputAsset: QuoteAssetV0_1
      outputAsset: QuoteAssetV0_1
      amountIn: canonical positive uint256 integer string
      amountOut: canonical positive uint256 integer string
      observableBlockWindow:
        fromBlock: canonical unsigned integer string
        toBlock: canonical unsigned integer string
    

    The raw top-level object may contain additional JSON fields. They remain in the
    preserved raw source and owned snapshot but do not silently become normalized
    fields.

    Extraction reads exact named fields; it never reads a symbol or formatted
    amount. Missing fields, getter/clone failures, nulls, JavaScript-number amounts,
    float/exponent/negative/zero/leading-zero amounts, non-143 chain, invalid asset
    shape, malformed block strings, or fromBlock > toBlock produce the fixed
    per-candidate code MALFORMED_QUOTE.

    Every candidate outcome is retained in byte-stable protocol order:

    • acquisition success and eligible: original RawQuote by identity,
      deep-frozen adapter-owned raw snapshot, normalized quote, and timing evidence;
    • acquisition success but ineligible/malformed: original RawQuote by
      identity when it can be retained, owned snapshot when created, and the fixed
      structured failure;
    • timeout/acquisition failure: structured safe failure with no raw exception,
      message, URL, header, account, params, key, or cause.

    The collector never freezes, mutates, reorders, or replaces the source-owned
    RawQuote. Derived snapshots and normalized records are separately labelled
    Mini-Demo-derived data.

    8. Closed per-candidate failure vocabulary

    QuoteCandidateFailureCodeV0_1 has exactly:

    PROTOCOL_NOT_ALLOWED
    QUOTE_TIMEOUT
    QUOTE_ACQUISITION_FAILED
    MALFORMED_QUOTE
    ASSET_DIRECTION_MISMATCH
    AMOUNT_BASIS_MISMATCH
    UNKNOWN_ASSET
    UNKNOWN_DECIMALS
    

    Rules:

    • an existing safe MossAdapterError.code may be retained only as an optional
      bounded sourceErrorCode;
    • raw exceptions and their messages are never retained;
    • every candidate has exactly one terminal success or failure outcome;
    • one protocol failure never erases another protocol success;
    • a top-level no-selection result is
      { status: "NOT_SELECTED", code: "NO_ELIGIBLE_QUOTE", method: "DETERMINISTIC_CANDIDATE_SELECTION_V0_1" };
    • all candidate outcomes remain present when all fail.

    9. AbortSignal interface expansion and exact timeout behavior

    This plan explicitly requests a new Scope Gate for a backward-compatible
    expansion of the #25 port:

    type QuoteRequestOptionsV0_1 = Readonly<{ signal?: AbortSignal }>;
    
    MossPort.quote(
      protocolId: string,
      input: QuoteInput,
      options?: QuoteRequestOptionsV0_1,
    ): Promise<RawQuote>;
    
    MossSourceBindings.quote(
      protocolId: string,
      input: QuoteInput,
      options?: QuoteRequestOptionsV0_1,
    ): Promise<{ operation: MossLoadedOperation; quote: JsonValue }>;

    ProductionMossPort, FakeMossPort, and createBoundMossPort forward
    the exact signal. No other port method changes.

    The collector:

    1. creates one AbortController per allowed candidate;
    2. starts every allowed Quote request before awaiting settlement;
    3. fixes the deadline at exactly 8,000 milliseconds;
    4. aborts with a stable non-sensitive timeout reason at the deadline;
    5. records QUOTE_TIMEOUT as that candidate's terminal outcome;
    6. attaches fulfillment and rejection handlers before racing the deadline so
      every late settlement is consumed;
    7. discards any late fulfillment/rejection after the timeout outcome is
      committed;
    8. freezes the returned derived outcome set so a late promise cannot mutate,
      append, replace, or influence selection.

    The aggregate uses Promise.allSettled over the complete candidate task list.
    A bare Promise.race without terminal late-result handling is forbidden.

    A candidate outside allowedProtocols receives PROTOCOL_NOT_ALLOWED
    without invoking port.quote.

    The Quote port contract for #26 is read-only and must honor AbortSignal.
    Tests prove signal propagation, abort observation, late fulfillment/rejection
    drainage, no unhandled rejection, no outcome mutation, and no selection
    pollution. If a production binding cannot honor cancellation or cannot prove
    its Quote call has no externally mutating side effect, implementation stops and
    returns to Maintainer; #26 must not claim cancellation of an uncooperative
    source.

    10. Eligibility and deterministic selection order

    Global request/catalog validation happens before any Quote delegation. For each
    candidate, terminal classification uses this exact order:

    1. canonical candidate identity and duplicate check;
    2. allowlist membership; otherwise PROTOCOL_NOT_ALLOWED and no call;
    3. Quote acquisition within 8 seconds; otherwise QUOTE_TIMEOUT or
      QUOTE_ACQUISITION_FAILED;
    4. owned raw snapshot and complete projection validation; otherwise
      MALFORMED_QUOTE;
    5. exact input/output asset identity match; otherwise
      ASSET_DIRECTION_MISMATCH;
    6. exact amountIn string equality with the request basis; otherwise
      AMOUNT_BASIS_MISMATCH;
    7. catalog membership for both assets; otherwise UNKNOWN_ASSET;
    8. KNOWN decimals with approved source metadata; otherwise
      UNKNOWN_DECIMALS;
    9. lossless BigInt normalization; only then is the candidate eligible.

    Because every eligible candidate must have the exact same output asset
    identity, all output amounts use the same authoritative decimals. Lossless
    normalization therefore does not divide, round, format, or convert display
    units:

    normalizedAmountOut = canonical amountOut base-unit string
    comparisonAmount = BigInt(normalizedAmountOut)
    comparisonDecimals = approved output-asset decimals
    

    Input/output decimals records and their authority/version/provenance are
    retained in NormalizedQuoteV0_1. Conflicting decimals for one asset make the
    catalog invalid. Cross-asset price conversion, USD normalization, symbol-based
    comparison, rounding, and Number arithmetic are forbidden.

    Winner:

    1. highest comparisonAmount wins;
    2. exact amount tie uses protocolId UTF-8 byte lexicographic ascending;
    3. comparison uses TextEncoder byte arrays and numeric byte comparison;
    4. localeCompare, locale collation, case folding, Unicode normalization,
      settlement order, candidate input order, wall time, and random values are
      forbidden.

    ProtocolId is already lowercase ASCII, so UTF-8 and ASCII bytes coincide.
    Duplicate protocol IDs are rejected before collection.

    All returned candidate outcomes are sorted by the same byte comparator,
    independent of settlement order.

    11. RFC 8785 selected-quote digest

    The selected candidate contains:

    SelectedQuoteDigestV0_1:
      algorithm: "RFC8785-SHA256"
      value: "sha256:" + exactly 64 lowercase hexadecimal characters
      payload: deep-frozen canonical-payload source object
    

    The exact canonical payload is:

    {
      schemaVersion: "moss-mini-demo/selected-quote-digest/0.1",
      chainId: 143,
      protocolId,
      method,
      account,
      inputAsset,
      outputAsset,
      inputAmount,
      outputAmount,
      inputDecimals: {
        value, authority, sourceVersion, provenance, sourceReference
      },
      outputDecimals: {
        value, authority, sourceVersion, provenance, sourceReference
      },
      observableBlockWindow: { fromBlock, toBlock },
      mossSource: {
        provenance,
        upstreamCommit,
        integrationCommit: string | null,
        patchsetDigest: string | null
      },
      rawQuote: adapter-owned raw snapshot
    }
    

    The payload is passed to canonicalize@3.0.0; the returned RFC 8785 text is
    UTF-8 encoded and hashed once with Node createHash("sha256"). A
    canonicalization rejection, undefined result, invalid Unicode, or non-I-JSON
    value is MALFORMED_QUOTE; no fallback serializer is allowed.

    The digest is a reproducibility fingerprint for the selected Quote record. It
    is not a signature, Capability digest, integrity proof, execution binding, or
    evidence that a later Capability came from this exact Quote.

    Tests include a published RFC 8785 canonicalization vector and a fixed SHA-256
    golden vector. They also recompute the selected payload digest independently
    through the pinned package. No approximate property sorter is tested or used.

    12. Time and block evidence

    For every acquired Quote:

    • acquiredAt is captured when the Quote settles, using
      new Date(Date.now()).toISOString(), and must be canonical UTC with
      milliseconds;
    • acquiredMonotonicNs is the canonical unsigned decimal string returned by
      process.hrtime.bigint();
    • the clock identifier is exactly NODE_PROCESS_HRTIME_V0_1;
    • the required observable block window comes from the validated raw projection
      and uses unsigned decimal strings with fromBlock <= toBlock.

    Missing or malformed block-window evidence produces MALFORMED_QUOTE; it is
    never replaced with the current time or an inferred block.

    acquiredAt, acquiredMonotonicNs, deadlines, and settlement order are
    excluded from the RFC 8785 digest and from the selection subobject. They
    cannot change eligibility, ranking, selected protocol, or digest.

    #26 does not call action. It only records the monotonic acquisition token
    needed for a future authorized orchestrator to compute quote-to-action elapsed
    time from the same process clock. A process/clock discontinuity must be treated
    as unprovable by that future scope; wall-clock subtraction is not a substitute.

    13. Required tests

    All data is inline and synthetic. No real protocol, token, address, Quote, RPC,
    wallet, key, Receipt, simulation, or chain record is used.

    packages/moss-adapter/test/quote.test.ts covers:

    • exact raw projection extraction;
    • raw identity preservation and separate frozen owned snapshot;
    • request/raw input and output asset validation by tagged identity, never
      symbol;
    • exact amount-in basis matching;
    • integer-string/BigInt-only behavior;
    • zero, leading-zero, float, exponent, Number, negative, missing, and
      over-uint256 rejection;
    • missing/invalid/reversed observable block window;
    • malformed Quote fixed to MALFORMED_QUOTE;
    • unknown asset and unknown decimals fail closed;
    • decimals authority, version, provenance, and reference retention;
    • request/getter/Proxy errors sanitized before delegation;
    • original request and RawQuote are not mutated;
    • acquired time shape and monotonic token shape;
    • exact RFC 8785 canonicalization vector and SHA-256 golden digest;
    • different wall/monotonic times produce the same normalized selection core and
      selected digest.

    packages/moss-adapter/test/selection.test.ts contains the complete matrix
    with expected terminal outcome and selection:

    1. both success / first has higher output -> first selected;
    2. both success / second has higher output -> second selected;
    3. equal output -> byte-ascending protocol selected;
    4. reversed candidate order and reversed settlement order -> identical
      selection, digest, and byte-ordered outcome identities;
    5. one timeout plus one success -> success selected and timeout retained;
    6. late fulfillment after timeout -> timeout remains terminal and selection is
      unchanged;
    7. late rejection after timeout -> drained, no unhandled rejection or mutation;
    8. one malformed plus one success -> success selected and malformed retained;
    9. asset direction mismatch -> ineligible with exact code;
    10. amount basis mismatch -> ineligible with exact code;
    11. outside allowlist -> no delegation and exact code;
    12. all acquisition failures -> all failures retained and
      NO_ELIGIBLE_QUOTE;
    13. mixed failure classes with no eligible quote -> every class retained;
    14. uint256 maximum values -> accepted and compared with BigInt;
    15. unknown asset -> no eligible candidate;
    16. unknown decimals -> no eligible candidate;
    17. single protocol failure never removes another success;
    18. repeated execution with controlled evidence -> identical deterministic
      selection and digest and no input mutation.

    Cancellation regressions assert the exact AbortSignal reaches both
    Production and Fake bindings. Existing five-method, source-sanitization,
    input-sanitization, identity, server-only, and Fake determinism tests remain
    green.

    The public-entry smoke must expect exactly one additional runtime export,
    collectAndSelectQuotesV0_1, and must continue proving normal browser-like
    import failure through the existing server-only guard.

    14. Implementation sequence after authorization

    1. Re-fetch and verify exact main. Stop if it is not
      a0f3a3e8605812c9f0997059b4a30ea7a736022c.
    2. Create only the proposed branch and isolated worktree.
    3. Add the exact report-schema workspace dependency and
      canonicalize@3.0.0; update only the allowed lockfile records.
    4. Add the versioned type contracts and AbortSignal forwarding while preserving
      the five-method MossPort.
    5. Implement reject-only asset catalog validation.
    6. Implement raw Quote projection, source/derived separation, timeout/abort and
      complete per-protocol outcome collection with Promise.allSettled.
    7. Implement byte-stable selection and the fixed RFC8785/SHA-256 digest.
    8. Add the two focused test files and update only the listed regressions/smoke.
    9. Audit exact paths, dependency integrity, late-promise behavior, public
      exports, and prohibited wording.
    10. Run clean-checkout verification, push one Draft PR with Closes #26,
      wait for exact-head CI, and hand the frozen head to independent Pre-PR QA.

    No implementation starts before an explicit
    SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED comment accepts this plan,
    including the optional AbortSignal interface expansion and the two dependencies.

    15. Verification and CI acceptance

    Use Node 22.23.1 and pnpm 11.16.0. Final verification runs from a fresh
    isolated checkout with no pre-existing dist or .next:

    node --version
    pnpm --version
    pnpm install --frozen-lockfile
    pnpm --filter @moss-mini-demo/moss-adapter build
    pnpm --filter @moss-mini-demo/moss-adapter typecheck
    pnpm vitest run packages/moss-adapter/test/quote.test.ts
    pnpm vitest run packages/moss-adapter/test/selection.test.ts
    pnpm vitest run packages/moss-adapter/test
    pnpm test:package-import
    pnpm build
    pnpm typecheck
    pnpm test
    pnpm check
    pnpm test:web:bundle
    pnpm test:web:production
    git diff --check origin/main...HEAD
    git diff --name-only origin/main...HEAD
    git status --short

    Acceptance requires:

    • exact 16-path diff;
    • frozen install without historic artifacts;
    • all focused, adapter, package-import, full, browser-bundle, and production
      tests pass;
    • canonicalization golden vectors and SHA-256 recomputation pass;
    • no Number amount path and no locale-sensitive comparator;
    • all candidate failures/raw successes retained;
    • late promises cannot mutate a returned result or influence selection;
    • the only workflow job remains the unchanged quality-gate;
    • exact pushed Head quality-gate is SUCCESS;
    • PR remains Draft until independent QA.

    16. Explicit non-goals

    #26 does not implement:

    • Capability construction;
    • Capability digest or integrity verification;
    • simulation acquisition or mapping;
    • Receipt, Outcome, Warning, coverage, ordering, or state-continuity evidence;
    • Alignment;
    • Decision, STOP, or MANUAL_REVIEW;
    • report generation;
    • a cryptographic Quote-to-Capability binding;
    • action orchestration or quote-to-action elapsed calculation;
    • RPC, live-chain access, wallet, signing, transaction construction, or
      transaction submission;
    • protocol-specific Kuru/Pancake code;
    • real asset/protocol addresses or a production catalog;
    • symbol pricing, USD conversion, slippage optimization, routing, or a
      best-trade claim;
    • Web/API orchestration, UI, database, deployment, Clear402, M2-06: Implement capability construction, amount conversion and integrity #27, or any later
      M2 issue.

    No ADR, workflow, documentation, report-schema source, decision-engine,
    Fixture, Issue/Project state, branch protection, required check, permission, or
    main change is in scope.

    17. Risks, stop conditions, and rollback

    Primary risks:

    • expanding the M2-04: Create moss-adapter package and public ports #25 quote method without a new Scope Gate;
    • an uncooperative binding ignoring AbortSignal;
    • late promise mutation or unhandled rejection;
    • source/derived Quote conflation;
    • unapproved asset or decimals defaults;
    • Number precision loss;
    • locale-dependent ties;
    • incomplete RFC 8785 behavior;
    • digesting timestamps and making selection nondeterministic;
    • exposing raw source errors or secrets;
    • accidental claims that selection is best, safe, or bound to Capability.

    Stop and return to Maintainer if:

    • main differs before branch creation;
    • AbortSignal cannot be propagated and honored;
    • a source operation may mutate external state after timeout;
    • a real protocol/address/catalog decision is required;
    • a third dependency, build-script permission, public runtime export, root
      script, or seventeenth path is needed;
    • canonicalize package identity/integrity differs;
    • raw Quote cannot provide the fixed projection or block window;
    • digest reproducibility fails an RFC/golden vector;
    • any implementation would cross into Capability, simulation, Alignment,
      Decision, Web/API, RPC, wallet, signing, or transaction behavior.

    Rollback is one revert of the #26 implementation commit after merge, or removal
    of only the unmerged #26 branch/PR. It must not revert #25/#24, alter main
    directly, change the Moss gitlink, or modify protection/required checks.

    18. Requested Maintainer decision

    Please record one of:

    • SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED, explicitly accepting the
      exact 16 paths, the two dependencies, the optional AbortSignal expansion,
      the raw Quote projection, asset catalog contract, timeout behavior,
      selection law, digest payload, and time exclusion; or
    • SCOPE_CHANGES_REQUESTED, naming the exact path, dependency, API, evidence,
      or semantic decision that must change.

    Until that record exists, Builder status is:

    WORK_PLAN_SUBMITTED / AWAITING_MAINTAINER_SCOPE_CONFIRMATION

    No implementation branch, worktree, file change, commit, push, or PR is
    authorized.

  4. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    SCOPE_CHANGES_REQUESTED

    Reviewed Work Plan: #26 (comment)

    Reviewed baseline: main@a0f3a3e8605812c9f0997059b4a30ea7a736022c

    Maintainer Scope Gate result: CHANGES REQUESTED. Do not create the #26 implementation branch, worktree, commit or PR.

    Items that are already acceptable

    • the writable set is an explicit 16-path closed list;
    • the M2-04: Create moss-adapter package and public ports #25 five-method MossPort remains five methods, and the optional AbortSignal change to quote is disclosed rather than hidden;
    • integer amounts remain canonical strings/BigInt, with no Number conversion or rounding;
    • amount-basis equality, asset-direction checks, byte-stable protocol ordering, per-protocol terminal outcomes, late-settlement drainage, timing exclusion from selection/digest, and Quote-not-execution-evidence wording are directionally correct;
    • the selected-Quote digest is explicitly distinct from M2-06: Implement capability construction, amount conversion and integrity #27 Capability integrity and does not call action or absorb simulation/Decision behavior;
    • canonicalize@3.0.0 metadata was independently verified: Apache-2.0, zero runtime dependencies, built-in type declaration, no install/build lifecycle script, and the stated registry integrity matches.

    1. Asset-catalog authority is currently self-asserted

    assetCatalog is part of the same public, caller-controlled QuoteCollectionRequestV0_1, yet an entry may declare authority: "MAINTAINER_APPROVED" and provenance: "CONFIGURATION". Input cloning and shape validation cannot prove those claims. A caller can construct arbitrary decimals/source metadata and thereby make a candidate eligible under a false authority label.

    The revised plan must define a real trust boundary:

    • separate untrusted quote request data from server-owned catalog configuration, or remove the MAINTAINER_APPROVED claim and define the weaker caller-supplied semantics;
    • identify the catalog owner and exact injection/loading boundary;
    • define catalog-level identity/version/provenance and whether a digest or immutable source record is required;
    • give exact grammar and length for sourceVersion and sourceReference rather than “bounded ASCII”;
    • specify fail-closed behavior when trusted catalog configuration is absent, unknown, duplicated, stale or inconsistent;
    • make clear that synthetic test catalogs are not production/Maintainer-approved asset evidence.

    No real address needs to be added by #26, but arbitrary request data must not be able to self-certify authority.

    2. Public result and candidate unions are not exact enough

    The plan names the exported types but does not freeze the complete shape of QuoteCandidateOutcomeV0_1, the selected top-level result, normalized records, timing records, failure records or source-error retention. The prose leaves Builder decisions about discriminants, optional fields and field ownership.

    The revised plan must provide exact TypeScript-like discriminated shapes for:

    • acquired eligible success;
    • acquired but ineligible/malformed outcome;
    • timeout/acquisition failure;
    • selected and NOT_SELECTED top-level results;
    • exact placement of raw identity, owned snapshot, normalized data, timing evidence, failure code and optional sourceErrorCode;
    • exact immutability and candidate ordering rules.

    Each candidate must have exactly one terminal discriminant and one failure precedence outcome. No optional-field combination may imply contradictory success/failure states.

    3. Timeout must not overclaim source cancellation

    The collector-local 8,000 ms terminal timeout and late-promise drainage are sound, but the plan also says the production Quote binding “must honor” AbortSignal. #25 provides an injected structural binding and this repository has no real protocol binding whose cancellation behavior can be proven in #26. Signal forwarding is testable; actual source cancellation is not guaranteed by the TypeScript interface.

    Revise the contract to distinguish:

    • collector terminality: exactly when QUOTE_TIMEOUT becomes immutable;
    • signal propagation: exact abort reason/value and when abort() is called;
    • underlying-source cancellation: never claimed unless a concrete binding proves it;
    • an uncooperative source that ignores the signal and fulfills/rejects late: must be drained, must not mutate outcomes or selection, and must not produce an unhandled rejection;
    • external side effects: Quote is required to be read-only, but M2-05: Implement quote collection and deterministic selection #26 must not claim that timeout canceled an unverified source operation.

    Add explicit cooperative and signal-ignoring late-fulfillment/late-rejection matrix cases.

    4. RFC 8785 evidence must be frozen to exact vectors

    The dependency and hashing direction are acceptable. The revised plan must identify the exact published RFC 8785 test vector(s), expected canonical UTF-8 text/bytes, and the exact fixed payload plus expected lowercase SHA-256 value used as the golden test. “A published vector” still leaves the implementation to invent the actual acceptance evidence.

    Required resubmission

    Leave this Work Plan unchanged as history and submit one complete revised Work Plan comment. Reconfirm the same main baseline, or refresh the entire plan if main moves. The revision must restate the full path list, dependencies, port change, exact public contracts, catalog authority, timeout semantics, digest/time rules, tests, non-goals and stop conditions.

    #26 remains Open / Qy assigned / Project In Progress for planning only. This comment is not implementation authorization. #23 remains Ready/unassigned; #27 and later dependent issues remain Blocked.

  5. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    Final Implementation Work Plan v2 - M2-05 Quote Collection and Deterministic Selection - Awaiting Maintainer Scope Confirmation

    0. Status, history, authority, and exact baseline

    This is a complete, self-contained replacement proposal for the #26
    implementation scope. It supersedes the prior Work Plan for future Scope Gate
    purposes, but does not edit or delete history.

    Historical records retained:

    The four requested corrections are incorporated throughout this full plan:

    1. catalog configuration is separated from caller request data, has no
      self-asserted Maintainer approval, and has a precise immutable source record;
    2. every public result, candidate, timing, raw-retention, normalized, failure,
      and digest shape is a closed discriminated contract;
    3. timeout guarantees collector terminality and signal propagation only, never
      unproven underlying-source cancellation;
    4. RFC 8785 conformance and selected-payload SHA evidence are fixed to exact
      input, canonical text/bytes, and expected hashes.

    Exact planning baseline was rechecked immediately before this comment:

    This comment is planning only. It is not
    SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED, QA PASS, Review,
    READY_TO_MERGE, Merge Gate, or merge authorization. No branch, worktree,
    repository file, dependency, lockfile, commit, push, or PR is created by this
    comment.

    If origin/main changes before branch creation, Builder stops, rescans the
    new main, and requests a new Scope Gate instead of implementing against this
    baseline.

    1. Goal and trust boundary

    Add one server-only, deterministic Quote collection and candidate-selection
    operation to the existing @moss-mini-demo/moss-adapter public entry.

    The operation:

    1. receives untrusted request data separately from server-owned asset catalog
      configuration;
    2. validates chain-143 comparison basis and catalog freshness/integrity;
    3. requests allowed candidate protocol Quotes concurrently;
    4. records exactly one terminal outcome for every candidate;
    5. preserves every acquired RawQuote separately from Mini-Demo-derived
      snapshots and normalization;
    6. selects only among eligible normalized outcomes using the disclosed rule;
    7. returns either one selected candidate or an explicit no-eligible-candidate
      result.

    A Quote is protocol-selection context only. It is not Capability, simulation,
    execution evidence, a best-trade claim, safety, approval, authorization, or a
    cryptographic binding to a later Capability.

    The only public rule identifier is:

    DETERMINISTIC_CANDIDATE_SELECTION_V0_1

    Public output and tests must not call the result best, optimal, safe, approved,
    executable, guaranteed, or equivalent.

    2. Exact 16-path writable list

    Implementation may modify exactly these 16 repository paths and no others:

    1. pnpm-lock.yaml
    2. packages/moss-adapter/package.json
    3. packages/moss-adapter/src/types.ts
    4. packages/moss-adapter/src/source-bindings.ts
    5. packages/moss-adapter/src/production.ts
    6. packages/moss-adapter/src/fake.ts
    7. packages/moss-adapter/src/asset-catalog.ts (new)
    8. packages/moss-adapter/src/quote.ts (new)
    9. packages/moss-adapter/src/selection.ts (new)
    10. packages/moss-adapter/src/index.ts
    11. packages/moss-adapter/test/contracts.test.ts
    12. packages/moss-adapter/test/production.test.ts
    13. packages/moss-adapter/test/fake.test.ts
    14. packages/moss-adapter/test/quote.test.ts (new)
    15. packages/moss-adapter/test/selection.test.ts (new)
    16. packages/moss-adapter/test/public-entry.smoke.mjs

    Exact metadata decisions:

    • types.ts: yes, modified for the closed contracts in Sections 3, 5, 6, 8,
      9, 11, and 12.
    • index.ts: yes, modified only for the exact exports in Section 3.
    • adapter package.json: yes, modified only for the two dependencies in
      Section 4; package name and sole . export remain unchanged.
    • pnpm-lock.yaml: yes, modified only for the adapter importer and exact
      canonicalize@3.0.0 package record.
    • root package.json: no change.
    • pnpm-workspace.yaml: no change.
    • packages/moss-adapter/src/errors.ts: no change.
    • adapter tsconfig files: no change.
    • no fixture file is added; tests use inline synthetic data.

    Any seventeenth path, report-schema source edit, root script change, workflow
    edit, build-permission change, documentation edit, or third dependency requires
    a revised Scope Gate.

    3. Sole public entry, runtime API, and type surface

    The package keeps exactly one public entry:

    @moss-mini-demo/moss-adapter

    No subpath export or private src import is permitted.

    All existing exports remain. The only new runtime export is:

    collectAndSelectQuotesV0_1

    Exact signature:

    function collectAndSelectQuotesV0_1(
      port: MossPort,
      catalog: AssetCatalogV0_1,
      request: QuoteCollectionRequestV0_1,
    ): Promise<QuoteCollectionResultV0_1>;

    The parameter order is part of the boundary:

    The only new type exports are:

    • QuoteRequestOptionsV0_1;
    • QuoteAssetV0_1;
    • AssetCatalogV0_1;
    • AssetCatalogEntryV0_1;
    • DecimalsRecordV0_1;
    • AssetCatalogSnapshotV0_1;
    • QuoteCollectionRequestV0_1;
    • QuoteRawProjectionV0_1;
    • QuoteTimingV0_1;
    • RawQuoteRetentionV0_1;
    • NormalizedQuoteV0_1;
    • QuoteCandidateFailureCodeV0_1;
    • QuoteCandidateOutcomeV0_1;
    • SelectedQuoteDigestV0_1;
    • QuoteCollectionResultV0_1.

    No public canonicalization helper, hash helper, comparator, catalog factory,
    asset lookup helper, timer helper, second port, runtime Schema, or
    source-binding bypass is added.

    4. Exact dependencies and supply-chain policy

    packages/moss-adapter/package.json adds exactly:

    • @moss-mini-demo/report-schema: workspace:*;
    • canonicalize: 3.0.0.

    The existing server-only: 0.0.1 dependency remains unchanged.

    The adapter consumes only the report-schema public package entry and reuses:

    • AssetSchema;
    • EvmAddressSchema;
    • ProtocolIdSchema;
    • PositiveAmountSchema;
    • UnsignedAmountSchema.

    It does not import report-schema private source or copy those validators.

    RFC 8785 canonicalization uses exact canonicalize@3.0.0:

    SHA-256 uses Node 22 node:crypto over canonical UTF-8 bytes. No second hash
    dependency and no hand-written/approximate canonicalizer is allowed.

    The lockfile may add only:

    • @moss-mini-demo/report-schema: workspace:* to the moss-adapter importer;
    • canonicalize: 3.0.0 to that importer;
    • the exact canonicalize@3.0.0 package/snapshot record.

    No unrelated version refresh is accepted. pnpm-workspace.yaml and the sole
    'sharp@0.34.5': true build permission remain byte-for-byte unchanged. No
    second build-script permission may be added manually or by pnpm.

    5. Untrusted request contract

    QuoteCollectionRequestV0_1 is reject-only caller data:

    type QuoteCollectionRequestV0_1 = Readonly<{
      chainId: 143;
      candidateProtocols: readonly string[];
      allowedProtocols: readonly string[];
      quoteInput: Readonly<{
        method: string;
        account: string;
        params: Readonly<{
          inputAsset: QuoteAssetV0_1;
          outputAsset: QuoteAssetV0_1;
          amountIn: string;
          readonly [additionalKey: string]: JsonValue;
        }>;
      }>;
      inputAsset: QuoteAssetV0_1;
      outputAsset: QuoteAssetV0_1;
      amountIn: string;
    }>;

    Validation:

    • candidate and allowed arrays are non-empty;
    • each ProtocolId uses the accepted lowercase ASCII kebab grammar;
    • each array is unique; no trimming, sorting-as-repair, case folding, or
      deduplication occurs;
    • chainId is exactly 143;
    • account is an exact EIP-55 non-zero address;
    • quoteInput.params.inputAsset, outputAsset, and amountIn exactly
      equal the top-level basis;
    • additional params remain JSON only and cannot replace the required fields.

    The request is cloned once inside a sanitized boundary. Getter, hostile/revoked
    Proxy, cyclic/non-JSON input, duplicate protocol, invalid address, invalid
    amount, wrong chain, or inconsistent duplicated basis throws a new
    MossAdapterError:

    code: INVALID_INPUT
    operation: quote
    cause: absent
    raw message/data: absent
    Quote delegations: 0
    

    Amounts are canonical positive base-10 integer strings no larger than
    2^256 - 1. Number, float, exponent, sign, whitespace, separator, leading
    zero, zero, and over-uint256 forms are rejected. Ranking conversion uses only
    BigInt.

    6. Asset identity and server-owned catalog boundary

    QuoteAssetV0_1 is exactly:

    type QuoteAssetV0_1 =
      | Readonly<{ kind: "NATIVE" }>
      | Readonly<{ kind: "ERC20"; address: EvmAddress }>;

    Rules:

    • chain id is literal 143;
    • native has no address;
    • ERC-20 identity is exact EIP-55 address;
    • zero/sentinel addresses fail;
    • symbol/name/icon/ticker/decimals do not establish identity;
    • no lowercasing, checksum repair, symbol lookup, address inference, or native
      sentinel occurs.

    The catalog is not inside QuoteCollectionRequestV0_1. It is injected
    separately by the server composition owner. #26 provides no loader and commits
    no production catalog/address. A later Web/API composition scope owns loading
    and access control.

    The catalog never claims MAINTAINER_APPROVED. Its provenance is only a
    source-channel classification and does not prove approval, correctness, or live
    evidence:

    type DecimalsRecordV0_1 =
      | Readonly<{ status: "KNOWN"; value: number }>
      | Readonly<{ status: "UNKNOWN" }>;
    
    type AssetCatalogEntryV0_1 = Readonly<{
      asset: QuoteAssetV0_1;
      decimals: DecimalsRecordV0_1;
    }>;
    
    type AssetCatalogV0_1 = Readonly<{
      schemaVersion: "0.1";
      catalogId: string;
      sourceVersion: string;
      provenance: "SERVER_CONFIGURED" | "SYNTHETIC_TEST";
      sourceReference: string;
      chainId: 143;
      validFrom: string;
      validUntil: string;
      entries: readonly AssetCatalogEntryV0_1[];
    }>;

    Exact scalar grammar:

    • catalogId: ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$, length 1..64;
    • sourceVersion:
      ^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$,
      length 5..32;
    • sourceReference: ^[a-z0-9][a-z0-9._:/@-]{0,127}$, length 1..128;
    • timestamps: calendar-valid canonical UTC with exactly millisecond precision,
      YYYY-MM-DDTHH:mm:ss.sssZ;
    • validFrom < validUntil;
    • freshness at collection start is
      validFrom <= new Date(Date.now()) < validUntil;
    • known decimals are safe integers 0..255;
    • entries are non-empty and asset identities are unique.

    Catalog validation happens before any Quote call. Missing catalog, getter/Proxy,
    wrong schema/chain, malformed source metadata, empty/duplicate entries,
    conflicting duplicate decimals, invalid time interval, not-yet-valid catalog,
    expired catalog, or clone/freeze failure throws the same stable
    INVALID_INPUT / quote error with zero delegations.

    A structurally valid catalog may intentionally omit an asset or record
    UNKNOWN decimals:

    • requested asset absent -> every otherwise allowed candidate is SKIPPED
      with UNKNOWN_ASSET, zero Quote calls;
    • requested asset decimals UNKNOWN -> every otherwise allowed candidate is
      SKIPPED with UNKNOWN_DECIMALS, zero Quote calls.

    Synthetic tests must use provenance: "SYNTHETIC_TEST". Such a catalog is
    test configuration only, not production/Maintainer-approved asset evidence.
    SERVER_CONFIGURED likewise identifies loading channel only and is not an
    approval claim.

    7. Catalog immutable snapshot and digest

    After validation, the adapter:

    1. creates an owned clone;
    2. sorts entries by canonical asset-key UTF-8 bytes:
      ERC20:<EIP55 address> or NATIVE;
    3. deep-freezes the clone;
    4. canonicalizes this exact catalog payload:
      {
        schemaVersion: "moss-mini-demo/asset-catalog/0.1",
        catalogId,
        sourceVersion,
        provenance,
        sourceReference,
        chainId,
        validFrom,
        validUntil,
        entries: sorted owned entries
      }
      
    5. hashes canonical UTF-8 with SHA-256;
    6. creates digest = "sha256:" + 64 lowercase hex.

    Exact result metadata:

    type AssetCatalogSnapshotV0_1 = Readonly<{
      schemaVersion: "0.1";
      catalogId: string;
      sourceVersion: string;
      provenance: "SERVER_CONFIGURED" | "SYNTHETIC_TEST";
      sourceReference: string;
      chainId: 143;
      validFrom: string;
      validUntil: string;
      entries: readonly AssetCatalogEntryV0_1[];
      digest: `sha256:${string}`;
    }>;

    The digest proves only deterministic content identity of the owned catalog
    snapshot. It does not authenticate who supplied it and is not approval or
    chain evidence.

    The catalog snapshot is included once in the top-level result. Normalized
    quotes reference its identity/digest rather than copying authority claims.

    8. Raw Quote projection and source/derived separation

    Current RawQuote.mossOriginal.value remains complete opaque JsonValue.
    #26 does not rewrite, freeze, or replace it.

    The collector takes one adapter-owned structured clone and extracts:

    type QuoteRawProjectionV0_1 = Readonly<{
      chainId: 143;
      inputAsset: QuoteAssetV0_1;
      outputAsset: QuoteAssetV0_1;
      amountIn: string;
      amountOut: string;
      observableBlockWindow: Readonly<{
        fromBlock: string;
        toBlock: string;
      }>;
    }>;

    Additional raw JSON fields remain in both original raw source and owned
    snapshot, but do not silently become normalized fields.

    Missing fields, unsnapshotable raw data, invalid Unicode/non-I-JSON digest
    material, number/float/exponent/signed/zero/leading-zero/over-uint256 amounts,
    wrong chain, invalid asset, malformed block number, or
    BigInt(fromBlock) > BigInt(toBlock) yields MALFORMED_QUOTE.

    Exact raw retention union:

    type RawQuoteRetentionV0_1 =
      | Readonly<{
          status: "SNAPSHOTTED";
          source: RawQuote;       // exact acquired object identity
          snapshot: RawQuote["mossOriginal"]["value"]; // owned and deep-frozen
        }>
      | Readonly<{
          status: "UNSNAPSHOTTABLE";
          source: RawQuote;       // exact acquired object identity
        }>;

    Only MALFORMED_QUOTE may carry UNSNAPSHOTTABLE. Every other acquired
    outcome has SNAPSHOTTED.

    The adapter never mutates or freezes source. Containers, snapshots,
    catalog, normalized data, timing data, failures, digest payload, outcome array,
    and result are frozen. Freezing a derived container must not recursively freeze
    the source-owned RawQuote; implementation freezes owned fields explicitly.

    9. Exact timing contract

    type QuoteTimingV0_1 = Readonly<{
      observedAt: string; // canonical UTC milliseconds
      monotonicNs: string; // canonical unsigned integer string
      clock: "NODE_PROCESS_HRTIME_V0_1";
    }>;
    • observedAt = new Date(Date.now()).toISOString();
    • monotonicNs = process.hrtime.bigint().toString(10);
    • each candidate gets exactly one terminal timing record;
    • acquired candidates name it acquiredTiming;
    • skipped/acquisition-failed candidates name it terminalTiming.

    Timing is observation metadata. It is excluded from ranking, selected digest,
    catalog digest, and deterministic selection fields.

    #26 does not call action. The monotonic acquisition token only allows a
    future authorized same-process orchestrator to calculate quote-to-action
    elapsed time. Clock/process discontinuity is unprovable; wall-clock subtraction
    must not replace the monotonic calculation.

    10. Closed failure vocabulary and precedence

    QuoteCandidateFailureCodeV0_1 is exactly:

    PROTOCOL_NOT_ALLOWED
    QUOTE_TIMEOUT
    QUOTE_ACQUISITION_FAILED
    MALFORMED_QUOTE
    ASSET_DIRECTION_MISMATCH
    AMOUNT_BASIS_MISMATCH
    UNKNOWN_ASSET
    UNKNOWN_DECIMALS
    

    Global invalid request/catalog throws MossAdapterError(INVALID_INPUT, quote)
    before candidate outcomes exist.

    Per-candidate precedence is exactly:

    1. outside allowed set -> PROTOCOL_NOT_ALLOWED;
    2. requested asset absent -> UNKNOWN_ASSET;
    3. requested decimals unknown -> UNKNOWN_DECIMALS;
    4. 8,000 ms collector deadline -> QUOTE_TIMEOUT;
    5. source reject/throw before deadline -> QUOTE_ACQUISITION_FAILED;
    6. raw snapshot/projection/canonicalization invalid -> MALFORMED_QUOTE;
    7. raw input/output asset differs -> ASSET_DIRECTION_MISMATCH;
    8. raw amountIn differs -> AMOUNT_BASIS_MISMATCH;
    9. otherwise -> ELIGIBLE.

    Exactly one precedence result is retained per candidate. Later checks do not
    overwrite an earlier failure.

    An existing stable source MossAdapterError.code is retained only in the
    QUOTE_ACQUISITION_FAILED branch as
    sourceErrorCode: MossAdapterErrorCode | null. Generic/hostile exceptions
    produce null. No raw exception, cause, message, URL, headers, account,
    params, key, or source object is retained in that branch.

    11. Exact candidate outcome discriminated union

    type QuoteCandidateOutcomeV0_1 =
      | Readonly<{
          status: "SKIPPED";
          protocolId: string;
          terminalTiming: QuoteTimingV0_1;
          failure: Readonly<{
            code:
              | "PROTOCOL_NOT_ALLOWED"
              | "UNKNOWN_ASSET"
              | "UNKNOWN_DECIMALS";
          }>;
        }>
      | Readonly<{
          status: "ACQUISITION_FAILED";
          protocolId: string;
          terminalTiming: QuoteTimingV0_1;
          failure:
            | Readonly<{ code: "QUOTE_TIMEOUT" }>
            | Readonly<{
                code: "QUOTE_ACQUISITION_FAILED";
                sourceErrorCode: MossAdapterErrorCode | null;
              }>;
        }>
      | Readonly<{
          status: "INELIGIBLE";
          protocolId: string;
          acquiredTiming: QuoteTimingV0_1;
          raw:
            | RawQuoteRetentionV0_1 // only MALFORMED_QUOTE may be unsnapshottable
            | Readonly<{
                status: "SNAPSHOTTED";
                source: RawQuote;
                snapshot: RawQuote["mossOriginal"]["value"];
              }>;
          failure:
            | Readonly<{ code: "MALFORMED_QUOTE" }>
            | Readonly<{ code: "ASSET_DIRECTION_MISMATCH" }>
            | Readonly<{ code: "AMOUNT_BASIS_MISMATCH" }>;
        }>
      | Readonly<{
          status: "ELIGIBLE";
          protocolId: string;
          acquiredTiming: QuoteTimingV0_1;
          raw: Readonly<{
            status: "SNAPSHOTTED";
            source: RawQuote;
            snapshot: RawQuote["mossOriginal"]["value"];
          }>;
          normalized: NormalizedQuoteV0_1;
        }>;

    Implementation refines the INELIGIBLE branch so:

    • MALFORMED_QUOTE accepts either raw-retention discriminant;
    • asset/amount mismatch requires raw.status: "SNAPSHOTTED".

    No branch has optional raw/timing/normalized/failure fields. There is no shape
    that is simultaneously successful and failed.

    Every candidate ProtocolId appears once. Outcomes are sorted by ProtocolId
    UTF-8 byte order, not input or settlement order.

    12. Normalized quote contract and lossless ranking

    type NormalizedQuoteV0_1 = Readonly<{
      chainId: 143;
      protocolId: string;
      method: string;
      account: string;
      inputAsset: QuoteAssetV0_1;
      outputAsset: QuoteAssetV0_1;
      inputAmount: string;
      outputAmount: string;
      normalizedAmountOut: string;
      inputDecimals: number;
      outputDecimals: number;
      catalog: Readonly<{
        catalogId: string;
        sourceVersion: string;
        provenance: "SERVER_CONFIGURED" | "SYNTHETIC_TEST";
        sourceReference: string;
        digest: `sha256:${string}`;
      }>;
      observableBlockWindow: Readonly<{
        fromBlock: string;
        toBlock: string;
      }>;
      mossSource: Readonly<{
        provenance: "PINNED_SUBMODULE" | "SYNTHETIC_FAKE";
        upstreamCommit: string;
        integrationCommit: string | null;
        patchsetDigest: string | null;
      }>;
    }>;

    Every eligible candidate has exact request output-asset identity, so all output
    amounts share one catalog decimals record. Normalization is lossless:

    normalizedAmountOut = exact canonical amountOut base-unit string
    comparisonAmount = BigInt(normalizedAmountOut)
    comparisonDecimals = outputDecimals
    

    No division, multiplication, rounding, display unit, cross-asset conversion,
    USD value, symbol, or Number is used. The maximum uint256 value is accepted.

    Winner:

    1. highest BigInt(normalizedAmountOut);
    2. tie -> ProtocolId UTF-8 byte lexicographic ascending;
    3. comparator uses TextEncoder and numeric byte comparison;
    4. no localeCompare, collation, case folding, Unicode normalization,
      candidate order, settlement order, time, random value, or environment input.

    ProtocolId is canonical lowercase ASCII, so its UTF-8 and ASCII bytes coincide.

    13. Port change and exact timeout semantics

    This v2 still requests a new Scope Gate for the disclosed, backward-compatible
    optional signal expansion:

    type QuoteRequestOptionsV0_1 = Readonly<{
      signal?: AbortSignal;
    }>;
    
    MossPort.quote(
      protocolId: string,
      input: QuoteInput,
      options?: QuoteRequestOptionsV0_1,
    ): Promise<RawQuote>;
    
    MossSourceBindings.quote(
      protocolId: string,
      input: QuoteInput,
      options?: QuoteRequestOptionsV0_1,
    ): Promise<Readonly<{
      operation: MossLoadedOperation;
      quote: JsonValue;
    }>>;

    ProductionMossPort, FakeMossPort, and createBoundMossPort forward
    the exact signal. No other method changes. Existing two-argument calls remain
    valid.

    For each allowed, catalog-known candidate:

    1. create one AbortController;
    2. attach fulfillment and rejection drainage handlers to the source promise;
    3. start a fixed 8,000 ms timer immediately around the one port.quote call;
    4. if source settles first, clear timer and commit acquired outcome;
    5. if timer callback runs first while local state is unsettled:
      • call controller.abort("MOSS_MINI_DEMO_QUOTE_TIMEOUT_V0_1");
      • synchronously mark local terminal state;
      • freeze one QUOTE_TIMEOUT outcome;
    6. any later fulfillment/rejection is consumed and discarded;
    7. it cannot append, replace, mutate, rank, digest, or select;
    8. aggregate all candidate tasks using Promise.allSettled.

    Collector terminality is the local immutable state transition in step 5. It
    does not depend on source cooperation.

    Signal propagation guarantees only:

    • the exact signal object reaches Production/Fake/source bindings;
    • signal.aborted === true after deadline;
    • signal.reason === "MOSS_MINI_DEMO_QUOTE_TIMEOUT_V0_1".

    #26 does not claim that the underlying source request was canceled. A
    cooperative source may stop. A signal-ignoring source may still fulfill/reject
    later; both paths are drained. Quote is required to be a read-only operation,
    but this interface cannot prove absence of external effects in an unimplemented
    real source.

    No output or documentation may say “source canceled” or equivalent unless a
    future concrete binding independently proves that behavior.

    A bare Promise.race without preattached late-settlement drainage is
    forbidden.

    14. Exact selected digest and top-level result unions

    type SelectedQuoteDigestV0_1 = Readonly<{
      algorithm: "RFC8785-SHA256";
      value: `sha256:${string}`;
      payload: Readonly<Record<string, JsonValue>>;
    }>;
    
    type QuoteCollectionResultV0_1 =
      | Readonly<{
          status: "SELECTED";
          method: "DETERMINISTIC_CANDIDATE_SELECTION_V0_1";
          catalog: AssetCatalogSnapshotV0_1;
          outcomes: readonly QuoteCandidateOutcomeV0_1[];
          selected: Readonly<{
            protocolId: string;
            digest: SelectedQuoteDigestV0_1;
          }>;
        }>
      | Readonly<{
          status: "NOT_SELECTED";
          method: "DETERMINISTIC_CANDIDATE_SELECTION_V0_1";
          code: "NO_ELIGIBLE_QUOTE";
          catalog: AssetCatalogSnapshotV0_1;
          outcomes: readonly QuoteCandidateOutcomeV0_1[];
        }>;

    SELECTED exists only when exactly one outcome is the deterministic winner.
    The selected protocol references an ELIGIBLE outcome. NOT_SELECTED
    exists only when zero outcomes are eligible. The full candidate list is always
    present.

    The selected digest payload is exactly:

    {
      schemaVersion: "moss-mini-demo/selected-quote-digest/0.1",
      chainId,
      protocolId,
      method,
      account,
      inputAsset,
      outputAsset,
      inputAmount,
      outputAmount,
      catalog: {
        catalogId,
        sourceVersion,
        provenance,
        sourceReference,
        digest
      },
      inputDecimals,
      outputDecimals,
      observableBlockWindow: { fromBlock, toBlock },
      mossSource: {
        provenance,
        upstreamCommit,
        integrationCommit: string | null,
        patchsetDigest: string | null
      },
      rawQuote: adapter-owned raw snapshot
    }
    

    It is passed to canonicalize@3.0.0; canonical UTF-8 bytes are hashed once
    with Node createHash("sha256"). Output is
    sha256: plus exactly 64 lowercase hex characters.

    A canonicalization rejection, undefined result, invalid Unicode, or non-I-JSON
    value yields MALFORMED_QUOTE; no fallback serializer exists.

    The digest is only a deterministic fingerprint of selected Quote context. It
    is not a signature, Capability digest, integrity proof, execution binding, or
    evidence that a later Capability came from this Quote.

    acquiredTiming, terminalTiming, candidate order, settlement order,
    deadline, and wall/monotonic time are excluded from catalog digest, selected
    digest, ranking, and selected subobject.

    15. Frozen RFC 8785 conformance vector

    The implementation test must use the exact public values.json vector from
    the RFC 8785 reference test corpus:

    Exact canonical text, with the backslash escapes shown literally:

    {"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],"string":"€$\u000f\nA'B\"\\\\\"/"}

    Exact canonical UTF-8 bytes in lowercase hexadecimal:

    7b 22 6c 69 74 65 72 61 6c 73 22 3a 5b 6e 75 6c 6c 2c 74 72 75 65 2c 66 61 6c 73 65 5d 2c 22 6e
    75 6d 62 65 72 73 22 3a 5b 33 33 33 33 33 33 33 33 33 2e 33 33 33 33 33 33 33 2c 31 65 2b 33 30
    2c 34 2e 35 2c 30 2e 30 30 32 2c 31 65 2d 32 37 5d 2c 22 73 74 72 69 6e 67 22 3a 22 e2 82 ac 24
    5c 75 30 30 30 66 5c 6e 41 27 42 5c 22 5c 5c 5c 5c 5c 22 2f 22 7d
    

    SHA-256 of those exact bytes:

    2d5e01a318d0f0879ab568c4be289c8b1f64ef8921a53c6277d5e069978baacb

    The test must compare canonical text, UTF-8 byte hex, and SHA. It must not
    regenerate expected values from the implementation under test.

    16. Frozen synthetic catalog and selected-Quote golden vectors

    The test catalog source payload is exactly:

    {
      "schemaVersion": "moss-mini-demo/asset-catalog/0.1",
      "catalogId": "synthetic-assets",
      "sourceVersion": "1.0.0",
      "provenance": "SYNTHETIC_TEST",
      "sourceReference": "test/asset-catalog-v1",
      "chainId": 143,
      "validFrom": "2026-01-01T00:00:00.000Z",
      "validUntil": "2027-01-01T00:00:00.000Z",
      "entries": [
        {
          "asset": {
            "kind": "ERC20",
            "address": "0x2222222222222222222222222222222222222222"
          },
          "decimals": { "status": "KNOWN", "value": 6 }
        },
        {
          "asset": { "kind": "NATIVE" },
          "decimals": { "status": "KNOWN", "value": 18 }
        }
      ]
    }

    Expected canonical catalog text:

    {"catalogId":"synthetic-assets","chainId":143,"entries":[{"asset":{"address":"0x2222222222222222222222222222222222222222","kind":"ERC20"},"decimals":{"status":"KNOWN","value":6}},{"asset":{"kind":"NATIVE"},"decimals":{"status":"KNOWN","value":18}}],"provenance":"SYNTHETIC_TEST","schemaVersion":"moss-mini-demo/asset-catalog/0.1","sourceReference":"test/asset-catalog-v1","sourceVersion":"1.0.0","validFrom":"2026-01-01T00:00:00.000Z","validUntil":"2027-01-01T00:00:00.000Z"}

    Expected catalog digest:

    sha256:9f09d6a5e86f4ab1e459a7e0db1fd981afcbafe8cdc880e4e6b8e295dfc9a75e

    The fixed selected-Quote digest payload is exactly:

    {
      "schemaVersion": "moss-mini-demo/selected-quote-digest/0.1",
      "chainId": 143,
      "protocolId": "alpha-protocol",
      "method": "swap",
      "account": "0x1111111111111111111111111111111111111111",
      "inputAsset": { "kind": "NATIVE" },
      "outputAsset": {
        "kind": "ERC20",
        "address": "0x2222222222222222222222222222222222222222"
      },
      "inputAmount": "1000000000000000000",
      "outputAmount": "42000000",
      "catalog": {
        "catalogId": "synthetic-assets",
        "sourceVersion": "1.0.0",
        "provenance": "SYNTHETIC_TEST",
        "sourceReference": "test/asset-catalog-v1",
        "digest": "sha256:9f09d6a5e86f4ab1e459a7e0db1fd981afcbafe8cdc880e4e6b8e295dfc9a75e"
      },
      "inputDecimals": 18,
      "outputDecimals": 6,
      "observableBlockWindow": {
        "fromBlock": "100",
        "toBlock": "101"
      },
      "mossSource": {
        "provenance": "SYNTHETIC_FAKE",
        "upstreamCommit": "1ae6b6322d51fae9104f047efb94e601050b967f",
        "integrationCommit": "1ae6b6322d51fae9104f047efb94e601050b967f",
        "patchsetDigest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
      },
      "rawQuote": {
        "chainId": 143,
        "inputAsset": { "kind": "NATIVE" },
        "outputAsset": {
          "kind": "ERC20",
          "address": "0x2222222222222222222222222222222222222222"
        },
        "amountIn": "1000000000000000000",
        "amountOut": "42000000",
        "observableBlockWindow": {
          "fromBlock": "100",
          "toBlock": "101"
        },
        "synthetic": true
      }
    }

    Expected canonical selected-Quote text:

    {"account":"0x1111111111111111111111111111111111111111","catalog":{"catalogId":"synthetic-assets","digest":"sha256:9f09d6a5e86f4ab1e459a7e0db1fd981afcbafe8cdc880e4e6b8e295dfc9a75e","provenance":"SYNTHETIC_TEST","sourceReference":"test/asset-catalog-v1","sourceVersion":"1.0.0"},"chainId":143,"inputAmount":"1000000000000000000","inputAsset":{"kind":"NATIVE"},"inputDecimals":18,"method":"swap","mossSource":{"integrationCommit":"1ae6b6322d51fae9104f047efb94e601050b967f","patchsetDigest":"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","provenance":"SYNTHETIC_FAKE","upstreamCommit":"1ae6b6322d51fae9104f047efb94e601050b967f"},"observableBlockWindow":{"fromBlock":"100","toBlock":"101"},"outputAmount":"42000000","outputAsset":{"address":"0x2222222222222222222222222222222222222222","kind":"ERC20"},"outputDecimals":6,"protocolId":"alpha-protocol","rawQuote":{"amountIn":"1000000000000000000","amountOut":"42000000","chainId":143,"inputAsset":{"kind":"NATIVE"},"observableBlockWindow":{"fromBlock":"100","toBlock":"101"},"outputAsset":{"address":"0x2222222222222222222222222222222222222222","kind":"ERC20"},"synthetic":true},"schemaVersion":"moss-mini-demo/selected-quote-digest/0.1"}

    Expected selected-Quote digest:

    sha256:b9ee24d4c1566c35b3282f4b15fc4d98551f4c2d199ea8221926bf09c8f37594

    Tests must compare the fixed payload, canonical text, and expected hash
    literally. Recomputing expected values using the code under test is not
    acceptance evidence.

    All addresses, protocol IDs, times, amounts, block values, raw values, catalog
    metadata, and provenance in these vectors are synthetic test data.

    17. Complete deterministic selection and timeout test matrix

    packages/moss-adapter/test/quote.test.ts must cover:

    • exact request clone/reject boundary;
    • catalog separate from request and no self-approved authority field;
    • catalog grammar, clone, sort, freeze, freshness, digest, and non-mutation;
    • missing, duplicate, inconsistent, not-yet-valid, and stale catalog -> stable
      INVALID_INPUT and zero delegations;
    • synthetic catalog explicitly remains SYNTHETIC_TEST;
    • absent asset -> SKIPPED/UNKNOWN_ASSET and zero delegation;
    • unknown decimals -> SKIPPED/UNKNOWN_DECIMALS and zero delegation;
    • raw projection extraction;
    • source RawQuote identity preserved;
    • owned raw snapshot separate and frozen;
    • request/raw asset matching by tagged identity, never symbol;
    • exact amount basis;
    • integer-string/BigInt-only behavior;
    • zero, leading-zero, float, exponent, Number, negative, missing, and
      over-uint256 rejection;
    • missing/invalid/reversed block window;
    • malformed fixed to MALFORMED_QUOTE;
    • input/source getter/Proxy failures sanitized;
    • input, catalog, and RawQuote not mutated;
    • exact timing shape;
    • exact RFC reference vector from Section 15;
    • exact catalog/selected vectors from Section 16;
    • different timing values yield identical ranking, selected protocol, catalog
      digest, and selected digest.

    packages/moss-adapter/test/selection.test.ts must cover the following exact
    matrix and expected discriminants:

    1. both success / first higher -> first ELIGIBLE, second ELIGIBLE,
      top SELECTED first;
    2. both success / second higher -> top SELECTED second;
    3. exact tie -> byte-ascending protocol selected;
    4. reversed input and settlement order -> same ordered outcome identities,
      selected protocol, catalog digest, and selected digest;
    5. outside allowlist -> SKIPPED/PROTOCOL_NOT_ALLOWED, zero call for it;
    6. unknown asset -> SKIPPED/UNKNOWN_ASSET, zero Quote calls;
    7. unknown decimals -> SKIPPED/UNKNOWN_DECIMALS, zero Quote calls;
    8. cooperative source observes abort -> immutable
      ACQUISITION_FAILED/QUOTE_TIMEOUT;
    9. signal-ignoring late fulfillment -> same timeout outcome, late value drained,
      no mutation/selection pollution;
    10. signal-ignoring late rejection -> same timeout outcome, rejection drained,
      no unhandled rejection;
    11. one timeout plus one eligible -> eligible selected, timeout retained;
    12. one source rejection plus one eligible -> eligible selected, source failure
      retained with safe sourceErrorCode/null;
    13. malformed plus eligible -> malformed INELIGIBLE, eligible selected;
    14. unsnapshotable malformed raw -> UNSNAPSHOTTABLE, no source leak;
    15. asset mismatch -> INELIGIBLE/ASSET_DIRECTION_MISMATCH;
    16. amount mismatch -> INELIGIBLE/AMOUNT_BASIS_MISMATCH;
    17. all acquisition failures -> all outcomes retained, top
      NOT_SELECTED/NO_ELIGIBLE_QUOTE;
    18. mixed skipped/acquisition/ineligible and no eligible -> every outcome
      retained under its single precedence code;
    19. uint256 maximum -> accepted and compared by BigInt;
    20. repeated controlled execution -> identical deterministic selection/digests
      and no input mutation.

    Additional regressions:

    • exact AbortSignal and reason forwarded by Production and Fake;
    • existing two-argument quote calls remain valid;
    • five-method port shape unchanged;
    • all M2-04: Create moss-adapter package and public ports #25 source/input sanitization tests remain green;
    • public-entry smoke sees exactly one new runtime export,
      collectAndSelectQuotesV0_1;
    • browser-like import still fails through server-only;
    • no localeCompare and no Number conversion for amounts.

    The aggregate must use Promise.allSettled, retain one terminal outcome per
    candidate, and return byte-ordered frozen outcomes.

    18. Implementation sequence after authorization

    1. Re-fetch and verify exact baseline. Stop if main is not
      a0f3a3e8605812c9f0997059b4a30ea7a736022c.
    2. Create only codex/issue-26-quote-selection and the approved isolated
      worktree.
    3. Add exactly the two dependencies and controlled lockfile records.
    4. Add exact public/internal types and optional signal forwarding without
      changing the five-method port.
    5. Implement catalog validation, owned snapshot, entry sorting, freshness, and
      digest.
    6. Implement request validation and raw Quote projection/retention.
    7. Implement collector-local terminal timeout, signal propagation, late
      drainage, and Promise.allSettled.
    8. Implement lossless normalization, UTF-8 byte comparator, result unions, and
      selected digest.
    9. Add the complete tests and update only listed regressions/smoke.
    10. Inspect exact paths, dependencies, lifecycle/build policy, public exports,
      source immutability, prohibited wording, and fixed vectors.
    11. Run clean-checkout verification.
    12. Create one Draft PR with Closes #26, wait for exact-head CI, post
      Implementation Report and Pre-PR QA Handoff, then freeze the Head.

    No implementation action occurs before a Maintainer explicitly accepts v2 with
    SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED.

    19. Local and CI verification

    Use Node 22.23.1 and pnpm 11.16.0. Final evidence runs from a fresh
    isolated checkout with no pre-existing dist or .next:

    node --version
    pnpm --version
    pnpm install --frozen-lockfile
    pnpm --filter @moss-mini-demo/moss-adapter build
    pnpm --filter @moss-mini-demo/moss-adapter typecheck
    pnpm vitest run packages/moss-adapter/test/quote.test.ts
    pnpm vitest run packages/moss-adapter/test/selection.test.ts
    pnpm vitest run packages/moss-adapter/test
    pnpm test:package-import
    pnpm build
    pnpm typecheck
    pnpm test
    pnpm check
    pnpm test:web:bundle
    pnpm test:web:production
    git diff --check origin/main...HEAD
    git diff --name-only origin/main...HEAD
    git status --short

    Acceptance requires:

    • exact 16-path diff;
    • exact dependency/lockfile change and no build-policy change;
    • frozen install independent of historic artifacts;
    • focused, adapter, package-import, full, browser-bundle, and production tests
      pass;
    • all fixed canonical text/byte/hash vectors pass literally;
    • all candidate branches and precedence rules pass;
    • no Number amount path, locale comparator, time-dependent selection, raw
      mutation, late-promise mutation, unhandled rejection, or source-cancellation
      overclaim;
    • existing workflow unchanged and exact pushed-head quality-gate SUCCESS;
    • Draft PR remains Draft for independent QA.

    20. Explicit non-goals

    #26 does not implement:

    • Capability construction;
    • Capability digest/integrity (M2-06: Implement capability construction, amount conversion and integrity #27);
    • simulation acquisition or mapping;
    • Receipt, Outcome, Warning, coverage, ordering, or state continuity;
    • Alignment;
    • Decision, STOP, or MANUAL_REVIEW;
    • report generation;
    • cryptographic Quote-to-Capability binding;
    • action orchestration or quote-to-action elapsed calculation;
    • proof that a source operation was canceled;
    • RPC/live-chain access;
    • wallet, signing, transaction construction, or transaction submission;
    • protocol-specific Kuru/Pancake code;
    • real asset/protocol addresses or production catalog values;
    • Maintainer-approval attestation for catalog data;
    • symbol pricing, USD conversion, slippage optimization, routing, or best-trade
      claims;
    • Web/API orchestration, UI, database, deployment, Clear402, M2-06: Implement capability construction, amount conversion and integrity #27, or later M2
      behavior.

    No ADR, workflow, docs, report-schema source, decision-engine, Fixture,
    Issue/Project state, branch protection, required check, permission, main, Moss
    gitlink, or .gitmodules change is in scope.

    21. Risks, stop conditions, and rollback

    Primary risks:

    • treating caller data as trusted catalog authority;
    • expanding M2-04: Create moss-adapter package and public ports #25 port without explicit Scope Gate;
    • claiming AbortSignal proves source cancellation;
    • late fulfillment/rejection changing terminal state;
    • contradictory optional result fields;
    • losing raw Quote/failure evidence;
    • unapproved asset/decimals defaults;
    • Number precision loss;
    • locale-sensitive ties;
    • incomplete RFC 8785 evidence;
    • timestamps entering selection/digest;
    • raw exception/secret leakage;
    • selection being described as best/safe/bound to Capability.

    Stop and return to Maintainer if:

    • main differs before branch creation;
    • a catalog must claim Maintainer approval or needs real address values;
    • server composition/loading must be implemented in M2-05: Implement quote collection and deterministic selection #26;
    • a source operation is not read-only and timeout behavior could create
      external mutation;
    • signal forwarding requires a sixth port method or required breaking
      parameter;
    • any third dependency, build permission, runtime export, root script,
      seventeenth path, workflow, docs, ADR, Schema, Web/API, or vendor change is
      needed;
    • canonicalize identity/integrity differs;
    • fixed RFC/catalog/selected vectors do not reproduce exactly;
    • raw Quote cannot supply the fixed projection/block window;
    • implementation crosses into Capability, simulation, Alignment, Decision,
      RPC, wallet, signing, or transaction behavior.

    Rollback is one revert of the #26 implementation commit after merge, or removal
    of only the unmerged #26 branch/PR. It must not revert #25/#24, edit main
    directly, change the Moss gitlink, or modify protection/required checks.

    22. Requested Maintainer decision

    Please record one of:

    • SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED, explicitly accepting this v2
      comment, exact 16 paths, two dependencies, public API/unions, separate
      catalog trust semantics, catalog digest/freshness, optional signal forwarding,
      collector-only timeout claims, raw/normalized ownership, deterministic
      selection, exact vectors, non-goals, and stop conditions; or
    • SCOPE_CHANGES_REQUESTED, naming the exact path, dependency, API, trust,
      evidence, vector, or semantic decision that must change.

    Until that decision exists:

    WORK_PLAN_V2_SUBMITTED / AWAITING_MAINTAINER_SCOPE_CONFIRMATION

    No implementation branch, worktree, file change, dependency installation,
    lockfile generation, commit, push, or PR is authorized.

  6. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    SCOPE_CHANGES_REQUESTED - Work Plan v2

    Reviewed Work Plan v2: #26 (comment)

    Reviewed baseline: main@a0f3a3e8605812c9f0997059b4a30ea7a736022c

    Maintainer Scope Gate result: CHANGES REQUESTED. No #26 implementation branch, worktree, dependency install, lockfile generation, commit or PR is authorized.

    Items now accepted

    • catalog is separated from untrusted request data and assigned to server composition;
    • catalog provenance no longer self-asserts Maintainer approval, and its digest is correctly limited to content identity rather than authentication;
    • failure precedence is singular and ordered;
    • timeout promises only collector terminality and signal propagation, not underlying-source cancellation;
    • cooperative and signal-ignoring late fulfillment/rejection drainage is explicitly tested;
    • BigInt/amount basis, timing exclusion, raw/source ownership, Quote-not-execution-evidence language and M2-06: Implement capability construction, amount conversion and integrity #27 isolation remain correct;
    • the same exact 16 paths and two dependencies remain declared;
    • the synthetic catalog and selected-Quote canonical strings independently hash to the stated values.

    Two exact blockers remain.

    1. INELIGIBLE union still permits contradictory states

    The published QuoteCandidateOutcomeV0_1 gives the INELIGIBLE branch:

    raw: RawQuoteRetentionV0_1 | SnapshottedRaw;
    failure:
      | { code: "MALFORMED_QUOTE" }
      | { code: "ASSET_DIRECTION_MISMATCH" }
      | { code: "AMOUNT_BASIS_MISMATCH" };

    Because RawQuoteRetentionV0_1 includes status: "UNSNAPSHOTTABLE", the actual exported type accepts both contradictory combinations:

    • UNSNAPSHOTTABLE + ASSET_DIRECTION_MISMATCH;
    • UNSNAPSHOTTABLE + AMOUNT_BASIS_MISMATCH.

    The following prose says implementation will refine this, but prose does not change the public TypeScript contract. The type itself must make invalid combinations unrepresentable. Define exact correlated branches, for example:

    • MALFORMED_QUOTE with RawQuoteRetentionV0_1;
    • ASSET_DIRECTION_MISMATCH with SNAPSHOTTED only;
    • AMOUNT_BASIS_MISMATCH with SNAPSHOTTED only.

    Freeze the literal exported union in the revised plan and add compile-time/type-level plus runtime matrix evidence for the forbidden combinations.

    2. RFC 8785 canonical text contradicts its bytes and SHA

    Section 15 labels this exact text as canonical:

    ..."string":"EUR-CONTROL-CHAR-RENDERING"...
    

    The actual v2 block renders the U+000F position as backslash-caret-O (5c 5e 4f). Backslash-caret is not a valid JSON escape. The declared canonical byte vector instead encodes 5c 75 30 30 30 66 (the six ASCII bytes for a lowercase Unicode escape of U+000F).

    Independent calculation gives:

    • SHA-256 of the exact canonical-text block currently printed in v2: c92e902800898ec717a3dd3bee5eb3f256f3ca1b18810feb9cc45759a376fe01;
    • SHA-256 of the declared byte vector: 2d5e01a318d0f0879ab568c4be289c8b1f64ef8921a53c6277d5e069978baacb.

    The declared byte vector and second hash agree with the RFC 8785 reference vector; the printed canonical text does not. Replace it with exact valid canonical JSON using the lowercase six-character Unicode escape for U+000F, then independently prove all three representations are identical:

    1. canonical text UTF-8 encoding equals the frozen hex bytes;
    2. those bytes hash to the frozen SHA;
    3. canonicalize@3.0.0 output equals the same text.

    Do not retain two competing golden representations.

    Required resubmission

    Keep v1 and v2 unchanged as audit history. Submit one complete, self-contained Work Plan v3 with the exact correlated public union and corrected RFC vector reproduced throughout. Reconfirm the baseline and restate all 16 paths, two dependencies, catalog boundary, timeout semantics, full public shapes, vectors, tests, non-goals and stop conditions.

    #26 remains Open / Qy assigned / Project In Progress for planning only. #23 remains Ready/unassigned; #27 and later dependent Issues remain Blocked. Scope authorization and local environment cleanup remain deferred.

  7. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    Final Implementation Work Plan v3 - M2-05 Quote Collection and Deterministic Selection - Awaiting Maintainer Scope Confirmation

    0. Status, history, authority, and exact baseline

    This is a complete, self-contained replacement proposal for the #26
    implementation scope. It supersedes the prior Work Plan for future Scope Gate
    purposes, but does not edit or delete history.

    Historical records retained unchanged:

    This v3 is a full replacement proposal for the future Scope Gate. It does not
    edit or delete v1, v2, or either review comment.

    All boundaries accepted during the v2 review remain unchanged: the catalog is
    separate from untrusted request data; catalog provenance is not Maintainer
    approval; failure precedence is singular; timeout proves collector terminality
    and signal propagation only; late fulfillment/rejection is drained; BigInt,
    timing exclusion, raw ownership, Quote-not-execution-evidence language, the
    exact 16 paths, and the exact two dependencies remain fixed.

    The two remaining v2 blockers are corrected throughout this complete plan:

    1. the exported QuoteCandidateOutcomeV0_1 now contains three separately
      correlated INELIGIBLE branches, making
      UNSNAPSHOTTABLE + ASSET_DIRECTION_MISMATCH and
      UNSNAPSHOTTABLE + AMOUNT_BASIS_MISMATCH unrepresentable;
    2. the RFC 8785 canonical text now contains the literal six ASCII characters
      \u000f, and the plan freezes one canonical text whose UTF-8 bytes, byte
      vector, SHA-256, and canonicalize@3.0.0 output must all be identical.

    Exact planning baseline was rechecked immediately before this comment:

    This comment is planning only. It is not
    SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED, QA PASS, Review,
    READY_TO_MERGE, Merge Gate, or merge authorization. No branch, worktree,
    repository file, dependency, lockfile, commit, push, or PR is created by this
    comment.

    If origin/main changes before branch creation, Builder stops, rescans the
    new main, and requests a new Scope Gate instead of implementing against this
    baseline.

    1. Goal and trust boundary

    Add one server-only, deterministic Quote collection and candidate-selection
    operation to the existing @moss-mini-demo/moss-adapter public entry.

    The operation:

    1. receives untrusted request data separately from server-owned asset catalog
      configuration;
    2. validates chain-143 comparison basis and catalog freshness/integrity;
    3. requests allowed candidate protocol Quotes concurrently;
    4. records exactly one terminal outcome for every candidate;
    5. preserves every acquired RawQuote separately from Mini-Demo-derived
      snapshots and normalization;
    6. selects only among eligible normalized outcomes using the disclosed rule;
    7. returns either one selected candidate or an explicit no-eligible-candidate
      result.

    A Quote is protocol-selection context only. It is not Capability, simulation,
    execution evidence, a best-trade claim, safety, approval, authorization, or a
    cryptographic binding to a later Capability.

    The only public rule identifier is:

    DETERMINISTIC_CANDIDATE_SELECTION_V0_1

    Public output and tests must not call the result best, optimal, safe, approved,
    executable, guaranteed, or equivalent.

    2. Exact 16-path writable list

    Implementation may modify exactly these 16 repository paths and no others:

    1. pnpm-lock.yaml
    2. packages/moss-adapter/package.json
    3. packages/moss-adapter/src/types.ts
    4. packages/moss-adapter/src/source-bindings.ts
    5. packages/moss-adapter/src/production.ts
    6. packages/moss-adapter/src/fake.ts
    7. packages/moss-adapter/src/asset-catalog.ts (new)
    8. packages/moss-adapter/src/quote.ts (new)
    9. packages/moss-adapter/src/selection.ts (new)
    10. packages/moss-adapter/src/index.ts
    11. packages/moss-adapter/test/contracts.test.ts
    12. packages/moss-adapter/test/production.test.ts
    13. packages/moss-adapter/test/fake.test.ts
    14. packages/moss-adapter/test/quote.test.ts (new)
    15. packages/moss-adapter/test/selection.test.ts (new)
    16. packages/moss-adapter/test/public-entry.smoke.mjs

    Exact metadata decisions:

    • types.ts: yes, modified for the closed contracts in Sections 3, 5, 6, 8,
      9, 11, and 12.
    • index.ts: yes, modified only for the exact exports in Section 3.
    • adapter package.json: yes, modified only for the two dependencies in
      Section 4; package name and sole . export remain unchanged.
    • pnpm-lock.yaml: yes, modified only for the adapter importer and exact
      canonicalize@3.0.0 package record.
    • root package.json: no change.
    • pnpm-workspace.yaml: no change.
    • packages/moss-adapter/src/errors.ts: no change.
    • adapter tsconfig files: no change.
    • no fixture file is added; tests use inline synthetic data.

    Any seventeenth path, report-schema source edit, root script change, workflow
    edit, build-permission change, documentation edit, or third dependency requires
    a revised Scope Gate.

    3. Sole public entry, runtime API, and type surface

    The package keeps exactly one public entry:

    @moss-mini-demo/moss-adapter

    No subpath export or private src import is permitted.

    All existing exports remain. The only new runtime export is:

    collectAndSelectQuotesV0_1

    Exact signature:

    function collectAndSelectQuotesV0_1(
      port: MossPort,
      catalog: AssetCatalogV0_1,
      request: QuoteCollectionRequestV0_1,
    ): Promise<QuoteCollectionResultV0_1>;

    The parameter order is part of the boundary:

    The only new type exports are:

    • QuoteRequestOptionsV0_1;
    • QuoteAssetV0_1;
    • AssetCatalogV0_1;
    • AssetCatalogEntryV0_1;
    • DecimalsRecordV0_1;
    • AssetCatalogSnapshotV0_1;
    • QuoteCollectionRequestV0_1;
    • QuoteRawProjectionV0_1;
    • QuoteTimingV0_1;
    • RawQuoteRetentionV0_1;
    • NormalizedQuoteV0_1;
    • QuoteCandidateFailureCodeV0_1;
    • QuoteCandidateOutcomeV0_1;
    • SelectedQuoteDigestV0_1;
    • QuoteCollectionResultV0_1.

    No public canonicalization helper, hash helper, comparator, catalog factory,
    asset lookup helper, timer helper, second port, runtime Schema, or
    source-binding bypass is added.

    4. Exact dependencies and supply-chain policy

    packages/moss-adapter/package.json adds exactly:

    • @moss-mini-demo/report-schema: workspace:*;
    • canonicalize: 3.0.0.

    The existing server-only: 0.0.1 dependency remains unchanged.

    The adapter consumes only the report-schema public package entry and reuses:

    • AssetSchema;
    • EvmAddressSchema;
    • ProtocolIdSchema;
    • PositiveAmountSchema;
    • UnsignedAmountSchema.

    It does not import report-schema private source or copy those validators.

    RFC 8785 canonicalization uses exact canonicalize@3.0.0:

    SHA-256 uses Node 22 node:crypto over canonical UTF-8 bytes. No second hash
    dependency and no hand-written/approximate canonicalizer is allowed.

    The lockfile may add only:

    • @moss-mini-demo/report-schema: workspace:* to the moss-adapter importer;
    • canonicalize: 3.0.0 to that importer;
    • the exact canonicalize@3.0.0 package/snapshot record.

    No unrelated version refresh is accepted. pnpm-workspace.yaml and the sole
    'sharp@0.34.5': true build permission remain byte-for-byte unchanged. No
    second build-script permission may be added manually or by pnpm.

    5. Untrusted request contract

    QuoteCollectionRequestV0_1 is reject-only caller data:

    type QuoteCollectionRequestV0_1 = Readonly<{
      chainId: 143;
      candidateProtocols: readonly string[];
      allowedProtocols: readonly string[];
      quoteInput: Readonly<{
        method: string;
        account: string;
        params: Readonly<{
          inputAsset: QuoteAssetV0_1;
          outputAsset: QuoteAssetV0_1;
          amountIn: string;
          readonly [additionalKey: string]: JsonValue;
        }>;
      }>;
      inputAsset: QuoteAssetV0_1;
      outputAsset: QuoteAssetV0_1;
      amountIn: string;
    }>;

    Validation:

    • candidate and allowed arrays are non-empty;
    • each ProtocolId uses the accepted lowercase ASCII kebab grammar;
    • each array is unique; no trimming, sorting-as-repair, case folding, or
      deduplication occurs;
    • chainId is exactly 143;
    • account is an exact EIP-55 non-zero address;
    • quoteInput.params.inputAsset, outputAsset, and amountIn exactly
      equal the top-level basis;
    • additional params remain JSON only and cannot replace the required fields.

    The request is cloned once inside a sanitized boundary. Getter, hostile/revoked
    Proxy, cyclic/non-JSON input, duplicate protocol, invalid address, invalid
    amount, wrong chain, or inconsistent duplicated basis throws a new
    MossAdapterError:

    code: INVALID_INPUT
    operation: quote
    cause: absent
    raw message/data: absent
    Quote delegations: 0
    

    Amounts are canonical positive base-10 integer strings no larger than
    2^256 - 1. Number, float, exponent, sign, whitespace, separator, leading
    zero, zero, and over-uint256 forms are rejected. Ranking conversion uses only
    BigInt.

    6. Asset identity and server-owned catalog boundary

    QuoteAssetV0_1 is exactly:

    type QuoteAssetV0_1 =
      | Readonly<{ kind: "NATIVE" }>
      | Readonly<{ kind: "ERC20"; address: EvmAddress }>;

    Rules:

    • chain id is literal 143;
    • native has no address;
    • ERC-20 identity is exact EIP-55 address;
    • zero/sentinel addresses fail;
    • symbol/name/icon/ticker/decimals do not establish identity;
    • no lowercasing, checksum repair, symbol lookup, address inference, or native
      sentinel occurs.

    The catalog is not inside QuoteCollectionRequestV0_1. It is injected
    separately by the server composition owner. #26 provides no loader and commits
    no production catalog/address. A later Web/API composition scope owns loading
    and access control.

    The catalog never claims MAINTAINER_APPROVED. Its provenance is only a
    source-channel classification and does not prove approval, correctness, or live
    evidence:

    type DecimalsRecordV0_1 =
      | Readonly<{ status: "KNOWN"; value: number }>
      | Readonly<{ status: "UNKNOWN" }>;
    
    type AssetCatalogEntryV0_1 = Readonly<{
      asset: QuoteAssetV0_1;
      decimals: DecimalsRecordV0_1;
    }>;
    
    type AssetCatalogV0_1 = Readonly<{
      schemaVersion: "0.1";
      catalogId: string;
      sourceVersion: string;
      provenance: "SERVER_CONFIGURED" | "SYNTHETIC_TEST";
      sourceReference: string;
      chainId: 143;
      validFrom: string;
      validUntil: string;
      entries: readonly AssetCatalogEntryV0_1[];
    }>;

    Exact scalar grammar:

    • catalogId: ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$, length 1..64;
    • sourceVersion:
      ^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$,
      length 5..32;
    • sourceReference: ^[a-z0-9][a-z0-9._:/@-]{0,127}$, length 1..128;
    • timestamps: calendar-valid canonical UTC with exactly millisecond precision,
      YYYY-MM-DDTHH:mm:ss.sssZ;
    • validFrom < validUntil;
    • freshness at collection start is
      validFrom <= new Date(Date.now()) < validUntil;
    • known decimals are safe integers 0..255;
    • entries are non-empty and asset identities are unique.

    Catalog validation happens before any Quote call. Missing catalog, getter/Proxy,
    wrong schema/chain, malformed source metadata, empty/duplicate entries,
    conflicting duplicate decimals, invalid time interval, not-yet-valid catalog,
    expired catalog, or clone/freeze failure throws the same stable
    INVALID_INPUT / quote error with zero delegations.

    A structurally valid catalog may intentionally omit an asset or record
    UNKNOWN decimals:

    • requested asset absent -> every otherwise allowed candidate is SKIPPED
      with UNKNOWN_ASSET, zero Quote calls;
    • requested asset decimals UNKNOWN -> every otherwise allowed candidate is
      SKIPPED with UNKNOWN_DECIMALS, zero Quote calls.

    Synthetic tests must use provenance: "SYNTHETIC_TEST". Such a catalog is
    test configuration only, not production/Maintainer-approved asset evidence.
    SERVER_CONFIGURED likewise identifies loading channel only and is not an
    approval claim.

    7. Catalog immutable snapshot and digest

    After validation, the adapter:

    1. creates an owned clone;
    2. sorts entries by canonical asset-key UTF-8 bytes:
      ERC20:<EIP55 address> or NATIVE;
    3. deep-freezes the clone;
    4. canonicalizes this exact catalog payload:
      {
        schemaVersion: "moss-mini-demo/asset-catalog/0.1",
        catalogId,
        sourceVersion,
        provenance,
        sourceReference,
        chainId,
        validFrom,
        validUntil,
        entries: sorted owned entries
      }
      
    5. hashes canonical UTF-8 with SHA-256;
    6. creates digest = "sha256:" + 64 lowercase hex.

    Exact result metadata:

    type AssetCatalogSnapshotV0_1 = Readonly<{
      schemaVersion: "0.1";
      catalogId: string;
      sourceVersion: string;
      provenance: "SERVER_CONFIGURED" | "SYNTHETIC_TEST";
      sourceReference: string;
      chainId: 143;
      validFrom: string;
      validUntil: string;
      entries: readonly AssetCatalogEntryV0_1[];
      digest: `sha256:${string}`;
    }>;

    The digest proves only deterministic content identity of the owned catalog
    snapshot. It does not authenticate who supplied it and is not approval or
    chain evidence.

    The catalog snapshot is included once in the top-level result. Normalized
    quotes reference its identity/digest rather than copying authority claims.

    8. Raw Quote projection and source/derived separation

    Current RawQuote.mossOriginal.value remains complete opaque JsonValue.
    #26 does not rewrite, freeze, or replace it.

    The collector takes one adapter-owned structured clone and extracts:

    type QuoteRawProjectionV0_1 = Readonly<{
      chainId: 143;
      inputAsset: QuoteAssetV0_1;
      outputAsset: QuoteAssetV0_1;
      amountIn: string;
      amountOut: string;
      observableBlockWindow: Readonly<{
        fromBlock: string;
        toBlock: string;
      }>;
    }>;

    Additional raw JSON fields remain in both original raw source and owned
    snapshot, but do not silently become normalized fields.

    Missing fields, unsnapshotable raw data, invalid Unicode/non-I-JSON digest
    material, number/float/exponent/signed/zero/leading-zero/over-uint256 amounts,
    wrong chain, invalid asset, malformed block number, or
    BigInt(fromBlock) > BigInt(toBlock) yields MALFORMED_QUOTE.

    Exact raw retention union:

    type RawQuoteRetentionV0_1 =
      | Readonly<{
          status: "SNAPSHOTTED";
          source: RawQuote;       // exact acquired object identity
          snapshot: RawQuote["mossOriginal"]["value"]; // owned and deep-frozen
        }>
      | Readonly<{
          status: "UNSNAPSHOTTABLE";
          source: RawQuote;       // exact acquired object identity
        }>;

    Only MALFORMED_QUOTE may carry UNSNAPSHOTTABLE. Every other acquired
    outcome has SNAPSHOTTED.

    The adapter never mutates or freezes source. Containers, snapshots,
    catalog, normalized data, timing data, failures, digest payload, outcome array,
    and result are frozen. Freezing a derived container must not recursively freeze
    the source-owned RawQuote; implementation freezes owned fields explicitly.

    9. Exact timing contract

    type QuoteTimingV0_1 = Readonly<{
      observedAt: string; // canonical UTC milliseconds
      monotonicNs: string; // canonical unsigned integer string
      clock: "NODE_PROCESS_HRTIME_V0_1";
    }>;
    • observedAt = new Date(Date.now()).toISOString();
    • monotonicNs = process.hrtime.bigint().toString(10);
    • each candidate gets exactly one terminal timing record;
    • acquired candidates name it acquiredTiming;
    • skipped/acquisition-failed candidates name it terminalTiming.

    Timing is observation metadata. It is excluded from ranking, selected digest,
    catalog digest, and deterministic selection fields.

    #26 does not call action. The monotonic acquisition token only allows a
    future authorized same-process orchestrator to calculate quote-to-action
    elapsed time. Clock/process discontinuity is unprovable; wall-clock subtraction
    must not replace the monotonic calculation.

    10. Closed failure vocabulary and precedence

    QuoteCandidateFailureCodeV0_1 is exactly:

    PROTOCOL_NOT_ALLOWED
    QUOTE_TIMEOUT
    QUOTE_ACQUISITION_FAILED
    MALFORMED_QUOTE
    ASSET_DIRECTION_MISMATCH
    AMOUNT_BASIS_MISMATCH
    UNKNOWN_ASSET
    UNKNOWN_DECIMALS
    

    Global invalid request/catalog throws MossAdapterError(INVALID_INPUT, quote)
    before candidate outcomes exist.

    Per-candidate precedence is exactly:

    1. outside allowed set -> PROTOCOL_NOT_ALLOWED;
    2. requested asset absent -> UNKNOWN_ASSET;
    3. requested decimals unknown -> UNKNOWN_DECIMALS;
    4. 8,000 ms collector deadline -> QUOTE_TIMEOUT;
    5. source reject/throw before deadline -> QUOTE_ACQUISITION_FAILED;
    6. raw snapshot/projection/canonicalization invalid -> MALFORMED_QUOTE;
    7. raw input/output asset differs -> ASSET_DIRECTION_MISMATCH;
    8. raw amountIn differs -> AMOUNT_BASIS_MISMATCH;
    9. otherwise -> ELIGIBLE.

    Exactly one precedence result is retained per candidate. Later checks do not
    overwrite an earlier failure.

    An existing stable source MossAdapterError.code is retained only in the
    QUOTE_ACQUISITION_FAILED branch as
    sourceErrorCode: MossAdapterErrorCode | null. Generic/hostile exceptions
    produce null. No raw exception, cause, message, URL, headers, account,
    params, key, or source object is retained in that branch.

    11. Exact correlated candidate outcome discriminated union

    QuoteCandidateOutcomeV0_1 is exported exactly as the following literal
    union. The three INELIGIBLE variants are separate union members; raw and
    failure are not independent unions.

    type QuoteCandidateOutcomeV0_1 =
      | Readonly<{
          status: "SKIPPED";
          protocolId: string;
          terminalTiming: QuoteTimingV0_1;
          failure: Readonly<{
            code:
              | "PROTOCOL_NOT_ALLOWED"
              | "UNKNOWN_ASSET"
              | "UNKNOWN_DECIMALS";
          }>;
        }>
      | Readonly<{
          status: "ACQUISITION_FAILED";
          protocolId: string;
          terminalTiming: QuoteTimingV0_1;
          failure:
            | Readonly<{ code: "QUOTE_TIMEOUT" }>
            | Readonly<{
                code: "QUOTE_ACQUISITION_FAILED";
                sourceErrorCode: MossAdapterErrorCode | null;
              }>;
        }>
      | Readonly<{
          status: "INELIGIBLE";
          protocolId: string;
          acquiredTiming: QuoteTimingV0_1;
          raw: RawQuoteRetentionV0_1;
          failure: Readonly<{ code: "MALFORMED_QUOTE" }>;
        }>
      | Readonly<{
          status: "INELIGIBLE";
          protocolId: string;
          acquiredTiming: QuoteTimingV0_1;
          raw: Readonly<{
            status: "SNAPSHOTTED";
            source: RawQuote;
            snapshot: RawQuote["mossOriginal"]["value"];
          }>;
          failure: Readonly<{ code: "ASSET_DIRECTION_MISMATCH" }>;
        }>
      | Readonly<{
          status: "INELIGIBLE";
          protocolId: string;
          acquiredTiming: QuoteTimingV0_1;
          raw: Readonly<{
            status: "SNAPSHOTTED";
            source: RawQuote;
            snapshot: RawQuote["mossOriginal"]["value"];
          }>;
          failure: Readonly<{ code: "AMOUNT_BASIS_MISMATCH" }>;
        }>
      | Readonly<{
          status: "ELIGIBLE";
          protocolId: string;
          acquiredTiming: QuoteTimingV0_1;
          raw: Readonly<{
            status: "SNAPSHOTTED";
            source: RawQuote;
            snapshot: RawQuote["mossOriginal"]["value"];
          }>;
          normalized: NormalizedQuoteV0_1;
        }>;

    Correlation is therefore part of the exported TypeScript contract:

    • MALFORMED_QUOTE may pair with either SNAPSHOTTED or
      UNSNAPSHOTTABLE from RawQuoteRetentionV0_1;
    • ASSET_DIRECTION_MISMATCH can pair only with SNAPSHOTTED;
    • AMOUNT_BASIS_MISMATCH can pair only with SNAPSHOTTED;
    • ELIGIBLE can pair only with SNAPSHOTTED and must contain normalized;
    • no branch has optional raw, timing, normalized, or failure fields.

    packages/moss-adapter/test/contracts.test.ts must freeze compile-time evidence:

    1. valid examples for both malformed raw-retention variants and both
      snapshotted mismatch variants use
      satisfies QuoteCandidateOutcomeV0_1;
    2. Extract plus an AssertNever<T extends never> assertion proves each
      UNSNAPSHOTTABLE mismatch intersection is never;
    3. literal assignments for
      UNSNAPSHOTTABLE + ASSET_DIRECTION_MISMATCH and
      UNSNAPSHOTTABLE + AMOUNT_BASIS_MISMATCH are each guarded by
      @ts-expect-error, and typecheck fails if either directive becomes unused.

    Runtime collector tests separately exercise malformed-snapshotted,
    malformed-unsnapshottable, asset-mismatch-snapshotted, and
    amount-mismatch-snapshotted outcomes and assert their exact keys. They also
    assert that no produced asset/amount mismatch has
    raw.status === "UNSNAPSHOTTABLE".

    Every candidate ProtocolId appears once. Outcomes are sorted by ProtocolId
    UTF-8 byte order, not input or settlement order.

    12. Normalized quote contract and lossless ranking

    type NormalizedQuoteV0_1 = Readonly<{
      chainId: 143;
      protocolId: string;
      method: string;
      account: string;
      inputAsset: QuoteAssetV0_1;
      outputAsset: QuoteAssetV0_1;
      inputAmount: string;
      outputAmount: string;
      normalizedAmountOut: string;
      inputDecimals: number;
      outputDecimals: number;
      catalog: Readonly<{
        catalogId: string;
        sourceVersion: string;
        provenance: "SERVER_CONFIGURED" | "SYNTHETIC_TEST";
        sourceReference: string;
        digest: `sha256:${string}`;
      }>;
      observableBlockWindow: Readonly<{
        fromBlock: string;
        toBlock: string;
      }>;
      mossSource: Readonly<{
        provenance: "PINNED_SUBMODULE" | "SYNTHETIC_FAKE";
        upstreamCommit: string;
        integrationCommit: string | null;
        patchsetDigest: string | null;
      }>;
    }>;

    Every eligible candidate has exact request output-asset identity, so all output
    amounts share one catalog decimals record. Normalization is lossless:

    normalizedAmountOut = exact canonical amountOut base-unit string
    comparisonAmount = BigInt(normalizedAmountOut)
    comparisonDecimals = outputDecimals
    

    No division, multiplication, rounding, display unit, cross-asset conversion,
    USD value, symbol, or Number is used. The maximum uint256 value is accepted.

    Winner:

    1. highest BigInt(normalizedAmountOut);
    2. tie -> ProtocolId UTF-8 byte lexicographic ascending;
    3. comparator uses TextEncoder and numeric byte comparison;
    4. no localeCompare, collation, case folding, Unicode normalization,
      candidate order, settlement order, time, random value, or environment input.

    ProtocolId is canonical lowercase ASCII, so its UTF-8 and ASCII bytes coincide.

    13. Port change and exact timeout semantics

    This v3 still requests a new Scope Gate for the disclosed, backward-compatible
    optional signal expansion:

    type QuoteRequestOptionsV0_1 = Readonly<{
      signal?: AbortSignal;
    }>;
    
    MossPort.quote(
      protocolId: string,
      input: QuoteInput,
      options?: QuoteRequestOptionsV0_1,
    ): Promise<RawQuote>;
    
    MossSourceBindings.quote(
      protocolId: string,
      input: QuoteInput,
      options?: QuoteRequestOptionsV0_1,
    ): Promise<Readonly<{
      operation: MossLoadedOperation;
      quote: JsonValue;
    }>>;

    ProductionMossPort, FakeMossPort, and createBoundMossPort forward
    the exact signal. No other method changes. Existing two-argument calls remain
    valid.

    For each allowed, catalog-known candidate:

    1. create one AbortController;
    2. attach fulfillment and rejection drainage handlers to the source promise;
    3. start a fixed 8,000 ms timer immediately around the one port.quote call;
    4. if source settles first, clear timer and commit acquired outcome;
    5. if timer callback runs first while local state is unsettled:
      • call controller.abort("MOSS_MINI_DEMO_QUOTE_TIMEOUT_V0_1");
      • synchronously mark local terminal state;
      • freeze one QUOTE_TIMEOUT outcome;
    6. any later fulfillment/rejection is consumed and discarded;
    7. it cannot append, replace, mutate, rank, digest, or select;
    8. aggregate all candidate tasks using Promise.allSettled.

    Collector terminality is the local immutable state transition in step 5. It
    does not depend on source cooperation.

    Signal propagation guarantees only:

    • the exact signal object reaches Production/Fake/source bindings;
    • signal.aborted === true after deadline;
    • signal.reason === "MOSS_MINI_DEMO_QUOTE_TIMEOUT_V0_1".

    #26 does not claim that the underlying source request was canceled. A
    cooperative source may stop. A signal-ignoring source may still fulfill/reject
    later; both paths are drained. Quote is required to be a read-only operation,
    but this interface cannot prove absence of external effects in an unimplemented
    real source.

    No output or documentation may say “source canceled” or equivalent unless a
    future concrete binding independently proves that behavior.

    A bare Promise.race without preattached late-settlement drainage is
    forbidden.

    14. Exact selected digest and top-level result unions

    type SelectedQuoteDigestV0_1 = Readonly<{
      algorithm: "RFC8785-SHA256";
      value: `sha256:${string}`;
      payload: Readonly<Record<string, JsonValue>>;
    }>;
    
    type QuoteCollectionResultV0_1 =
      | Readonly<{
          status: "SELECTED";
          method: "DETERMINISTIC_CANDIDATE_SELECTION_V0_1";
          catalog: AssetCatalogSnapshotV0_1;
          outcomes: readonly QuoteCandidateOutcomeV0_1[];
          selected: Readonly<{
            protocolId: string;
            digest: SelectedQuoteDigestV0_1;
          }>;
        }>
      | Readonly<{
          status: "NOT_SELECTED";
          method: "DETERMINISTIC_CANDIDATE_SELECTION_V0_1";
          code: "NO_ELIGIBLE_QUOTE";
          catalog: AssetCatalogSnapshotV0_1;
          outcomes: readonly QuoteCandidateOutcomeV0_1[];
        }>;

    SELECTED exists only when exactly one outcome is the deterministic winner.
    The selected protocol references an ELIGIBLE outcome. NOT_SELECTED
    exists only when zero outcomes are eligible. The full candidate list is always
    present.

    The selected digest payload is exactly:

    {
      schemaVersion: "moss-mini-demo/selected-quote-digest/0.1",
      chainId,
      protocolId,
      method,
      account,
      inputAsset,
      outputAsset,
      inputAmount,
      outputAmount,
      catalog: {
        catalogId,
        sourceVersion,
        provenance,
        sourceReference,
        digest
      },
      inputDecimals,
      outputDecimals,
      observableBlockWindow: { fromBlock, toBlock },
      mossSource: {
        provenance,
        upstreamCommit,
        integrationCommit: string | null,
        patchsetDigest: string | null
      },
      rawQuote: adapter-owned raw snapshot
    }
    

    It is passed to canonicalize@3.0.0; canonical UTF-8 bytes are hashed once
    with Node createHash("sha256"). Output is
    sha256: plus exactly 64 lowercase hex characters.

    A canonicalization rejection, undefined result, invalid Unicode, or non-I-JSON
    value yields MALFORMED_QUOTE; no fallback serializer exists.

    The digest is only a deterministic fingerprint of selected Quote context. It
    is not a signature, Capability digest, integrity proof, execution binding, or
    evidence that a later Capability came from this Quote.

    acquiredTiming, terminalTiming, candidate order, settlement order,
    deadline, and wall/monotonic time are excluded from catalog digest, selected
    digest, ranking, and selected subobject.

    15. Frozen RFC 8785 conformance vector with one canonical representation

    The implementation test uses the exact public values.json vector from the
    RFC 8785 reference test corpus:

    Exact reference input text:

    {
      "numbers": [333333333.33333329, 1E30, 4.50, 2e-3, 0.000000000000000000000000001],
      "string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/",
      "literals": [null, true, false]
    }

    The single exact canonical text is the following valid JSON. The U+000F
    position is represented by the literal six ASCII characters \u000f;
    there is no caret notation and no second text representation.

    {"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],"string":"€$\u000f\nA'B\"\\\\\"/"}

    Exact UTF-8 bytes of that canonical text, in lowercase hexadecimal:

    7b 22 6c 69 74 65 72 61 6c 73 22 3a 5b 6e 75 6c 6c 2c 74 72 75 65 2c 66 61 6c 73 65 5d 2c 22 6e
    75 6d 62 65 72 73 22 3a 5b 33 33 33 33 33 33 33 33 33 2e 33 33 33 33 33 33 33 2c 31 65 2b 33 30
    2c 34 2e 35 2c 30 2e 30 30 32 2c 31 65 2d 32 37 5d 2c 22 73 74 72 69 6e 67 22 3a 22 e2 82 ac 24
    5c 75 30 30 30 66 5c 6e 41 27 42 5c 22 5c 5c 5c 5c 5c 22 2f 22 7d
    

    The exact byte count is 118. SHA-256 of those exact bytes is:

    2d5e01a318d0f0879ab568c4be289c8b1f64ef8921a53c6277d5e069978baacb

    The test freezes these four independent equality checks:

    1. JSON.parse(expectedCanonicalText) succeeds, and the decoded control
      character is U+000F;
    2. Buffer.from(expectedCanonicalText, "utf8") is exactly 118 bytes and its
      hex equals the frozen byte vector above;
    3. SHA-256 of those same bytes equals the frozen hash above;
    4. canonicalize@3.0.0(JSON.parse(referenceInputText)) equals
      expectedCanonicalText byte-for-byte.

    The expected text, bytes, and SHA are literal constants taken from the
    reference corpus and independently frozen in the test. Expected values must
    not be regenerated from the implementation under test. There is no fallback
    serializer and no competing golden representation.

    16. Frozen synthetic catalog and selected-Quote golden vectors

    The test catalog source payload is exactly:

    {
      "schemaVersion": "moss-mini-demo/asset-catalog/0.1",
      "catalogId": "synthetic-assets",
      "sourceVersion": "1.0.0",
      "provenance": "SYNTHETIC_TEST",
      "sourceReference": "test/asset-catalog-v1",
      "chainId": 143,
      "validFrom": "2026-01-01T00:00:00.000Z",
      "validUntil": "2027-01-01T00:00:00.000Z",
      "entries": [
        {
          "asset": {
            "kind": "ERC20",
            "address": "0x2222222222222222222222222222222222222222"
          },
          "decimals": { "status": "KNOWN", "value": 6 }
        },
        {
          "asset": { "kind": "NATIVE" },
          "decimals": { "status": "KNOWN", "value": 18 }
        }
      ]
    }

    Expected canonical catalog text:

    {"catalogId":"synthetic-assets","chainId":143,"entries":[{"asset":{"address":"0x2222222222222222222222222222222222222222","kind":"ERC20"},"decimals":{"status":"KNOWN","value":6}},{"asset":{"kind":"NATIVE"},"decimals":{"status":"KNOWN","value":18}}],"provenance":"SYNTHETIC_TEST","schemaVersion":"moss-mini-demo/asset-catalog/0.1","sourceReference":"test/asset-catalog-v1","sourceVersion":"1.0.0","validFrom":"2026-01-01T00:00:00.000Z","validUntil":"2027-01-01T00:00:00.000Z"}

    Expected catalog digest:

    sha256:9f09d6a5e86f4ab1e459a7e0db1fd981afcbafe8cdc880e4e6b8e295dfc9a75e

    The fixed selected-Quote digest payload is exactly:

    {
      "schemaVersion": "moss-mini-demo/selected-quote-digest/0.1",
      "chainId": 143,
      "protocolId": "alpha-protocol",
      "method": "swap",
      "account": "0x1111111111111111111111111111111111111111",
      "inputAsset": { "kind": "NATIVE" },
      "outputAsset": {
        "kind": "ERC20",
        "address": "0x2222222222222222222222222222222222222222"
      },
      "inputAmount": "1000000000000000000",
      "outputAmount": "42000000",
      "catalog": {
        "catalogId": "synthetic-assets",
        "sourceVersion": "1.0.0",
        "provenance": "SYNTHETIC_TEST",
        "sourceReference": "test/asset-catalog-v1",
        "digest": "sha256:9f09d6a5e86f4ab1e459a7e0db1fd981afcbafe8cdc880e4e6b8e295dfc9a75e"
      },
      "inputDecimals": 18,
      "outputDecimals": 6,
      "observableBlockWindow": {
        "fromBlock": "100",
        "toBlock": "101"
      },
      "mossSource": {
        "provenance": "SYNTHETIC_FAKE",
        "upstreamCommit": "1ae6b6322d51fae9104f047efb94e601050b967f",
        "integrationCommit": "1ae6b6322d51fae9104f047efb94e601050b967f",
        "patchsetDigest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
      },
      "rawQuote": {
        "chainId": 143,
        "inputAsset": { "kind": "NATIVE" },
        "outputAsset": {
          "kind": "ERC20",
          "address": "0x2222222222222222222222222222222222222222"
        },
        "amountIn": "1000000000000000000",
        "amountOut": "42000000",
        "observableBlockWindow": {
          "fromBlock": "100",
          "toBlock": "101"
        },
        "synthetic": true
      }
    }

    Expected canonical selected-Quote text:

    {"account":"0x1111111111111111111111111111111111111111","catalog":{"catalogId":"synthetic-assets","digest":"sha256:9f09d6a5e86f4ab1e459a7e0db1fd981afcbafe8cdc880e4e6b8e295dfc9a75e","provenance":"SYNTHETIC_TEST","sourceReference":"test/asset-catalog-v1","sourceVersion":"1.0.0"},"chainId":143,"inputAmount":"1000000000000000000","inputAsset":{"kind":"NATIVE"},"inputDecimals":18,"method":"swap","mossSource":{"integrationCommit":"1ae6b6322d51fae9104f047efb94e601050b967f","patchsetDigest":"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","provenance":"SYNTHETIC_FAKE","upstreamCommit":"1ae6b6322d51fae9104f047efb94e601050b967f"},"observableBlockWindow":{"fromBlock":"100","toBlock":"101"},"outputAmount":"42000000","outputAsset":{"address":"0x2222222222222222222222222222222222222222","kind":"ERC20"},"outputDecimals":6,"protocolId":"alpha-protocol","rawQuote":{"amountIn":"1000000000000000000","amountOut":"42000000","chainId":143,"inputAsset":{"kind":"NATIVE"},"observableBlockWindow":{"fromBlock":"100","toBlock":"101"},"outputAsset":{"address":"0x2222222222222222222222222222222222222222","kind":"ERC20"},"synthetic":true},"schemaVersion":"moss-mini-demo/selected-quote-digest/0.1"}

    Expected selected-Quote digest:

    sha256:b9ee24d4c1566c35b3282f4b15fc4d98551f4c2d199ea8221926bf09c8f37594

    Tests must compare the fixed payload, canonical text, and expected hash
    literally. Recomputing expected values using the code under test is not
    acceptance evidence.

    All addresses, protocol IDs, times, amounts, block values, raw values, catalog
    metadata, and provenance in these vectors are synthetic test data.

    17. Complete deterministic selection and timeout test matrix

    packages/moss-adapter/test/quote.test.ts must cover:

    • exact request clone/reject boundary;
    • catalog separate from request and no self-approved authority field;
    • catalog grammar, clone, sort, freeze, freshness, digest, and non-mutation;
    • missing, duplicate, inconsistent, not-yet-valid, and stale catalog -> stable
      INVALID_INPUT and zero delegations;
    • synthetic catalog explicitly remains SYNTHETIC_TEST;
    • absent asset -> SKIPPED/UNKNOWN_ASSET and zero delegation;
    • unknown decimals -> SKIPPED/UNKNOWN_DECIMALS and zero delegation;
    • raw projection extraction;
    • source RawQuote identity preserved;
    • owned raw snapshot separate and frozen;
    • request/raw asset matching by tagged identity, never symbol;
    • exact amount basis;
    • integer-string/BigInt-only behavior;
    • zero, leading-zero, float, exponent, Number, negative, missing, and
      over-uint256 rejection;
    • missing/invalid/reversed block window;
    • malformed fixed to MALFORMED_QUOTE;
    • input/source getter/Proxy failures sanitized;
    • input, catalog, and RawQuote not mutated;
    • exact timing shape;
    • compile-time valid satisfies examples for every correlated INELIGIBLE
      branch;
    • AssertNever and @ts-expect-error evidence that both unsnapshottable
      mismatch combinations are forbidden by the exported type;
    • exact RFC reference input, canonical text, UTF-8 byte, byte-count, and SHA
      vector from Section 15;
    • exact catalog/selected vectors from Section 16;
    • different timing values yield identical ranking, selected protocol, catalog
      digest, and selected digest.

    packages/moss-adapter/test/selection.test.ts must cover the following exact
    matrix and expected discriminants:

    1. both success / first higher -> first ELIGIBLE, second ELIGIBLE,
      top SELECTED first;
    2. both success / second higher -> top SELECTED second;
    3. exact tie -> byte-ascending protocol selected;
    4. reversed input and settlement order -> same ordered outcome identities,
      selected protocol, catalog digest, and selected digest;
    5. outside allowlist -> SKIPPED/PROTOCOL_NOT_ALLOWED, zero call for it;
    6. unknown asset -> SKIPPED/UNKNOWN_ASSET, zero Quote calls;
    7. unknown decimals -> SKIPPED/UNKNOWN_DECIMALS, zero Quote calls;
    8. cooperative source observes abort -> immutable
      ACQUISITION_FAILED/QUOTE_TIMEOUT;
    9. signal-ignoring late fulfillment -> same timeout outcome, late value drained,
      no mutation/selection pollution;
    10. signal-ignoring late rejection -> same timeout outcome, rejection drained,
      no unhandled rejection;
    11. one timeout plus one eligible -> eligible selected, timeout retained;
    12. one source rejection plus one eligible -> eligible selected, source failure
      retained with safe sourceErrorCode/null;
    13. malformed plus eligible -> malformed INELIGIBLE, eligible selected;
    14. unsnapshotable malformed raw -> UNSNAPSHOTTABLE, no source leak;
    15. asset mismatch -> INELIGIBLE/ASSET_DIRECTION_MISMATCH;
    16. amount mismatch -> INELIGIBLE/AMOUNT_BASIS_MISMATCH;
    17. all acquisition failures -> all outcomes retained, top
      NOT_SELECTED/NO_ELIGIBLE_QUOTE;
    18. mixed skipped/acquisition/ineligible and no eligible -> every outcome
      retained under its single precedence code;
    19. uint256 maximum -> accepted and compared by BigInt;
    20. repeated controlled execution -> identical deterministic selection/digests
      and no input mutation.
      The correlated-union runtime matrix additionally asserts:
    • malformed + snapshotted -> INELIGIBLE/MALFORMED_QUOTE;
    • malformed + unsnapshottable -> INELIGIBLE/MALFORMED_QUOTE;
    • asset mismatch -> INELIGIBLE/ASSET_DIRECTION_MISMATCH with
      raw.status: "SNAPSHOTTED";
    • amount mismatch -> INELIGIBLE/AMOUNT_BASIS_MISMATCH with
      raw.status: "SNAPSHOTTED";
    • neither mismatch path can produce UNSNAPSHOTTABLE.

    Additional regressions:

    • exact AbortSignal and reason forwarded by Production and Fake;
    • existing two-argument quote calls remain valid;
    • five-method port shape unchanged;
    • all M2-04: Create moss-adapter package and public ports #25 source/input sanitization tests remain green;
    • public-entry smoke sees exactly one new runtime export,
      collectAndSelectQuotesV0_1;
    • browser-like import still fails through server-only;
    • no localeCompare and no Number conversion for amounts.

    The aggregate must use Promise.allSettled, retain one terminal outcome per
    candidate, and return byte-ordered frozen outcomes.

    18. Implementation sequence after authorization

    1. Re-fetch and verify exact baseline. Stop if main is not
      a0f3a3e8605812c9f0997059b4a30ea7a736022c.
    2. Create only codex/issue-26-quote-selection and the approved isolated
      worktree.
    3. Add exactly the two dependencies and controlled lockfile records.
    4. Add exact public/internal types and optional signal forwarding without
      changing the five-method port.
    5. Implement catalog validation, owned snapshot, entry sorting, freshness, and
      digest.
    6. Implement request validation and raw Quote projection/retention.
    7. Implement collector-local terminal timeout, signal propagation, late
      drainage, and Promise.allSettled.
    8. Implement lossless normalization, UTF-8 byte comparator, result unions, and
      selected digest.
    9. Add the complete tests and update only listed regressions/smoke.
    10. Inspect exact paths, dependencies, lifecycle/build policy, public exports,
      source immutability, prohibited wording, and fixed vectors.
    11. Run clean-checkout verification.
    12. Create one Draft PR with Closes #26, wait for exact-head CI, post
      Implementation Report and Pre-PR QA Handoff, then freeze the Head.

    No implementation action occurs before a Maintainer explicitly accepts v3 with
    SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED.

    19. Local and CI verification

    Use Node 22.23.1 and pnpm 11.16.0. Final evidence runs from a fresh
    isolated checkout with no pre-existing dist or .next:

    node --version
    pnpm --version
    pnpm install --frozen-lockfile
    pnpm --filter @moss-mini-demo/moss-adapter build
    pnpm --filter @moss-mini-demo/moss-adapter typecheck
    pnpm vitest run packages/moss-adapter/test/quote.test.ts
    pnpm vitest run packages/moss-adapter/test/selection.test.ts
    pnpm vitest run packages/moss-adapter/test
    pnpm test:package-import
    pnpm build
    pnpm typecheck
    pnpm test
    pnpm check
    pnpm test:web:bundle
    pnpm test:web:production
    git diff --check origin/main...HEAD
    git diff --name-only origin/main...HEAD
    git status --short

    Acceptance requires:

    • exact 16-path diff;
    • exact dependency/lockfile change and no build-policy change;
    • frozen install independent of historic artifacts;
    • focused, adapter, package-import, full, browser-bundle, and production tests
      pass;
    • all fixed canonical text/byte/hash vectors pass literally;
    • RFC canonical text parses, encodes to the frozen bytes, hashes to the frozen
      SHA, and equals canonicalize@3.0.0 output;
    • correlated compile-time/type-level and runtime outcome matrices pass;
    • all candidate branches and precedence rules pass;
    • no Number amount path, locale comparator, time-dependent selection, raw
      mutation, late-promise mutation, unhandled rejection, or source-cancellation
      overclaim;
    • existing workflow unchanged and exact pushed-head quality-gate SUCCESS;
    • Draft PR remains Draft for independent QA.

    20. Explicit non-goals

    #26 does not implement:

    • Capability construction;
    • Capability digest/integrity (M2-06: Implement capability construction, amount conversion and integrity #27);
    • simulation acquisition or mapping;
    • Receipt, Outcome, Warning, coverage, ordering, or state continuity;
    • Alignment;
    • Decision, STOP, or MANUAL_REVIEW;
    • report generation;
    • cryptographic Quote-to-Capability binding;
    • action orchestration or quote-to-action elapsed calculation;
    • proof that a source operation was canceled;
    • RPC/live-chain access;
    • wallet, signing, transaction construction, or transaction submission;
    • protocol-specific Kuru/Pancake code;
    • real asset/protocol addresses or production catalog values;
    • Maintainer-approval attestation for catalog data;
    • symbol pricing, USD conversion, slippage optimization, routing, or best-trade
      claims;
    • Web/API orchestration, UI, database, deployment, Clear402, M2-06: Implement capability construction, amount conversion and integrity #27, or later M2
      behavior.

    No ADR, workflow, docs, report-schema source, decision-engine, Fixture,
    Issue/Project state, branch protection, required check, permission, main, Moss
    gitlink, or .gitmodules change is in scope.

    21. Risks, stop conditions, and rollback

    Primary risks:

    • treating caller data as trusted catalog authority;
    • expanding M2-04: Create moss-adapter package and public ports #25 port without explicit Scope Gate;
    • claiming AbortSignal proves source cancellation;
    • late fulfillment/rejection changing terminal state;
    • contradictory optional result fields;
    • losing raw Quote/failure evidence;
    • unapproved asset/decimals defaults;
    • Number precision loss;
    • locale-sensitive ties;
    • incomplete RFC 8785 evidence;
    • timestamps entering selection/digest;
    • raw exception/secret leakage;
    • selection being described as best/safe/bound to Capability.

    Stop and return to Maintainer if:

    • main differs before branch creation;
    • a catalog must claim Maintainer approval or needs real address values;
    • server composition/loading must be implemented in M2-05: Implement quote collection and deterministic selection #26;
    • a source operation is not read-only and timeout behavior could create
      external mutation;
    • signal forwarding requires a sixth port method or required breaking
      parameter;
    • any third dependency, build permission, runtime export, root script,
      seventeenth path, workflow, docs, ADR, Schema, Web/API, or vendor change is
      needed;
    • canonicalize identity/integrity differs;
    • fixed RFC/catalog/selected vectors do not reproduce exactly;
    • raw Quote cannot supply the fixed projection/block window;
    • implementation crosses into Capability, simulation, Alignment, Decision,
      RPC, wallet, signing, or transaction behavior.

    Rollback is one revert of the #26 implementation commit after merge, or removal
    of only the unmerged #26 branch/PR. It must not revert #25/#24, edit main
    directly, change the Moss gitlink, or modify protection/required checks.

    22. Requested Maintainer decision

    Please record one of:

    • SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED, explicitly accepting this v3
      comment, exact 16 paths, two dependencies, public API/unions, separate
      catalog trust semantics, catalog digest/freshness, optional signal forwarding,
      collector-only timeout claims, raw/normalized ownership, deterministic
      selection, exact vectors, non-goals, and stop conditions; or
    • SCOPE_CHANGES_REQUESTED, naming the exact path, dependency, API, trust,
      evidence, vector, or semantic decision that must change.

    Until that decision exists:

    WORK_PLAN_V3_SUBMITTED / AWAITING_MAINTAINER_SCOPE_CONFIRMATION

    No implementation branch, worktree, file change, dependency installation,
    lockfile generation, commit, push, or PR is authorized.

  8. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    SCOPE_CHANGES_REQUESTED - Work Plan v3

    Frozen review object:

    The v2 correlated-union blocker is resolved at plan level. The exported union has separate malformed, asset-mismatch, and amount-mismatch members; both forbidden UNSNAPSHOTTABLE mismatch combinations are unrepresentable. The proposed compile-time satisfies / AssertNever / @ts-expect-error checks and the runtime correlation matrix are adequate. The previously accepted catalog authority, timeout and late-drainage, BigInt, digest, raw-evidence, and Quote-not-execution-evidence boundaries remain intact. The scope remains exactly 16 paths and two dependencies, with no #27 behavior.

    One P0 vector blocker remains in Sections 0 and 15:

    • The reference input and expected canonical JSON encode the U+000F position as backslash-caret-O (hex 5c 5e 4f), not the required six-byte JSON escape: backslash, lowercase u, and four hex digits 000f (hex 5c 75 30 30 30 66).
    • JSON.parse(referenceInputText) fails at line 3 column 22 with a bad escaped character.
    • JSON.parse(expectedCanonicalText) fails at line 1 column 96 with a bad escaped character.
    • The actual expected-text bytes are 115 bytes and contain hex 5c5e4f; the declared vector is 118 bytes and contains 5c7530303066.
    • SHA-256 of the actual expected-text block is c92e902800898ec717a3dd3bee5eb3f256f3ca1b18810feb9cc45759a376fe01, not the declared 2d5e01a318d0f0879ab568c4be289c8b1f64ef8921a53c6277d5e069978baacb.

    Required correction:

    1. Preserve v3 unchanged as audit history and submit a complete replacement Work Plan v4 in a new comment.
    2. In both JSON code blocks, encode U+000F with the six ASCII bytes 5c 75 30 30 30 66 (backslash, lowercase u, then 000f); do not use caret notation or an actual control byte in the comment.
    3. Freeze one representation for which both JSON parses succeed, decoded string contains U+000F, UTF-8 length is 118, hex equals the existing declared vector, SHA-256 equals 2d5e01a318d0f0879ab568c4be289c8b1f64ef8921a53c6277d5e069978baacb, and canonicalize@3.0.0(JSON.parse(referenceInputText)) equals the expected canonical text byte-for-byte.
    4. Keep the accepted correlated unions, compile-time/runtime matrix, exact 16 paths, exact two dependencies, and all v2 trust-boundary decisions unchanged.

    Conclusion: SCOPE_CHANGES_REQUESTED. This is not SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED. Do not create the #26 branch/worktree, modify files or lockfile, install repository dependencies, commit, push, or open a PR. The old #25 execution environment is not cleared under this failed Scope Gate.

  9. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    Final Implementation Work Plan v4 - M2-05 Quote Collection and Deterministic Selection - Awaiting Maintainer Scope Confirmation

    0. Status, history, authority, and exact baseline

    This is a complete, self-contained replacement proposal for the #26
    implementation scope. It supersedes the prior Work Plan for future Scope Gate
    purposes, but does not edit or delete history.

    Historical records retained unchanged:

    This v4 is a full, self-contained replacement proposal for the future Scope
    Gate. It does not edit or delete v1, v2, v3, or any review comment.

    All v3-accepted contracts remain unchanged: the exact 16 paths and two
    dependencies; the sole public entry and optional AbortSignal port expansion;
    server-owned catalog separated from untrusted request data; catalog provenance
    not being Maintainer approval; freshness, immutable snapshot and content digest;
    raw/source versus derived ownership; singular failure precedence; collector
    terminality and late-settlement drainage without claiming source cancellation;
    BigInt-only lossless ranking; deterministic byte tie-break; closed top-level and
    correlated candidate unions; fixed selected digest; all non-goals and stop
    conditions.

    The only v4 correction is the RFC 8785 vector representation. Both JSON code
    blocks now contain the literal six ASCII characters \u000f for U+000F.
    No caret notation and no actual U+000F control byte are present in the comment.
    The publication readback below is part of v4 acceptance.

    Exact planning baseline was rechecked immediately before this comment:

    This comment is planning only. It is not
    SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED, QA PASS, Review,
    READY_TO_MERGE, Merge Gate, or merge authorization. No branch, worktree,
    repository file, dependency, lockfile, commit, push, or PR is created by this
    comment.

    If origin/main changes before branch creation, Builder stops, rescans the
    new main, and requests a new Scope Gate instead of implementing against this
    baseline.

    1. Goal and trust boundary

    Add one server-only, deterministic Quote collection and candidate-selection
    operation to the existing @moss-mini-demo/moss-adapter public entry.

    The operation:

    1. receives untrusted request data separately from server-owned asset catalog
      configuration;
    2. validates chain-143 comparison basis and catalog freshness/integrity;
    3. requests allowed candidate protocol Quotes concurrently;
    4. records exactly one terminal outcome for every candidate;
    5. preserves every acquired RawQuote separately from Mini-Demo-derived
      snapshots and normalization;
    6. selects only among eligible normalized outcomes using the disclosed rule;
    7. returns either one selected candidate or an explicit no-eligible-candidate
      result.

    A Quote is protocol-selection context only. It is not Capability, simulation,
    execution evidence, a best-trade claim, safety, approval, authorization, or a
    cryptographic binding to a later Capability.

    The only public rule identifier is:

    DETERMINISTIC_CANDIDATE_SELECTION_V0_1

    Public output and tests must not call the result best, optimal, safe, approved,
    executable, guaranteed, or equivalent.

    2. Exact 16-path writable list

    Implementation may modify exactly these 16 repository paths and no others:

    1. pnpm-lock.yaml
    2. packages/moss-adapter/package.json
    3. packages/moss-adapter/src/types.ts
    4. packages/moss-adapter/src/source-bindings.ts
    5. packages/moss-adapter/src/production.ts
    6. packages/moss-adapter/src/fake.ts
    7. packages/moss-adapter/src/asset-catalog.ts (new)
    8. packages/moss-adapter/src/quote.ts (new)
    9. packages/moss-adapter/src/selection.ts (new)
    10. packages/moss-adapter/src/index.ts
    11. packages/moss-adapter/test/contracts.test.ts
    12. packages/moss-adapter/test/production.test.ts
    13. packages/moss-adapter/test/fake.test.ts
    14. packages/moss-adapter/test/quote.test.ts (new)
    15. packages/moss-adapter/test/selection.test.ts (new)
    16. packages/moss-adapter/test/public-entry.smoke.mjs

    Exact metadata decisions:

    • types.ts: yes, modified for the closed contracts in Sections 3, 5, 6, 8,
      9, 11, and 12.
    • index.ts: yes, modified only for the exact exports in Section 3.
    • adapter package.json: yes, modified only for the two dependencies in
      Section 4; package name and sole . export remain unchanged.
    • pnpm-lock.yaml: yes, modified only for the adapter importer and exact
      canonicalize@3.0.0 package record.
    • root package.json: no change.
    • pnpm-workspace.yaml: no change.
    • packages/moss-adapter/src/errors.ts: no change.
    • adapter tsconfig files: no change.
    • no fixture file is added; tests use inline synthetic data.

    Any seventeenth path, report-schema source edit, root script change, workflow
    edit, build-permission change, documentation edit, or third dependency requires
    a revised Scope Gate.

    3. Sole public entry, runtime API, and type surface

    The package keeps exactly one public entry:

    @moss-mini-demo/moss-adapter

    No subpath export or private src import is permitted.

    All existing exports remain. The only new runtime export is:

    collectAndSelectQuotesV0_1

    Exact signature:

    function collectAndSelectQuotesV0_1(
      port: MossPort,
      catalog: AssetCatalogV0_1,
      request: QuoteCollectionRequestV0_1,
    ): Promise<QuoteCollectionResultV0_1>;

    The parameter order is part of the boundary:

    The only new type exports are:

    • QuoteRequestOptionsV0_1;
    • QuoteAssetV0_1;
    • AssetCatalogV0_1;
    • AssetCatalogEntryV0_1;
    • DecimalsRecordV0_1;
    • AssetCatalogSnapshotV0_1;
    • QuoteCollectionRequestV0_1;
    • QuoteRawProjectionV0_1;
    • QuoteTimingV0_1;
    • RawQuoteRetentionV0_1;
    • NormalizedQuoteV0_1;
    • QuoteCandidateFailureCodeV0_1;
    • QuoteCandidateOutcomeV0_1;
    • SelectedQuoteDigestV0_1;
    • QuoteCollectionResultV0_1.

    No public canonicalization helper, hash helper, comparator, catalog factory,
    asset lookup helper, timer helper, second port, runtime Schema, or
    source-binding bypass is added.

    4. Exact dependencies and supply-chain policy

    packages/moss-adapter/package.json adds exactly:

    • @moss-mini-demo/report-schema: workspace:*;
    • canonicalize: 3.0.0.

    The existing server-only: 0.0.1 dependency remains unchanged.

    The adapter consumes only the report-schema public package entry and reuses:

    • AssetSchema;
    • EvmAddressSchema;
    • ProtocolIdSchema;
    • PositiveAmountSchema;
    • UnsignedAmountSchema.

    It does not import report-schema private source or copy those validators.

    RFC 8785 canonicalization uses exact canonicalize@3.0.0:

    SHA-256 uses Node 22 node:crypto over canonical UTF-8 bytes. No second hash
    dependency and no hand-written/approximate canonicalizer is allowed.

    The lockfile may add only:

    • @moss-mini-demo/report-schema: workspace:* to the moss-adapter importer;
    • canonicalize: 3.0.0 to that importer;
    • the exact canonicalize@3.0.0 package/snapshot record.

    No unrelated version refresh is accepted. pnpm-workspace.yaml and the sole
    'sharp@0.34.5': true build permission remain byte-for-byte unchanged. No
    second build-script permission may be added manually or by pnpm.

    5. Untrusted request contract

    QuoteCollectionRequestV0_1 is reject-only caller data:

    type QuoteCollectionRequestV0_1 = Readonly<{
      chainId: 143;
      candidateProtocols: readonly string[];
      allowedProtocols: readonly string[];
      quoteInput: Readonly<{
        method: string;
        account: string;
        params: Readonly<{
          inputAsset: QuoteAssetV0_1;
          outputAsset: QuoteAssetV0_1;
          amountIn: string;
          readonly [additionalKey: string]: JsonValue;
        }>;
      }>;
      inputAsset: QuoteAssetV0_1;
      outputAsset: QuoteAssetV0_1;
      amountIn: string;
    }>;

    Validation:

    • candidate and allowed arrays are non-empty;
    • each ProtocolId uses the accepted lowercase ASCII kebab grammar;
    • each array is unique; no trimming, sorting-as-repair, case folding, or
      deduplication occurs;
    • chainId is exactly 143;
    • account is an exact EIP-55 non-zero address;
    • quoteInput.params.inputAsset, outputAsset, and amountIn exactly
      equal the top-level basis;
    • additional params remain JSON only and cannot replace the required fields.

    The request is cloned once inside a sanitized boundary. Getter, hostile/revoked
    Proxy, cyclic/non-JSON input, duplicate protocol, invalid address, invalid
    amount, wrong chain, or inconsistent duplicated basis throws a new
    MossAdapterError:

    code: INVALID_INPUT
    operation: quote
    cause: absent
    raw message/data: absent
    Quote delegations: 0
    

    Amounts are canonical positive base-10 integer strings no larger than
    2^256 - 1. Number, float, exponent, sign, whitespace, separator, leading
    zero, zero, and over-uint256 forms are rejected. Ranking conversion uses only
    BigInt.

    6. Asset identity and server-owned catalog boundary

    QuoteAssetV0_1 is exactly:

    type QuoteAssetV0_1 =
      | Readonly<{ kind: "NATIVE" }>
      | Readonly<{ kind: "ERC20"; address: EvmAddress }>;

    Rules:

    • chain id is literal 143;
    • native has no address;
    • ERC-20 identity is exact EIP-55 address;
    • zero/sentinel addresses fail;
    • symbol/name/icon/ticker/decimals do not establish identity;
    • no lowercasing, checksum repair, symbol lookup, address inference, or native
      sentinel occurs.

    The catalog is not inside QuoteCollectionRequestV0_1. It is injected
    separately by the server composition owner. #26 provides no loader and commits
    no production catalog/address. A later Web/API composition scope owns loading
    and access control.

    The catalog never claims MAINTAINER_APPROVED. Its provenance is only a
    source-channel classification and does not prove approval, correctness, or live
    evidence:

    type DecimalsRecordV0_1 =
      | Readonly<{ status: "KNOWN"; value: number }>
      | Readonly<{ status: "UNKNOWN" }>;
    
    type AssetCatalogEntryV0_1 = Readonly<{
      asset: QuoteAssetV0_1;
      decimals: DecimalsRecordV0_1;
    }>;
    
    type AssetCatalogV0_1 = Readonly<{
      schemaVersion: "0.1";
      catalogId: string;
      sourceVersion: string;
      provenance: "SERVER_CONFIGURED" | "SYNTHETIC_TEST";
      sourceReference: string;
      chainId: 143;
      validFrom: string;
      validUntil: string;
      entries: readonly AssetCatalogEntryV0_1[];
    }>;

    Exact scalar grammar:

    • catalogId: ^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$, length 1..64;
    • sourceVersion:
      ^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)$,
      length 5..32;
    • sourceReference: ^[a-z0-9][a-z0-9._:/@-]{0,127}$, length 1..128;
    • timestamps: calendar-valid canonical UTC with exactly millisecond precision,
      YYYY-MM-DDTHH:mm:ss.sssZ;
    • validFrom < validUntil;
    • freshness at collection start is
      validFrom <= new Date(Date.now()) < validUntil;
    • known decimals are safe integers 0..255;
    • entries are non-empty and asset identities are unique.

    Catalog validation happens before any Quote call. Missing catalog, getter/Proxy,
    wrong schema/chain, malformed source metadata, empty/duplicate entries,
    conflicting duplicate decimals, invalid time interval, not-yet-valid catalog,
    expired catalog, or clone/freeze failure throws the same stable
    INVALID_INPUT / quote error with zero delegations.

    A structurally valid catalog may intentionally omit an asset or record
    UNKNOWN decimals:

    • requested asset absent -> every otherwise allowed candidate is SKIPPED
      with UNKNOWN_ASSET, zero Quote calls;
    • requested asset decimals UNKNOWN -> every otherwise allowed candidate is
      SKIPPED with UNKNOWN_DECIMALS, zero Quote calls.

    Synthetic tests must use provenance: "SYNTHETIC_TEST". Such a catalog is
    test configuration only, not production/Maintainer-approved asset evidence.
    SERVER_CONFIGURED likewise identifies loading channel only and is not an
    approval claim.

    7. Catalog immutable snapshot and digest

    After validation, the adapter:

    1. creates an owned clone;
    2. sorts entries by canonical asset-key UTF-8 bytes:
      ERC20:<EIP55 address> or NATIVE;
    3. deep-freezes the clone;
    4. canonicalizes this exact catalog payload:
      {
        schemaVersion: "moss-mini-demo/asset-catalog/0.1",
        catalogId,
        sourceVersion,
        provenance,
        sourceReference,
        chainId,
        validFrom,
        validUntil,
        entries: sorted owned entries
      }
      
    5. hashes canonical UTF-8 with SHA-256;
    6. creates digest = "sha256:" + 64 lowercase hex.

    Exact result metadata:

    type AssetCatalogSnapshotV0_1 = Readonly<{
      schemaVersion: "0.1";
      catalogId: string;
      sourceVersion: string;
      provenance: "SERVER_CONFIGURED" | "SYNTHETIC_TEST";
      sourceReference: string;
      chainId: 143;
      validFrom: string;
      validUntil: string;
      entries: readonly AssetCatalogEntryV0_1[];
      digest: `sha256:${string}`;
    }>;

    The digest proves only deterministic content identity of the owned catalog
    snapshot. It does not authenticate who supplied it and is not approval or
    chain evidence.

    The catalog snapshot is included once in the top-level result. Normalized
    quotes reference its identity/digest rather than copying authority claims.

    8. Raw Quote projection and source/derived separation

    Current RawQuote.mossOriginal.value remains complete opaque JsonValue.
    #26 does not rewrite, freeze, or replace it.

    The collector takes one adapter-owned structured clone and extracts:

    type QuoteRawProjectionV0_1 = Readonly<{
      chainId: 143;
      inputAsset: QuoteAssetV0_1;
      outputAsset: QuoteAssetV0_1;
      amountIn: string;
      amountOut: string;
      observableBlockWindow: Readonly<{
        fromBlock: string;
        toBlock: string;
      }>;
    }>;

    Additional raw JSON fields remain in both original raw source and owned
    snapshot, but do not silently become normalized fields.

    Missing fields, unsnapshotable raw data, invalid Unicode/non-I-JSON digest
    material, number/float/exponent/signed/zero/leading-zero/over-uint256 amounts,
    wrong chain, invalid asset, malformed block number, or
    BigInt(fromBlock) > BigInt(toBlock) yields MALFORMED_QUOTE.

    Exact raw retention union:

    type RawQuoteRetentionV0_1 =
      | Readonly<{
          status: "SNAPSHOTTED";
          source: RawQuote;       // exact acquired object identity
          snapshot: RawQuote["mossOriginal"]["value"]; // owned and deep-frozen
        }>
      | Readonly<{
          status: "UNSNAPSHOTTABLE";
          source: RawQuote;       // exact acquired object identity
        }>;

    Only MALFORMED_QUOTE may carry UNSNAPSHOTTABLE. Every other acquired
    outcome has SNAPSHOTTED.

    The adapter never mutates or freezes source. Containers, snapshots,
    catalog, normalized data, timing data, failures, digest payload, outcome array,
    and result are frozen. Freezing a derived container must not recursively freeze
    the source-owned RawQuote; implementation freezes owned fields explicitly.

    9. Exact timing contract

    type QuoteTimingV0_1 = Readonly<{
      observedAt: string; // canonical UTC milliseconds
      monotonicNs: string; // canonical unsigned integer string
      clock: "NODE_PROCESS_HRTIME_V0_1";
    }>;
    • observedAt = new Date(Date.now()).toISOString();
    • monotonicNs = process.hrtime.bigint().toString(10);
    • each candidate gets exactly one terminal timing record;
    • acquired candidates name it acquiredTiming;
    • skipped/acquisition-failed candidates name it terminalTiming.

    Timing is observation metadata. It is excluded from ranking, selected digest,
    catalog digest, and deterministic selection fields.

    #26 does not call action. The monotonic acquisition token only allows a
    future authorized same-process orchestrator to calculate quote-to-action
    elapsed time. Clock/process discontinuity is unprovable; wall-clock subtraction
    must not replace the monotonic calculation.

    10. Closed failure vocabulary and precedence

    QuoteCandidateFailureCodeV0_1 is exactly:

    PROTOCOL_NOT_ALLOWED
    QUOTE_TIMEOUT
    QUOTE_ACQUISITION_FAILED
    MALFORMED_QUOTE
    ASSET_DIRECTION_MISMATCH
    AMOUNT_BASIS_MISMATCH
    UNKNOWN_ASSET
    UNKNOWN_DECIMALS
    

    Global invalid request/catalog throws MossAdapterError(INVALID_INPUT, quote)
    before candidate outcomes exist.

    Per-candidate precedence is exactly:

    1. outside allowed set -> PROTOCOL_NOT_ALLOWED;
    2. requested asset absent -> UNKNOWN_ASSET;
    3. requested decimals unknown -> UNKNOWN_DECIMALS;
    4. 8,000 ms collector deadline -> QUOTE_TIMEOUT;
    5. source reject/throw before deadline -> QUOTE_ACQUISITION_FAILED;
    6. raw snapshot/projection/canonicalization invalid -> MALFORMED_QUOTE;
    7. raw input/output asset differs -> ASSET_DIRECTION_MISMATCH;
    8. raw amountIn differs -> AMOUNT_BASIS_MISMATCH;
    9. otherwise -> ELIGIBLE.

    Exactly one precedence result is retained per candidate. Later checks do not
    overwrite an earlier failure.

    An existing stable source MossAdapterError.code is retained only in the
    QUOTE_ACQUISITION_FAILED branch as
    sourceErrorCode: MossAdapterErrorCode | null. Generic/hostile exceptions
    produce null. No raw exception, cause, message, URL, headers, account,
    params, key, or source object is retained in that branch.

    11. Exact correlated candidate outcome discriminated union

    QuoteCandidateOutcomeV0_1 is exported exactly as the following literal
    union. The three INELIGIBLE variants are separate union members; raw and
    failure are not independent unions.

    type QuoteCandidateOutcomeV0_1 =
      | Readonly<{
          status: "SKIPPED";
          protocolId: string;
          terminalTiming: QuoteTimingV0_1;
          failure: Readonly<{
            code:
              | "PROTOCOL_NOT_ALLOWED"
              | "UNKNOWN_ASSET"
              | "UNKNOWN_DECIMALS";
          }>;
        }>
      | Readonly<{
          status: "ACQUISITION_FAILED";
          protocolId: string;
          terminalTiming: QuoteTimingV0_1;
          failure:
            | Readonly<{ code: "QUOTE_TIMEOUT" }>
            | Readonly<{
                code: "QUOTE_ACQUISITION_FAILED";
                sourceErrorCode: MossAdapterErrorCode | null;
              }>;
        }>
      | Readonly<{
          status: "INELIGIBLE";
          protocolId: string;
          acquiredTiming: QuoteTimingV0_1;
          raw: RawQuoteRetentionV0_1;
          failure: Readonly<{ code: "MALFORMED_QUOTE" }>;
        }>
      | Readonly<{
          status: "INELIGIBLE";
          protocolId: string;
          acquiredTiming: QuoteTimingV0_1;
          raw: Readonly<{
            status: "SNAPSHOTTED";
            source: RawQuote;
            snapshot: RawQuote["mossOriginal"]["value"];
          }>;
          failure: Readonly<{ code: "ASSET_DIRECTION_MISMATCH" }>;
        }>
      | Readonly<{
          status: "INELIGIBLE";
          protocolId: string;
          acquiredTiming: QuoteTimingV0_1;
          raw: Readonly<{
            status: "SNAPSHOTTED";
            source: RawQuote;
            snapshot: RawQuote["mossOriginal"]["value"];
          }>;
          failure: Readonly<{ code: "AMOUNT_BASIS_MISMATCH" }>;
        }>
      | Readonly<{
          status: "ELIGIBLE";
          protocolId: string;
          acquiredTiming: QuoteTimingV0_1;
          raw: Readonly<{
            status: "SNAPSHOTTED";
            source: RawQuote;
            snapshot: RawQuote["mossOriginal"]["value"];
          }>;
          normalized: NormalizedQuoteV0_1;
        }>;

    Correlation is therefore part of the exported TypeScript contract:

    • MALFORMED_QUOTE may pair with either SNAPSHOTTED or
      UNSNAPSHOTTABLE from RawQuoteRetentionV0_1;
    • ASSET_DIRECTION_MISMATCH can pair only with SNAPSHOTTED;
    • AMOUNT_BASIS_MISMATCH can pair only with SNAPSHOTTED;
    • ELIGIBLE can pair only with SNAPSHOTTED and must contain normalized;
    • no branch has optional raw, timing, normalized, or failure fields.

    packages/moss-adapter/test/contracts.test.ts must freeze compile-time evidence:

    1. valid examples for both malformed raw-retention variants and both
      snapshotted mismatch variants use
      satisfies QuoteCandidateOutcomeV0_1;
    2. Extract plus an AssertNever<T extends never> assertion proves each
      UNSNAPSHOTTABLE mismatch intersection is never;
    3. literal assignments for
      UNSNAPSHOTTABLE + ASSET_DIRECTION_MISMATCH and
      UNSNAPSHOTTABLE + AMOUNT_BASIS_MISMATCH are each guarded by
      @ts-expect-error, and typecheck fails if either directive becomes unused.

    Runtime collector tests separately exercise malformed-snapshotted,
    malformed-unsnapshottable, asset-mismatch-snapshotted, and
    amount-mismatch-snapshotted outcomes and assert their exact keys. They also
    assert that no produced asset/amount mismatch has
    raw.status === "UNSNAPSHOTTABLE".

    Every candidate ProtocolId appears once. Outcomes are sorted by ProtocolId
    UTF-8 byte order, not input or settlement order.

    12. Normalized quote contract and lossless ranking

    type NormalizedQuoteV0_1 = Readonly<{
      chainId: 143;
      protocolId: string;
      method: string;
      account: string;
      inputAsset: QuoteAssetV0_1;
      outputAsset: QuoteAssetV0_1;
      inputAmount: string;
      outputAmount: string;
      normalizedAmountOut: string;
      inputDecimals: number;
      outputDecimals: number;
      catalog: Readonly<{
        catalogId: string;
        sourceVersion: string;
        provenance: "SERVER_CONFIGURED" | "SYNTHETIC_TEST";
        sourceReference: string;
        digest: `sha256:${string}`;
      }>;
      observableBlockWindow: Readonly<{
        fromBlock: string;
        toBlock: string;
      }>;
      mossSource: Readonly<{
        provenance: "PINNED_SUBMODULE" | "SYNTHETIC_FAKE";
        upstreamCommit: string;
        integrationCommit: string | null;
        patchsetDigest: string | null;
      }>;
    }>;

    Every eligible candidate has exact request output-asset identity, so all output
    amounts share one catalog decimals record. Normalization is lossless:

    normalizedAmountOut = exact canonical amountOut base-unit string
    comparisonAmount = BigInt(normalizedAmountOut)
    comparisonDecimals = outputDecimals
    

    No division, multiplication, rounding, display unit, cross-asset conversion,
    USD value, symbol, or Number is used. The maximum uint256 value is accepted.

    Winner:

    1. highest BigInt(normalizedAmountOut);
    2. tie -> ProtocolId UTF-8 byte lexicographic ascending;
    3. comparator uses TextEncoder and numeric byte comparison;
    4. no localeCompare, collation, case folding, Unicode normalization,
      candidate order, settlement order, time, random value, or environment input.

    ProtocolId is canonical lowercase ASCII, so its UTF-8 and ASCII bytes coincide.

    13. Port change and exact timeout semantics

    This v4 still requests a new Scope Gate for the disclosed, backward-compatible
    optional signal expansion:

    type QuoteRequestOptionsV0_1 = Readonly<{
      signal?: AbortSignal;
    }>;
    
    MossPort.quote(
      protocolId: string,
      input: QuoteInput,
      options?: QuoteRequestOptionsV0_1,
    ): Promise<RawQuote>;
    
    MossSourceBindings.quote(
      protocolId: string,
      input: QuoteInput,
      options?: QuoteRequestOptionsV0_1,
    ): Promise<Readonly<{
      operation: MossLoadedOperation;
      quote: JsonValue;
    }>>;

    ProductionMossPort, FakeMossPort, and createBoundMossPort forward
    the exact signal. No other method changes. Existing two-argument calls remain
    valid.

    For each allowed, catalog-known candidate:

    1. create one AbortController;
    2. attach fulfillment and rejection drainage handlers to the source promise;
    3. start a fixed 8,000 ms timer immediately around the one port.quote call;
    4. if source settles first, clear timer and commit acquired outcome;
    5. if timer callback runs first while local state is unsettled:
      • call controller.abort("MOSS_MINI_DEMO_QUOTE_TIMEOUT_V0_1");
      • synchronously mark local terminal state;
      • freeze one QUOTE_TIMEOUT outcome;
    6. any later fulfillment/rejection is consumed and discarded;
    7. it cannot append, replace, mutate, rank, digest, or select;
    8. aggregate all candidate tasks using Promise.allSettled.

    Collector terminality is the local immutable state transition in step 5. It
    does not depend on source cooperation.

    Signal propagation guarantees only:

    • the exact signal object reaches Production/Fake/source bindings;
    • signal.aborted === true after deadline;
    • signal.reason === "MOSS_MINI_DEMO_QUOTE_TIMEOUT_V0_1".

    #26 does not claim that the underlying source request was canceled. A
    cooperative source may stop. A signal-ignoring source may still fulfill/reject
    later; both paths are drained. Quote is required to be a read-only operation,
    but this interface cannot prove absence of external effects in an unimplemented
    real source.

    No output or documentation may say “source canceled” or equivalent unless a
    future concrete binding independently proves that behavior.

    A bare Promise.race without preattached late-settlement drainage is
    forbidden.

    14. Exact selected digest and top-level result unions

    type SelectedQuoteDigestV0_1 = Readonly<{
      algorithm: "RFC8785-SHA256";
      value: `sha256:${string}`;
      payload: Readonly<Record<string, JsonValue>>;
    }>;
    
    type QuoteCollectionResultV0_1 =
      | Readonly<{
          status: "SELECTED";
          method: "DETERMINISTIC_CANDIDATE_SELECTION_V0_1";
          catalog: AssetCatalogSnapshotV0_1;
          outcomes: readonly QuoteCandidateOutcomeV0_1[];
          selected: Readonly<{
            protocolId: string;
            digest: SelectedQuoteDigestV0_1;
          }>;
        }>
      | Readonly<{
          status: "NOT_SELECTED";
          method: "DETERMINISTIC_CANDIDATE_SELECTION_V0_1";
          code: "NO_ELIGIBLE_QUOTE";
          catalog: AssetCatalogSnapshotV0_1;
          outcomes: readonly QuoteCandidateOutcomeV0_1[];
        }>;

    SELECTED exists only when exactly one outcome is the deterministic winner.
    The selected protocol references an ELIGIBLE outcome. NOT_SELECTED
    exists only when zero outcomes are eligible. The full candidate list is always
    present.

    The selected digest payload is exactly:

    {
      schemaVersion: "moss-mini-demo/selected-quote-digest/0.1",
      chainId,
      protocolId,
      method,
      account,
      inputAsset,
      outputAsset,
      inputAmount,
      outputAmount,
      catalog: {
        catalogId,
        sourceVersion,
        provenance,
        sourceReference,
        digest
      },
      inputDecimals,
      outputDecimals,
      observableBlockWindow: { fromBlock, toBlock },
      mossSource: {
        provenance,
        upstreamCommit,
        integrationCommit: string | null,
        patchsetDigest: string | null
      },
      rawQuote: adapter-owned raw snapshot
    }
    

    It is passed to canonicalize@3.0.0; canonical UTF-8 bytes are hashed once
    with Node createHash("sha256"). Output is
    sha256: plus exactly 64 lowercase hex characters.

    A canonicalization rejection, undefined result, invalid Unicode, or non-I-JSON
    value yields MALFORMED_QUOTE; no fallback serializer exists.

    The digest is only a deterministic fingerprint of selected Quote context. It
    is not a signature, Capability digest, integrity proof, execution binding, or
    evidence that a later Capability came from this Quote.

    acquiredTiming, terminalTiming, candidate order, settlement order,
    deadline, and wall/monotonic time are excluded from catalog digest, selected
    digest, ranking, and selected subobject.

    15. Frozen RFC 8785 conformance vector with one canonical representation

    The implementation and publication-readback tests use the exact public
    values.json vector from the RFC 8785 reference test corpus:

    Exact reference input text, with U+000F represented by the literal six ASCII
    characters \u000f:

    {
      "numbers": [333333333.33333329, 1E30, 4.50, 2e-3, 0.000000000000000000000000001],
      "string": "\u20ac$\u000f\u000aA'\u0042\u0022\u005c\\\"\/",
      "literals": [null, true, false]
    }

    Exact canonical output text, also with the literal six ASCII characters
    \u000f and no caret notation:

    {"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],"string":"€$\u000f\nA'B\"\\\\\"/"}

    Exact UTF-8 bytes of that canonical text, in lowercase hexadecimal:

    7b 22 6c 69 74 65 72 61 6c 73 22 3a 5b 6e 75 6c 6c 2c 74 72 75 65 2c 66 61 6c 73 65 5d 2c 22 6e
    75 6d 62 65 72 73 22 3a 5b 33 33 33 33 33 33 33 33 33 2e 33 33 33 33 33 33 33 2c 31 65 2b 33 30
    2c 34 2e 35 2c 30 2e 30 30 32 2c 31 65 2d 32 37 5d 2c 22 73 74 72 69 6e 67 22 3a 22 e2 82 ac 24
    5c 75 30 30 30 66 5c 6e 41 27 42 5c 22 5c 5c 5c 5c 5c 22 2f 22 7d
    

    The frozen canonical UTF-8 length is 118. Its SHA-256 is:

    2d5e01a318d0f0879ab568c4be289c8b1f64ef8921a53c6277d5e069978baacb

    The required independent checks are:

    1. JSON.parse(referenceInputText) succeeds;
    2. JSON.parse(expectedCanonicalText) succeeds;
    3. both parsed string values contain decoded U+000F;
    4. Buffer.from(expectedCanonicalText, "utf8") is 118 bytes;
    5. the byte hex contains 5c7530303066 and equals the complete frozen vector;
    6. SHA-256 of those exact bytes equals the frozen hash;
    7. canonicalize@3.0.0(JSON.parse(referenceInputText)) equals
      expectedCanonicalText byte-for-byte.

    Expected values are literal constants and are not regenerated from the
    implementation under test. No actual U+000F byte, caret representation, or
    competing golden text is accepted.

    16. Frozen synthetic catalog and selected-Quote golden vectors

    The test catalog source payload is exactly:

    {
      "schemaVersion": "moss-mini-demo/asset-catalog/0.1",
      "catalogId": "synthetic-assets",
      "sourceVersion": "1.0.0",
      "provenance": "SYNTHETIC_TEST",
      "sourceReference": "test/asset-catalog-v1",
      "chainId": 143,
      "validFrom": "2026-01-01T00:00:00.000Z",
      "validUntil": "2027-01-01T00:00:00.000Z",
      "entries": [
        {
          "asset": {
            "kind": "ERC20",
            "address": "0x2222222222222222222222222222222222222222"
          },
          "decimals": { "status": "KNOWN", "value": 6 }
        },
        {
          "asset": { "kind": "NATIVE" },
          "decimals": { "status": "KNOWN", "value": 18 }
        }
      ]
    }

    Expected canonical catalog text:

    {"catalogId":"synthetic-assets","chainId":143,"entries":[{"asset":{"address":"0x2222222222222222222222222222222222222222","kind":"ERC20"},"decimals":{"status":"KNOWN","value":6}},{"asset":{"kind":"NATIVE"},"decimals":{"status":"KNOWN","value":18}}],"provenance":"SYNTHETIC_TEST","schemaVersion":"moss-mini-demo/asset-catalog/0.1","sourceReference":"test/asset-catalog-v1","sourceVersion":"1.0.0","validFrom":"2026-01-01T00:00:00.000Z","validUntil":"2027-01-01T00:00:00.000Z"}

    Expected catalog digest:

    sha256:9f09d6a5e86f4ab1e459a7e0db1fd981afcbafe8cdc880e4e6b8e295dfc9a75e

    The fixed selected-Quote digest payload is exactly:

    {
      "schemaVersion": "moss-mini-demo/selected-quote-digest/0.1",
      "chainId": 143,
      "protocolId": "alpha-protocol",
      "method": "swap",
      "account": "0x1111111111111111111111111111111111111111",
      "inputAsset": { "kind": "NATIVE" },
      "outputAsset": {
        "kind": "ERC20",
        "address": "0x2222222222222222222222222222222222222222"
      },
      "inputAmount": "1000000000000000000",
      "outputAmount": "42000000",
      "catalog": {
        "catalogId": "synthetic-assets",
        "sourceVersion": "1.0.0",
        "provenance": "SYNTHETIC_TEST",
        "sourceReference": "test/asset-catalog-v1",
        "digest": "sha256:9f09d6a5e86f4ab1e459a7e0db1fd981afcbafe8cdc880e4e6b8e295dfc9a75e"
      },
      "inputDecimals": 18,
      "outputDecimals": 6,
      "observableBlockWindow": {
        "fromBlock": "100",
        "toBlock": "101"
      },
      "mossSource": {
        "provenance": "SYNTHETIC_FAKE",
        "upstreamCommit": "1ae6b6322d51fae9104f047efb94e601050b967f",
        "integrationCommit": "1ae6b6322d51fae9104f047efb94e601050b967f",
        "patchsetDigest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
      },
      "rawQuote": {
        "chainId": 143,
        "inputAsset": { "kind": "NATIVE" },
        "outputAsset": {
          "kind": "ERC20",
          "address": "0x2222222222222222222222222222222222222222"
        },
        "amountIn": "1000000000000000000",
        "amountOut": "42000000",
        "observableBlockWindow": {
          "fromBlock": "100",
          "toBlock": "101"
        },
        "synthetic": true
      }
    }

    Expected canonical selected-Quote text:

    {"account":"0x1111111111111111111111111111111111111111","catalog":{"catalogId":"synthetic-assets","digest":"sha256:9f09d6a5e86f4ab1e459a7e0db1fd981afcbafe8cdc880e4e6b8e295dfc9a75e","provenance":"SYNTHETIC_TEST","sourceReference":"test/asset-catalog-v1","sourceVersion":"1.0.0"},"chainId":143,"inputAmount":"1000000000000000000","inputAsset":{"kind":"NATIVE"},"inputDecimals":18,"method":"swap","mossSource":{"integrationCommit":"1ae6b6322d51fae9104f047efb94e601050b967f","patchsetDigest":"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","provenance":"SYNTHETIC_FAKE","upstreamCommit":"1ae6b6322d51fae9104f047efb94e601050b967f"},"observableBlockWindow":{"fromBlock":"100","toBlock":"101"},"outputAmount":"42000000","outputAsset":{"address":"0x2222222222222222222222222222222222222222","kind":"ERC20"},"outputDecimals":6,"protocolId":"alpha-protocol","rawQuote":{"amountIn":"1000000000000000000","amountOut":"42000000","chainId":143,"inputAsset":{"kind":"NATIVE"},"observableBlockWindow":{"fromBlock":"100","toBlock":"101"},"outputAsset":{"address":"0x2222222222222222222222222222222222222222","kind":"ERC20"},"synthetic":true},"schemaVersion":"moss-mini-demo/selected-quote-digest/0.1"}

    Expected selected-Quote digest:

    sha256:b9ee24d4c1566c35b3282f4b15fc4d98551f4c2d199ea8221926bf09c8f37594

    Tests must compare the fixed payload, canonical text, and expected hash
    literally. Recomputing expected values using the code under test is not
    acceptance evidence.

    All addresses, protocol IDs, times, amounts, block values, raw values, catalog
    metadata, and provenance in these vectors are synthetic test data.

    17. Complete deterministic selection and timeout test matrix

    packages/moss-adapter/test/quote.test.ts must cover:

    • exact request clone/reject boundary;
    • catalog separate from request and no self-approved authority field;
    • catalog grammar, clone, sort, freeze, freshness, digest, and non-mutation;
    • missing, duplicate, inconsistent, not-yet-valid, and stale catalog -> stable
      INVALID_INPUT and zero delegations;
    • synthetic catalog explicitly remains SYNTHETIC_TEST;
    • absent asset -> SKIPPED/UNKNOWN_ASSET and zero delegation;
    • unknown decimals -> SKIPPED/UNKNOWN_DECIMALS and zero delegation;
    • raw projection extraction;
    • source RawQuote identity preserved;
    • owned raw snapshot separate and frozen;
    • request/raw asset matching by tagged identity, never symbol;
    • exact amount basis;
    • integer-string/BigInt-only behavior;
    • zero, leading-zero, float, exponent, Number, negative, missing, and
      over-uint256 rejection;
    • missing/invalid/reversed block window;
    • malformed fixed to MALFORMED_QUOTE;
    • input/source getter/Proxy failures sanitized;
    • input, catalog, and RawQuote not mutated;
    • exact timing shape;
    • compile-time valid satisfies examples for every correlated INELIGIBLE
      branch;
    • AssertNever and @ts-expect-error evidence that both unsnapshottable
      mismatch combinations are forbidden by the exported type;
    • exact RFC reference input, canonical text, UTF-8 byte, byte-count, and SHA
      vector from Section 15;
    • exact catalog/selected vectors from Section 16;
    • different timing values yield identical ranking, selected protocol, catalog
      digest, and selected digest.

    packages/moss-adapter/test/selection.test.ts must cover the following exact
    matrix and expected discriminants:

    1. both success / first higher -> first ELIGIBLE, second ELIGIBLE,
      top SELECTED first;
    2. both success / second higher -> top SELECTED second;
    3. exact tie -> byte-ascending protocol selected;
    4. reversed input and settlement order -> same ordered outcome identities,
      selected protocol, catalog digest, and selected digest;
    5. outside allowlist -> SKIPPED/PROTOCOL_NOT_ALLOWED, zero call for it;
    6. unknown asset -> SKIPPED/UNKNOWN_ASSET, zero Quote calls;
    7. unknown decimals -> SKIPPED/UNKNOWN_DECIMALS, zero Quote calls;
    8. cooperative source observes abort -> immutable
      ACQUISITION_FAILED/QUOTE_TIMEOUT;
    9. signal-ignoring late fulfillment -> same timeout outcome, late value drained,
      no mutation/selection pollution;
    10. signal-ignoring late rejection -> same timeout outcome, rejection drained,
      no unhandled rejection;
    11. one timeout plus one eligible -> eligible selected, timeout retained;
    12. one source rejection plus one eligible -> eligible selected, source failure
      retained with safe sourceErrorCode/null;
    13. malformed plus eligible -> malformed INELIGIBLE, eligible selected;
    14. unsnapshotable malformed raw -> UNSNAPSHOTTABLE, no source leak;
    15. asset mismatch -> INELIGIBLE/ASSET_DIRECTION_MISMATCH;
    16. amount mismatch -> INELIGIBLE/AMOUNT_BASIS_MISMATCH;
    17. all acquisition failures -> all outcomes retained, top
      NOT_SELECTED/NO_ELIGIBLE_QUOTE;
    18. mixed skipped/acquisition/ineligible and no eligible -> every outcome
      retained under its single precedence code;
    19. uint256 maximum -> accepted and compared by BigInt;
    20. repeated controlled execution -> identical deterministic selection/digests
      and no input mutation.
      The correlated-union runtime matrix additionally asserts:
    • malformed + snapshotted -> INELIGIBLE/MALFORMED_QUOTE;
    • malformed + unsnapshottable -> INELIGIBLE/MALFORMED_QUOTE;
    • asset mismatch -> INELIGIBLE/ASSET_DIRECTION_MISMATCH with
      raw.status: "SNAPSHOTTED";
    • amount mismatch -> INELIGIBLE/AMOUNT_BASIS_MISMATCH with
      raw.status: "SNAPSHOTTED";
    • neither mismatch path can produce UNSNAPSHOTTABLE.

    Additional regressions:

    • exact AbortSignal and reason forwarded by Production and Fake;
    • existing two-argument quote calls remain valid;
    • five-method port shape unchanged;
    • all M2-04: Create moss-adapter package and public ports #25 source/input sanitization tests remain green;
    • public-entry smoke sees exactly one new runtime export,
      collectAndSelectQuotesV0_1;
    • browser-like import still fails through server-only;
    • no localeCompare and no Number conversion for amounts.

    The aggregate must use Promise.allSettled, retain one terminal outcome per
    candidate, and return byte-ordered frozen outcomes.

    18. Implementation sequence after authorization

    1. Re-fetch and verify exact baseline. Stop if main is not
      a0f3a3e8605812c9f0997059b4a30ea7a736022c.
    2. Create only codex/issue-26-quote-selection and the approved isolated
      worktree.
    3. Add exactly the two dependencies and controlled lockfile records.
    4. Add exact public/internal types and optional signal forwarding without
      changing the five-method port.
    5. Implement catalog validation, owned snapshot, entry sorting, freshness, and
      digest.
    6. Implement request validation and raw Quote projection/retention.
    7. Implement collector-local terminal timeout, signal propagation, late
      drainage, and Promise.allSettled.
    8. Implement lossless normalization, UTF-8 byte comparator, result unions, and
      selected digest.
    9. Add the complete tests and update only listed regressions/smoke.
    10. Inspect exact paths, dependencies, lifecycle/build policy, public exports,
      source immutability, prohibited wording, and fixed vectors.
    11. Run clean-checkout verification.
    12. Create one Draft PR with Closes #26, wait for exact-head CI, post
      Implementation Report and Pre-PR QA Handoff, then freeze the Head.

    No implementation action occurs before a Maintainer explicitly accepts v4 with
    SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED.

    19. Local and CI verification

    Use Node 22.23.1 and pnpm 11.16.0. Final evidence runs from a fresh
    isolated checkout with no pre-existing dist or .next:

    node --version
    pnpm --version
    pnpm install --frozen-lockfile
    pnpm --filter @moss-mini-demo/moss-adapter build
    pnpm --filter @moss-mini-demo/moss-adapter typecheck
    pnpm vitest run packages/moss-adapter/test/quote.test.ts
    pnpm vitest run packages/moss-adapter/test/selection.test.ts
    pnpm vitest run packages/moss-adapter/test
    pnpm test:package-import
    pnpm build
    pnpm typecheck
    pnpm test
    pnpm check
    pnpm test:web:bundle
    pnpm test:web:production
    git diff --check origin/main...HEAD
    git diff --name-only origin/main...HEAD
    git status --short

    Acceptance requires:

    • exact 16-path diff;
    • exact dependency/lockfile change and no build-policy change;
    • frozen install independent of historic artifacts;
    • focused, adapter, package-import, full, browser-bundle, and production tests
      pass;
    • all fixed canonical text/byte/hash vectors pass literally;
    • RFC canonical text parses, encodes to the frozen bytes, hashes to the frozen
      SHA, and equals canonicalize@3.0.0 output;
    • correlated compile-time/type-level and runtime outcome matrices pass;
    • all candidate branches and precedence rules pass;
    • no Number amount path, locale comparator, time-dependent selection, raw
      mutation, late-promise mutation, unhandled rejection, or source-cancellation
      overclaim;
    • existing workflow unchanged and exact pushed-head quality-gate SUCCESS;
    • Draft PR remains Draft for independent QA.

    20. Explicit non-goals

    #26 does not implement:

    • Capability construction;
    • Capability digest/integrity (M2-06: Implement capability construction, amount conversion and integrity #27);
    • simulation acquisition or mapping;
    • Receipt, Outcome, Warning, coverage, ordering, or state continuity;
    • Alignment;
    • Decision, STOP, or MANUAL_REVIEW;
    • report generation;
    • cryptographic Quote-to-Capability binding;
    • action orchestration or quote-to-action elapsed calculation;
    • proof that a source operation was canceled;
    • RPC/live-chain access;
    • wallet, signing, transaction construction, or transaction submission;
    • protocol-specific Kuru/Pancake code;
    • real asset/protocol addresses or production catalog values;
    • Maintainer-approval attestation for catalog data;
    • symbol pricing, USD conversion, slippage optimization, routing, or best-trade
      claims;
    • Web/API orchestration, UI, database, deployment, Clear402, M2-06: Implement capability construction, amount conversion and integrity #27, or later M2
      behavior.

    No ADR, workflow, docs, report-schema source, decision-engine, Fixture,
    Issue/Project state, branch protection, required check, permission, main, Moss
    gitlink, or .gitmodules change is in scope.

    21. Risks, stop conditions, and rollback

    Primary risks:

    • treating caller data as trusted catalog authority;
    • expanding M2-04: Create moss-adapter package and public ports #25 port without explicit Scope Gate;
    • claiming AbortSignal proves source cancellation;
    • late fulfillment/rejection changing terminal state;
    • contradictory optional result fields;
    • losing raw Quote/failure evidence;
    • unapproved asset/decimals defaults;
    • Number precision loss;
    • locale-sensitive ties;
    • incomplete RFC 8785 evidence;
    • timestamps entering selection/digest;
    • raw exception/secret leakage;
    • selection being described as best/safe/bound to Capability.

    Stop and return to Maintainer if:

    • main differs before branch creation;
    • a catalog must claim Maintainer approval or needs real address values;
    • server composition/loading must be implemented in M2-05: Implement quote collection and deterministic selection #26;
    • a source operation is not read-only and timeout behavior could create
      external mutation;
    • signal forwarding requires a sixth port method or required breaking
      parameter;
    • any third dependency, build permission, runtime export, root script,
      seventeenth path, workflow, docs, ADR, Schema, Web/API, or vendor change is
      needed;
    • canonicalize identity/integrity differs;
    • fixed RFC/catalog/selected vectors do not reproduce exactly;
    • raw Quote cannot supply the fixed projection/block window;
    • implementation crosses into Capability, simulation, Alignment, Decision,
      RPC, wallet, signing, or transaction behavior.

    Rollback is one revert of the #26 implementation commit after merge, or removal
    of only the unmerged #26 branch/PR. It must not revert #25/#24, edit main
    directly, change the Moss gitlink, or modify protection/required checks.

    22. Requested Maintainer decision

    Please record one of:

    • SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED, explicitly accepting this v4
      comment, exact 16 paths, two dependencies, public API/unions, separate
      catalog trust semantics, catalog digest/freshness, optional signal forwarding,
      collector-only timeout claims, raw/normalized ownership, deterministic
      selection, exact vectors, non-goals, and stop conditions; or
    • SCOPE_CHANGES_REQUESTED, naming the exact path, dependency, API, trust,
      evidence, vector, or semantic decision that must change.

    Until that decision exists:

    WORK_PLAN_V4_SUBMITTED / AWAITING_MAINTAINER_SCOPE_CONFIRMATION

    No implementation branch, worktree, file change, dependency installation,
    lockfile generation, commit, push, or PR is authorized.

  10. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    Work Plan v4 Publication Readback Verification - Maintainer Scope Review Requested

    • complete Work Plan v4: M2-05: Implement quote collection and deterministic selection #26 (comment)
    • exact baseline: main@a0f3a3e8605812c9f0997059b4a30ea7a736022c
    • GitHub API readback body SHA-256: f30f88dbe065a257a669d5c68e9f50abb7ae0495bd9232d7a69e5525a7322732
    • GitHub API stored body equals the submitted v4 body byte-for-byte: PASS
    • both JSON blocks contain the literal six-byte \u000f escape: PASS
    • caret representation absent: PASS
    • actual U+000F control byte absent from the comment source: PASS
    • JSON.parse(referenceInputText): PASS
    • JSON.parse(expectedCanonicalText): PASS
    • decoded character in both values: U+000F
    • canonical UTF-8 length: 118 bytes
    • canonical hex contains 5c7530303066: PASS
    • complete canonical hex equals the frozen vector: PASS
    • canonical SHA-256: 2d5e01a318d0f0879ab568c4be289c8b1f64ef8921a53c6277d5e069978baacb
    • cached, previously fetched canonicalize@3.0.0 output equals expected canonical text byte-for-byte: PASS
    • no dependency installation, repository file, lockfile, branch, worktree, commit, push, or PR was created

    v1, v2, v3, and all review records remain unchanged. Please perform a new
    Maintainer Scope Gate against the complete v4 comment.

    Until an explicit SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED record exists:

    WORK_PLAN_V4_SUBMITTED / AWAITING_MAINTAINER_SCOPE_CONFIRMATION

  11. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED

    Authorized planning object:

    Maintainer Scope Gate evidence:

    • both frozen RFC 8785 JSON blocks pass JSON.parse; both decoded strings contain U+000F;
    • expected canonical text is exactly 118 UTF-8 bytes and its complete hex equals the frozen vector;
    • SHA-256 is exactly 2d5e01a318d0f0879ab568c4be289c8b1f64ef8921a53c6277d5e069978baacb;
    • canonicalize@3.0.0(JSON.parse(referenceInputText)) equals the expected canonical text byte-for-byte;
    • the candidate outcome contract retains three correlated INELIGIBLE members, making both unsnapshottable mismatch combinations unrepresentable;
    • compile-time satisfies / AssertNever / @ts-expect-error checks and the correlated runtime matrix are required;
    • the v3-accepted catalog, timeout/late-drainage, BigInt, digest, raw-evidence ownership, deterministic ordering, and Quote-not-execution-evidence boundaries remain unchanged;
    • scope remains exactly the 16 paths in Work Plan v4 Section 2;
    • dependencies remain exactly @moss-mini-demo/report-schema: workspace:* and canonicalize: 3.0.0; the existing server-only dependency remains unchanged;
    • no Capability digest/integrity or other M2-06: Implement capability construction, amount conversion and integrity #27 behavior is authorized.

    Implementation authorization:

    • Builder may create only codex/issue-26-quote-selection in a new isolated worktree from the exact baseline above, after the old M2-04: Create moss-adapter package and public ports #25 execution environment is cleanly removed.
    • The sole public entry remains @moss-mini-demo/moss-adapter; the only new runtime export is collectAndSelectQuotesV0_1.
    • Any seventeenth path, third dependency, additional runtime export, Schema/ADR/workflow/build-permission change, public contract expansion, real integration, or M2-06: Implement capability construction, amount conversion and integrity #27/later-M2 behavior requires a new Scope Gate.
    • The implementation PR must remain Draft through Builder verification and independent QA.

    This record authorizes only the frozen Work Plan v4 implementation scope. It is not QA PASS, Maintainer Review, READY_TO_MERGE, Merge Gate, or merge authorization.

  12. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    EXECUTION_ENVIRONMENT_CLEARED

    Post-authorization local handoff:

    Builder may now create a new isolated #26 worktree and the authorized codex/issue-26-quote-selection branch from that exact baseline. The old #25 branch/worktree must not be reused. If origin/main moves before creation, stop and return for a refreshed Scope Gate.

    This handoff does not start QA, Review, READY_TO_MERGE, Merge Gate, or merge authorization.

  13. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    SCOPE_AMENDMENT_CONFIRMED / PATH_17_IMPLEMENTATION_AUTHORIZED

    Maintainer has independently inspected the current main@a0f3a3e8605812c9f0997059b4a30ea7a736022c version of packages/moss-adapter/test/server-only.test.ts.

    Decision: YES, the seventeenth path is authorized, subject to the exact closed boundary below.

    Reason

    Frozen Work Plan v4 already requires all of the following:

    • the only new runtime export is collectAndSelectQuotesV0_1;
    • the public-entry smoke must observe exactly that one new runtime export;
    • all existing M2-04: Create moss-adapter package and public ports #25 source/input/server-only tests remain green;
    • focused, adapter, package-import, full, browser-bundle, and production tests pass.

    The existing server-only.test.ts independently asserts the complete approved runtime-export allowlist for the source entry. Adding the v4-required runtime export necessarily makes that unchanged allowlist fail. Therefore the original 16-path list omitted one pre-existing test that must be updated to express the already-approved public contract. This amendment corrects the writable-path list; it does not expand product behavior or change the v4 contract.

    Exact amendment

    The authorized writable-path count changes from 16 to 17 by adding exactly:

    packages/moss-adapter/test/server-only.test.ts

    Within this seventeenth path, Builder may make exactly one semantic change:

    • add "collectAndSelectQuotesV0_1" to the expected runtime-export array in keeps the source entry limited to the approved runtime exports, preserving the array's default JavaScript sort order.

    The resulting expected runtime-export list must be exactly:

    MOSS_BUILD_INFO
    MossAdapterError
    collectAndSelectQuotesV0_1
    createFakeMossPort
    createProductionMossPort
    

    No test may be removed, skipped, weakened, renamed, mocked differently, or broadened. No server-only behavior, import condition, subprocess assertion, type-only export assertion, package export map, dependency, production code, or other line in this file may change except formatting strictly required by the one inserted array member.

    Unchanged authority and stop conditions

    Work Plan v4 comment 5201269230, frozen at 2026-08-06T06:42:10Z with body SHA-256 f30f88dbe065a257a669d5c68e9f50abb7ae0495bd9232d7a69e5525a7322732, and the original SCOPE_CONFIRMED / IMPLEMENTATION_AUTHORIZED record remain authoritative in every other respect.

    This is a v4 scope-list correction, not a new API/version decision and not authorization for #27 behavior.

    Any eighteenth path, second change in server-only.test.ts, third dependency, extra runtime export, contract change, workflow change, root-script change, Web/API change, vendor change, or #27 behavior remains a stop condition requiring a new Maintainer decision.

    Required handoff evidence

    The eventual Implementation Report / QA Handoff must include:

    • the exact diff for packages/moss-adapter/test/server-only.test.ts;
    • proof that its only semantic delta is the single allowlist member;
    • exact 17-path changed-file inventory;
    • all v4 focused and full verification results;
    • exact-head CI result.

    This amendment authorizes Builder Qy to continue implementation and run the complete test matrix. It is not QA PASS, READY_TO_MERGE, Merge Gate, or merge authorization.

  14. added a commit that references this issue on Aug 6, 2026
    65753ad
  15. pillowtalk-Qy commented on Aug 6, 2026

    @pillowtalk-Qy
    ContributorAuthor

    POST_MERGE_AUDIT_PASS

    Issue #26 delivery has completed on the exact authorized object.

    Post-merge audit:

    This audit completes Issue #26 only. It does not authorize Issue #27 implementation; #27 may enter planning only after its dependency-gate record and must receive a separate Work Plan Scope Gate before any implementation branch, commit, or PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions