Skip to content

Excise digital tax stamps: licensing, serialised marks, duty-gated release, and enforcement traceability - #36

Merged
munisp merged 5 commits into
devin/nsw-parity-registrationsfrom
devin/excise-tax-stamps
Aug 24, 2026
Merged

munisp merged 5 commits into
devin/nsw-parity-registrationsfrom
devin/excise-tax-stamps

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

Adds the excise / digital tax-stamp domain, which the platform previously did not have at all: grepping the repo for the domain returned exactly one line, an ad valorem exciseRate inside the duty calculation, with no stamp, serial, licensee, facility, SKU or activation concept in the schema or in any of the 104 routers. The comparison this was built from — SICPATRACE Evo, Authentix TransAct, DirectTrace and the EU 2018/574 regulatory floor — is in docs/excise-tax-stamps-parity.md, which defines the eleven capability areas (C1–C11) implemented here and the six innovations (I1–I6).

Stacked on #35 (which is stacked on #33); review those first.

The six innovations all exploit the one thing a standalone stamp vendor cannot have: this platform owns the customs side too.

  • I1 — release gated on duty actually settled. Stamp fulfilment for an import order sums posted duty_payment ledger entries for the declaration and refuses when they don't cover totalDue, in the same currency. Normally clearance and stamping are two separate systems, which is why the clear-but-never-stamped leak exists.
  • I2 — offline-verifiable marks that don't overclaim. A valid HMAC over the mark proves authority minting only, so the status is signature_valid_pending_reconciliation, never "genuine product".
  • I3 — impossible travel. Consecutive scans of one UID implying > 120 km/h flag the mark, retaining both scans; a clone accuses nobody.
  • I4 — stamp liability on the double-entry ledger, so excise reconciles like every other money path.
  • I5 — anonymous consumer scans as an enforcement sensor: no user id, no cookie correlation, no raw IP; coordinates coarsened to two decimals and the locality hashed.
  • I6 — seizure-to-source traversal from a seized mark up through aggregation → order → declaration → BL → manifest → importer/agent and back down to its siblings still in the market.

Fail-closed and honest-failure rules carried over from the audit in #33:

  • Minting refuses to run without a strong signing key (dev/placeholder keys rejected, validated at startup) rather than minting unverifiable marks.
  • Public verification returns unavailable on outage — never authentic and never suspect — and discloses status only, no commercial data.
  • A mark citing a key id we did issue but can no longer load is unavailable, not invalid_signature: our misconfiguration must not read as "this mark is fake".
  • Unknown data is nullable, never a zero or an empty string.

Money-path integrity

payOrder is replay-safe end to end, because a retry after a partial failure was otherwise a second posting of real revenue:

lock excise:pay:<orderId>            // no Redis ⇒ SERVICE_UNAVAILABLE, not an unguarded post
claim order.paymentIdempotencyKey    // conditional update, single winner
if posted ledger entry for key ⇒ reconcile order forward, do not transfer
if order.ledgerTransferId ⇒ verify at bridge, do not transfer
else POST transfer{idempotencyKey} ⇒ persist transferId ⇒ ledger entry ⇒ advance

The idempotencyKey is honoured at the boundary too: tigerbeetle-bridge now dedupes by key under the store mutex and returns the existing transfer, so concurrent replays cannot both post (that service is still an in-memory simulation of TigerBeetle, as its own comment says — unchanged by this PR).

Two defect families from the audit were reintroduced by the first cut of this domain and are fixed here rather than shipped: queries with write side effects (reconciliation, enforcement scan and public verify were .query()s that inserted rows — the mojaloop.getPaymentStatus shape) and a reconciliation variance that was arithmetically meaningless. issued − activated − retired − reportedProduction subtracts two overlapping quantities, so a perfectly reconciled order reported a large negative variance; it is now two separately meaningful numbers, stampVariance (issued vs activated + retired + still-issued) and productionVariance (activated vs reported production), each zero on a clean order.

Verification

server/excise.behavior.test.ts is caller-level against real Postgres and Redis with only the ledger module stubbed, covering: one transfer across repeated payOrder calls and recovery from an already-posted entry; the currency-mismatch, unsettled-duty and ledger-unavailable release gates; terminal-state re-entry; expired and suspended licences refused at action time; a revoked licence not approvable back to active; idempotent activation; resumable minting and two racing mint calls producing no duplicates; a mark refused a second live parent; both variances zero on a clean order; status-only public verification and the signing-outage path; impossible travel with both scans retained; and each traverseSource outcome, including a self-filed declaration traversing with actingAgentUserId: null — absent is not unavailable. Go tests cover sequential and concurrent bridge idempotency.

Typecheck holds at the repo's 72-error baseline with no diagnostic in a changed file. One failure in server/payments.test.ts reproduces identically on the base branch and is pre-existing. Not verified: no UI — this PR is server-side only — and the browser subsystem on the build box was unavailable all session, so nothing here has been seen rendered.

Link to Devin session: https://app.devin.ai/sessions/e68f0a7bf0e04fb8a3ba32ddb8e1fa23
Requested by: @munisp

devin-ai-integration Bot and others added 5 commits August 24, 2026 19:06
Co-Authored-By: Patrick Munis <pmunis@gmail.com>
Co-Authored-By: Patrick Munis <pmunis@gmail.com>
Co-Authored-By: Patrick Munis <pmunis@gmail.com>
Co-Authored-By: Patrick Munis <pmunis@gmail.com>
Co-Authored-By: Patrick Munis <pmunis@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Author
Original prompt from Patrick

Clone, review and analyze this https://github.com/munisp/singlewindow
Afterwhich, using Attach file analyze the codebase and implement the findings
ATTACHMENT:"https://app.devin.ai/attachments/80878dc3-af42-4287-9b79-2ab7bdfe3f03/codebase-defect-discovery-prompt.pdf"

@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@munisp
munisp merged commit 5e8ed25 into devin/nsw-parity-registrations Aug 24, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 1 additional finding in Devin Review. (Configure)

Open in Devin Review

Comment thread server/routers/excise.ts
Comment on lines +907 to +911
const activatedQuantity = marks.filter((mark) => mark.status === "active" || mark.activatedAt !== null).length;
const retiredQuantity = marks.filter((mark) => mark.status === "retired").length;
const stillIssuedQuantity = marks.filter((mark) => mark.status === "issued").length;
const reportedProductionQuantity = reports.reduce((sum, report) => sum + report.quantity, 0);
const stampVariance = issuedQuantity - activatedQuantity - retiredQuantity - stillIssuedQuantity;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Reconciliation reports false stamp shortfall after retiring an active mark

reconcileOrder counts a stamp as activated when activatedAt is set (excise.ts), but a stamp activated and then retired keeps activatedAt while its status becomes retired, so it is counted in both activatedQuantity and retiredQuantity. stampVariance (excise.ts) then reads negative for a fully accounted order. The analytics endpoint counts activation by current status only (excise.ts) and reports zero for the same data.

Suggested change
const activatedQuantity = marks.filter((mark) => mark.status === "active" || mark.activatedAt !== null).length;
const retiredQuantity = marks.filter((mark) => mark.status === "retired").length;
const stillIssuedQuantity = marks.filter((mark) => mark.status === "issued").length;
const reportedProductionQuantity = reports.reduce((sum, report) => sum + report.quantity, 0);
const stampVariance = issuedQuantity - activatedQuantity - retiredQuantity - stillIssuedQuantity;
const issuedQuantity = marks.length;
const activatedQuantity = marks.filter((mark) => mark.activatedAt !== null).length;
const currentlyActiveQuantity = marks.filter((mark) => mark.status === "active").length;
const retiredQuantity = marks.filter((mark) => mark.status === "retired").length;
const stillIssuedQuantity = marks.filter((mark) => mark.status === "issued").length;
const reportedProductionQuantity = reports.reduce((sum, report) => sum + report.quantity, 0);
const stampVariance = issuedQuantity - currentlyActiveQuantity - retiredQuantity - stillIssuedQuantity;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and it's a real one: retireMark sets status = "retired" but leaves activatedAt set, so an activated-then-retired mark is counted in both activatedQuantity and retiredQuantity and stampVariance reads negative on a fully accounted order — while analytics counts activation by current status and reports zero for the same data.

Fixing it slightly differently from the suggestion: the accountability identity moves to mutually exclusive current-status buckets (issued − (active + retired + stillIssued)), and "ever activated" (activatedAt !== null) is kept as the input to productionVariance, since production is reported against activation and retiring the mark afterwards does not unreport it — using currentlyActive there would make productionVariance go negative instead. Both counts are persisted separately and analytics is switched to the same definitions so the two surfaces cannot disagree again, with a behavioural test covering activate → report → retire.

Since this PR is already merged, the fix lands on the follow-on branch (devin/single-window-regulatory-layer) rather than here.

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.

1 participant