Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
2ac63cc
docs(tally): four findings left open when #306 was merged too early
Sep 11, 2026
22990f8
docs(tally): directional case fold, NFC withdrawal reaches every site
Sep 12, 2026
e8d2bad
docs(tally): scope the name fold to its baseline, keep the one proven…
Sep 12, 2026
0f3710d
docs(tally): one name-matching rule per step, and let it be the gated…
Sep 12, 2026
3ba9a43
docs(tally): narrow the 9.8 exception to the case 9.8 actually measured
Sep 12, 2026
96fce0f
docs(tally): withdraw a licence claim I inferred rather than measured
Sep 12, 2026
32f5613
docs(tally): exact-only is the right rule for binding and the wrong o…
Sep 12, 2026
3f7e432
docs(tally): a detector must be wider than a binder, not the same rul…
Sep 12, 2026
29a00c0
docs(tally): the guard ran after the write it was meant to prevent
Sep 12, 2026
8405c0c
Rectify master-create prerequisites and identity policy scope
Sep 12, 2026
df3e8da
Keep unknown-outcome recovery read-only across active guidance
Sep 12, 2026
0fd62ba
docs(tally): keep unknown outcomes out of dispatchable state
Sep 12, 2026
a1d155e
docs(tally): require intent fields before binding existing masters
Sep 12, 2026
4efd8db
docs(tally): align scoped qualification and held recovery across plans
Sep 12, 2026
3e23b3d
docs: keep slash matching directional
Sep 12, 2026
a26d985
docs: limit Silver slash comparison direction
Sep 12, 2026
6e017d2
docs(tally): four findings left open when #306 was merged too early
Sep 11, 2026
bc36768
docs(tally): directional case fold, NFC withdrawal reaches every site
Sep 12, 2026
9f228ee
docs(tally): scope the name fold to its baseline, keep the one proven…
Sep 12, 2026
e49fc5e
docs(tally): one name-matching rule per step, and let it be the gated…
Sep 12, 2026
3067545
docs(tally): narrow the 9.8 exception to the case 9.8 actually measured
Sep 12, 2026
36342e1
docs(tally): withdraw a licence claim I inferred rather than measured
Sep 12, 2026
67ea110
docs(tally): exact-only is the right rule for binding and the wrong o…
Sep 12, 2026
1e1cbc1
docs(tally): a detector must be wider than a binder, not the same rul…
Sep 12, 2026
f6e12a9
docs(tally): the guard ran after the write it was meant to prevent
Sep 12, 2026
2c6bcf9
docs(tally): a measured row is directional, and its reverse is not me…
Sep 12, 2026
cf58441
chore(tally): reseal the surface after rebasing onto #310
Sep 12, 2026
c0ea05b
docs(tally): a fold belongs at lookup, never in the key — and the rac…
Sep 12, 2026
98ec2c6
Merge commit 'b4c029d6f525badcca466ea9ee4a54e52e22677e' into tapish-c…
Sep 12, 2026
d2fb266
Merge commit 'cf58441b99f85f50693162ae2f014a7903510f4e' into tapish-c…
Sep 12, 2026
f023e92
chore(tally): reseal merged 314 surface
Sep 12, 2026
2a8236c
docs(tally): four findings left open when #306 was merged too early
Sep 11, 2026
0e7add7
docs(tally): directional case fold, NFC withdrawal reaches every site
Sep 12, 2026
e470315
docs(tally): scope the name fold to its baseline, keep the one proven…
Sep 12, 2026
dd8c4e6
docs(tally): one name-matching rule per step, and let it be the gated…
Sep 12, 2026
791ee1b
docs(tally): narrow the 9.8 exception to the case 9.8 actually measured
Sep 12, 2026
3127c77
docs(tally): withdraw a licence claim I inferred rather than measured
Sep 12, 2026
9287a32
docs(tally): exact-only is the right rule for binding and the wrong o…
Sep 12, 2026
9a37785
docs(tally): a detector must be wider than a binder, not the same rul…
Sep 12, 2026
c67632f
docs(tally): the guard ran after the write it was meant to prevent
Sep 12, 2026
f71e661
docs(tally): a measured row is directional, and its reverse is not me…
Sep 12, 2026
d867914
docs(tally): a fold belongs at lookup, never in the key — and the rac…
Sep 12, 2026
e6541f3
chore(tally): reseal after rebasing onto master
Sep 12, 2026
396e3b7
Merge commit 'c0ea05b91d034ceed64e4d5da5fe929442d61d23' into tapish-c…
Sep 12, 2026
ed632dc
docs(tally): do not prescribe a recovery that does not exist
Sep 12, 2026
52526b8
Merge branch 'tapish-codex/preserve314-e654-20260912' into tapish-cod…
Sep 12, 2026
c5f4a48
Merge commit 'aa859e91714714a7461826b87ddbfd217e5b2475' into tapish-c…
Sep 12, 2026
9e528d2
Reconcile measured provenance and unavailable create pre-images
Sep 12, 2026
ea3819b
Scope encoding observation to the provenance actually retained
Sep 12, 2026
1e80961
docs(tally): a pre-read is a check, not a mutation-time guarantee
Sep 12, 2026
af4727e
Reconcile policy location and withdraw stale replay acceptance
Sep 12, 2026
97375e2
Merge master into fix/306-followups and reconcile the fold narrative
Sep 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 85 additions & 26 deletions docs/tally/IMPLEMENTATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,16 +585,17 @@ has always named. It may overwrite, may partially update, or may duplicate. `TAL

**Consequences.**

*Positive:* this gives real duplicate prevention without a TDL plugin and without a UDF
fingerprint. For a generate-a-file-the-human-imports design, **re-running the same file is safe.**
*Positive:* the measured byte-identical Journal repeat produced no duplicate without a TDL plugin
or UDF fingerprint. That observation does not qualify a resend after an unknown outcome or an
intervening external edit; the repeated import can alter the existing voucher.

*Not the outbox, and not the narration marker.* Both of those were listed here as unnecessary and
neither is:

- **The durable dispatch intent stays.** `REMOTEID` prevents a duplicate; it does not tell you,
after a crash, *what you sent*. A resend is only safe while the exact key and payload are still
on disk, which is what the `row fsynced before dispatch` invariant and the restart-reconciliation
flow in `docs/agent/README.md` are for.
after a crash, *what you sent*. Preserve the exact key and payload on disk for read-only outcome
reconciliation, as required by `row fsynced before dispatch` and `docs/agent/README.md`.
Retaining them is not permission to resend after an unknown outcome.
- **An independent attribution marker stays.** The returned *attribute* does not echo the client
key — but the key itself survives in any field Tally does not own. The committed capture
`src-tauri/crates/bridge-tally-protocol/tests/fixtures/agent/native-namespaced-journal.utf16le.xml` returns it inside `NARRATION` as
Expand All @@ -611,7 +612,7 @@ proof-of-post claim must account for that — carry your own marker in a field T
or when the payload differs from the original (partial update semantics). Also untested on
licensed or standard TallyPrime.

### 3.3b Master-name matching: case- and separator-insensitive, otherwise exact
### 3.3b Master-name matching: directional alternatives, otherwise exact

**VERIFIED 2026-07-30**, against a ledger named `BRIDGE-PROBE-LEDGER-A` and one named
`ZZ Ram & Sons Pvt Ltd`:
Expand All @@ -627,7 +628,11 @@ licensed or standard TallyPrime.
| `ZZ Ram & Son Pvt Ltd` (singular for plural) | **rejected** |
| entirely different name | **rejected** |

So Tally normalises **case and separators** and is otherwise **exact on letters**.
These rows establish only the supplied candidate against the recorded master;
they do **not** establish a symmetric case/separator normalizer or canonical
fold. Automatic binding is exact-codepoint unless the authoritative protocol
records the particular directional comparison. In particular, do not infer a
slash rule from this baseline.

> **Promoted to `TALLY_PROTOCOL_REFERENCE.md` §9.4b**, which is where observed gateway behaviour
> belongs and which carries the consequences for a writer. This entry stays as the measurement
Expand Down Expand Up @@ -671,14 +676,53 @@ is no natural idempotency: voucher number is not a key.

**What the heading's "narrowed" means.** §3.3a since established that a byte-identical repeat under
the same `REMOTEID` is an **upsert** on the qualified Journal path — `CREATED=0, ALTERED=1`, no
duplicate. So on that path a crash-retry of the *same file* is safe on its own, and the sentence
that used to stand here — that the fingerprint plus an embedded key is "the only thing" preventing a
duplicate — is no longer true where §3.3a applies.

It is still true everywhere §3.3a does not reach, and that is most places: a **different** payload
under the same key is untested (it may overwrite, partially update or duplicate), as is any
non-Journal voucher type, any other SKU, and a retry across a Tally restart or a company boundary.
Name which case you are in before relying on either mechanism.
duplicate. This is the observed outcome of that repeat, not permission to resend after an unknown
outcome: retain the original batch identity and reconcile its outcome first. It does not protect
against an intervening external edit. The sentence that used to stand here — that the fingerprint
plus an embedded key is "the only thing" preventing a duplicate — is no longer true where §3.3a
applies.

**And it is not true anywhere else either.** An earlier revision of this paragraph — mine — said it
"is still true everywhere §3.3a does not reach", which quietly kept the fingerprint alive as a
duplicate-prevention mechanism in every case §3.3a excludes. §3.4a establishes the opposite: a
`(date, amount, ledger-set, voucher-type)` tuple **cannot** distinguish a retry from a legitimate
identical payment, so it prevents no duplicate anywhere. Two paragraphs of one patch contradicting
each other is how a withdrawn mandate comes back.

What is actually true outside §3.3a's reach is narrower and less comfortable — with **one**
narrowly qualified exception, and the qualification is tighter than the first correction made it
look. `TALLY_PROTOCOL_REFERENCE.md` §9.8 measured **one thing**: how a **failed `Alter`** behaves
under Manual numbering with `PREVENTDUPLICATES=Yes`. It was cleanly rejected — `CREATED=0,
ALTERED=0, EXCEPTIONS=1` — where automatic numbering silently duplicated. That is the whole result.

Three limits come with it, and §9.8 states two of them itself:

- **Request shape.** The observation is about a failed `Alter`. §9.8's own rule says *"Do not apply
the failed-`Alter` observation to a different request identity mechanism."* A crash retry sends a
`Create`, which is a different request shape and is **UNVERIFIED**.
Comment thread
lamemustafa marked this conversation as resolved.
Comment thread
lamemustafa marked this conversation as resolved.
- **SKU.** §9.8 carries no licensed qualification for the numbering path. Its later scope
clarification covers a licensed *Journal* `ACTION="Create"` repeat carrying `REMOTEID` and says
in terms that it does **not** establish voucher-number identity, the configured numbering method,
or other request shapes.
- **Voucher type.** Journal only, as everywhere else in this section.

So the honest statement is: **for a failed `Alter` on the measured baseline, Manual numbering
converts a silent duplicate into a clean rejection.** It is not a general duplicate-prevention
mechanism, and a `Create` retry is not covered by it.
Comment thread
lamemustafa marked this conversation as resolved.

Outside §3.3a's `REMOTEID` path and outside that one measured case, **there is no proven
duplicate-prevention mechanism at all.** A **different** payload under the same key is untested (it
may overwrite, partially update or duplicate), as is any non-Journal voucher type, any other SKU,
and a retry across a Tally restart or a company boundary. Name which case you are in, and where it
is neither, stop and involve a human rather than reaching for the tuple.

The tuple is withdrawn in every case. §9.8 does not rehabilitate the fingerprint — it reports how
one failure mode behaves under one setting, which is a different kind of thing entirely.

**Why this needed two corrections.** The first revision withdrew an over-broad claim ("no proven
mechanism anywhere") and replaced it with another one ("Manual + `PREVENTDUPLICATES` is a proven
mechanism"), widening §9.8 past both its request shape and its SKU in the act of narrowing
something else. A claim is not made safe by being a correction.

### 3.4a Undefined UDF fields are silently discarded — **the plan's primary idempotency key does not work as written**

Expand Down Expand Up @@ -773,23 +817,38 @@ fingerprint is **co-primary** rather than secondary. Withdrawn: promoting it doe
to do the job. The tuple is identical for a legitimate recurring or same-day repeat payment, so as
an automatic dedupe it suppresses real vouchers no matter which tier it is placed in — see §3.4a.

What follows instead is narrower and less comfortable: **there is no proven automatic
write-confirmation mechanism for Phase 4.** `REMOTEID` upsert covers a byte-identical repeat on the
Journal path (§3.3a) and nothing beyond it; a destroyed narration marker leaves a write
unattributable, and the honest response to that is to stop and ask a human, not to substitute a
signal that cannot tell the two cases apart.
What follows instead is narrower and less comfortable: **there is no proven mechanism that lets an
automatic dedupe *decision* be made from the fingerprint tuple.** That is not the same claim as "no
proven duplicate-prevention mechanism outside §3.3a" — it overstates the gap, but only just. One
mechanism is proven: `REMOTEID` upsert on a byte-identical repeat on the Journal path (§3.3a). One
narrower observation sits beside it: under Manual numbering with `PREVENTDUPLICATES=Yes`, a
**failed `Alter`** is cleanly rejected rather than silently duplicated — `CREATED=0, ALTERED=0,
EXCEPTIONS=1` (§3.3;
[`TALLY_PROTOCOL_REFERENCE.md` §9.8](TALLY_PROTOCOL_REFERENCE.md#98-voucher-numbering-method-changes-everything--use-manual)).
That is a failed-`Alter` result on §9.8's own baseline, not a general rejection mechanism: §9.8
forbids carrying it to a different request identity mechanism, and a crash retry sends a `Create`.
Neither reaches a destroyed narration marker or a differently-numbered duplicate under automatic
numbering; for those cases the honest response is still to stop and ask a human, not to substitute
the fingerprint as an automatic suppressor — it cannot tell a retry from a legitimate second
payment no matter which carrier is missing.

### 3.5 Identity after write

`LASTMID` is **0** on successful master creates — unusable. Read masters back by normalised
name. `LASTVCHID` is populated for vouchers and usable, subject to a foreign-writer
cross-check. It also accepts non-numeric text without error when parsed back, so validate it.
`LASTMID` is **0** on successful master creates — unusable. Read masters back by name — and
**normalised never means NFC/NFD-normalised**: §9.4b measured Tally matching on exact
codepoints, so normalising before comparing resolves an NFD create onto a distinct
pre-existing NFC master and promotes the wrong object. Which name rule applies is the
SCOPE GATE's question (`PROMPT_PLAYBOOK.md` Phase 4 step 4); on an unqualified licensed SKU
it is exact codepoints and nothing else. `LASTVCHID` is populated for vouchers and usable,
subject to a foreign-writer cross-check. It also accepts non-numeric text without error when parsed back, so validate it.

### 3.6 Master re-create is a silent Alter

Re-sending an identical ledger `ACTION="Create"` returned `CREATED=0, ALTERED=1` — the
existing master was **overwritten** with the retry payload. Pre-read before creating, and
persist `CREATED` and `ALTERED` as distinct outbox outcomes.
existing master was **overwritten** with the retry payload. Persist `CREATED` and `ALTERED` as
distinct outbox outcomes. A pre-read alone does not authorize creation: use the complete-catalogue
and mutation-time prerequisites in `PROMPT_PLAYBOOK.md` Phase 4 step 3a; an unqualified case stays
unresolved without dispatch.

### 3.7 Company pinning is asymmetric — I2

Expand Down Expand Up @@ -1116,7 +1175,7 @@ zero; fail closed or quarantine.
| Modal dialog | Gateway blocked until a human clicks | §5.1 |
| `ClosingBalance` read as a period figure | Wrong balance, presented as correct | §6.4 |
| `ACTION="Alter"` + `REMOTEID` | Creates a duplicate. `Create` upserts a **byte-identical** repeat; a *corrected* payload is UNVERIFIED and may overwrite, partially update or duplicate | §3.3a |
| Master name differing by more than case/separators | Voucher rejected, master NOT auto-created | §3.3b |
| Master name outside an exact or qualified directional comparison | Voucher rejected, master NOT auto-created | §3.3b |
| Omitting `BILLALLOCATIONS.LIST` | Allocation becomes `On Account` with no bill identity | §3.3c |
| Self-referential `$$NumItems` in a collection | Gateway hangs, empty reply | §5.3b |
| `<COMPUTE>` used for a per-request constant | Per-row work; request exceeds deadline | §2.3a |
Expand Down
Loading