Skip to content

docs: disposition the offline enqueue seam as a named rule-1 carve-out - #301

Merged
Yaraslaut merged 3 commits into
masterfrom
docs/offline-enqueue-carve-out
Aug 26, 2026
Merged

docs: disposition the offline enqueue seam as a named rule-1 carve-out#301
Yaraslaut merged 3 commits into
masterfrom
docs/offline-enqueue-carve-out

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Takes the disposition #197 asks for: document the carve-out. The seam does
not move, the queue stays passive, and no framework primitive is built.

What the carve-out says

docs/spec/offline/offline.md gains a closing subsection to "Ownership: who
enqueues" — "Disposition: app-layer by design (the rule-1 carve-out)" — and
the worked example now names it inline, so a reader who stops at the code block
still sees the exception is recorded:

That free function is app-layer by design, not by omission:
examples/IMPLEMENTATION.md rule 1 would otherwise keep this code inside a
model, and Disposition: app-layer by design below is the recorded
carve-out that puts it here.

The subsection makes four claims:

  1. Rule 1 fired; it was not overridden. "Its final clause is If logic can't
    be expressed in a model, that is a finding
    , and that document's prime
    directive says the same of the framework itself. This carve-out is that
    finding's outcome, not an argument that the rule is wrong."
  2. Why a model cannot host it — deliberately the narrow argument, not the
    retracted WASM one: enqueue() is the write path's last act before the wire,
    taken when the wire is unavailable. In the canonical wiring the client keeps
    an in-process LocalBackend, so a client-side model does exist and could
    own the decision; on a remote deployment it does not, and "the one machine
    that must decide 'queue this instead of sending it' is the one machine with no
    model on it."
  3. The boundary, as a two-column table. Seam: probing isOnline()/catching a
    failed execute(), minting the idempotency key, serialising, enqueue(),
    surfacing queue depth, and client-local bookkeeping that keeps this client's
    own
    queued items consistent. Model: validation, authorization, journal dedup,
    conflict classification — everything the payload means once it lands. The last
    seam row "is the sharp edge: it is genuinely domain-shaped, and the carve-out
    sanctions it only in a dedicated app-layer write-path class — never in a
    presenter, a QML bridge, or main()."
  4. Scope and when to revisit. This is the promotion rule's "explicitly
    dispositioned in the spec as app-layer by design" branch, taken at two
    occurrences of the transport-shaped seam and one of the domain-shaped
    version chaining. "A third rung independently growing its own
    enqueue-on-offline path is the trigger to reopen the question of a
    framework-owned outbox dispatcher — a standing disposition must not become the
    reason a third reinvention goes unexamined."

Reference examples — named, not invented

Both already exist on master, so the carve-out points at shipped code:

  • examples/lims/include/lims/offline/field_outbox.hpp — the domain-shaped half
    (local version ledger, base-version stamping, chaining a client's own second
    offline edit onto its first). Replay still goes through
    SampleModel::execute(QueuedCapture).
  • examples/kanban/gui_lib/board_qml_bridge.cpp — the transport-shaped half
    only. It lives in a presenter, which rule 1 names as forbidden for
    domain-shaped code; nothing there is domain-shaped, so it stands as glue
    under rule 2's "pure glue with no domain logic" justification. Anything
    carrying a domain invariant belongs in a FieldOutbox-shaped class instead.

Where it is placed

File Change
examples/IMPLEMENTATION.md Sub-bullet under rule 1's first bullet: "Named carve-out: the offline write-path enqueue seam." States it is the rule's last clause having fired, links the issue and the spec section, gives the boundary in one sentence, and names the sanctioned home (a write-path class, not a presenter).
docs/spec/offline/offline.md New ### closing "Ownership: who enqueues"; the worked example points at it.
examples/lims/…/field_outbox.hpp, examples/lims/README.md Made coherent with the disposition (see drift below).

Quotes verified against master (e8c8364)

  • examples/IMPLEMENTATION.md rule 1 — verbatim, including the final
    clause the issue leans on.
  • docs/spec/offline/offline.md, "Ownership: who enqueues" — "The queue
    is passive", "detecting an offline/failed execute() and calling enqueue()
    is the application's job", "The seam is on the write path, not inside
    morph::offline" — all verbatim, and the worked example is still the free
    function submit() the issue describes.
  • Scoping — kanban README: "offline is desktop-first here"; lims
    README: "inherits kanban's WASM-offline scope limits — desktop-first". The
    issue is right to drop rule 4's WASM clause.
  • kanban's shipped enqueue in board_qml_bridge.cpp (now line 310), doing
    exactly probe → opId → serialise → enqueue → queue depth.

Drift found

Deliberately not done

  • No framework OutboxDispatcher and no change to IOfflineQueue: the queue
    stays passive and the seam stays on the write path, exactly as the spec says.
  • No move of kanban's enqueue out of its QML bridge — it is transport glue, and
    the rule-1 conflict there is soft, as The offline enqueue seam is app-layer by default and has never been dispositioned #197 itself concludes.
  • The wider WASM-clause argument is not resurrected anywhere.
  • No header under include/ touched, so no spec-sync obligation.

bash scripts/check_spec_citations.sh passes (547 references scanned);
clang-format --dry-run -Werror clean on the touched header.

Closes #197

examples/IMPLEMENTATION.md rule 1 forbids domain-shaped logic outside a
model, and docs/spec/offline/offline.md's worked example puts the
enqueue-on-offline decision in a free function. The placement was never
dispositioned, so a reader could not tell an oversight from a sanctioned
exception.

Rule 1's own last clause -- "If logic can't be expressed in a model, that
is a finding" -- already anticipates this outcome. Record the finding's
disposition instead of moving the seam: the queue stays passive and the
seam stays on the write path.

Closes #197
The disposition reads as the closing subsection of "Ownership: who
enqueues", and the worked example now names it inline, so a reader who
stops at the code block still sees the exception is recorded.
field_outbox.hpp justified its placement with rule 4's WASM clause, which
does not apply -- the offline stack is desktop-first by both kanban's and
this rung's own scoping. The honest reason is narrower: this rung deploys
its field client remotely, so the machine making the enqueue decision has
no model on it. Point both the header and the rung's gap-ledger entry at
rule 1's named carve-out.
@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Yaraslaut
Yaraslaut merged commit e576af7 into master Aug 26, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The offline enqueue seam is app-layer by default and has never been dispositioned

1 participant