PostProcessing as a Success Boundary

In the previous guides, we used tags to apply behavior and to select semantics based on capability.

In this guide, we introduce Tagex’s most powerful abstraction: PostProcessing as a guaranteed success path.

The Missing Abstraction in Most Code

In typical Go code, success is implicit. You know something worked because nothing failed.

This leads to patterns like:


if err := normalize(row); err != nil { return err }
if err := applyLimits(row); err != nil { return err }
if err := persist(row); err != nil { return err }

The problem is not correctness — it is structure:

  • Success is inferred, not explicit
  • Logic is scattered
  • Boilerplate grows linearly
  • Data and consequence are disconnected

Tagex’s Guarantee

Tagex introduces a strong invariant:

If Success() runs, all tagged semantics have succeeded.

This is not a convention. It is enforced by the execution model.

Declaring the Success Path

Consider an import pipeline where data must be:

  • Normalized
  • Capability-adjusted
  • Only then persisted

type ImportRow struct {
    SKU   string `norm:"upper"`
    Price string `norm:"currency, locale=nl_NL"`
    Stock int    `cap:"nonnegative"`

    repo *Repository
}

The struct declares all required semantics. No action is taken yet.

Attaching the Consequence


func (r *ImportRow) Success() error {
    return r.repo.UpsertItem(
        r.SKU,
        r.Price,
        r.Stock,
    )
}

This method expresses a single idea:

Persist this row only if everything succeeded.

No flags. No checks. No duplicated logic.

Execution Flow

When ProcessStruct is called:

  1. All tagged directives are executed
  2. Any error aborts processing
  3. Success() runs only if no errors occurred

The success boundary is explicit and reliable.

Why This Is Powerful

  • The struct owns its success path
  • Data and consequence are colocated
  • No caller needs to “remember” what to do next
  • Boilerplate error handling disappears

This turns a plain data structure into a semantic transaction.

Reusability and Composition

Because semantics and consequences are attached to the struct:

  • The same struct can be used in batch jobs
  • In streaming pipelines
  • In tests
  • In one-off tools

The caller does not need to know what “success” means — the struct defines it.

PreProcessing (Briefly)

While this guide focuses on PostProcessing, it is worth noting the complementary role of PreProcessing.

Before() allows a struct to:

  • Prepare context
  • Initialize resources
  • Fail fast before any semantics run

Together, Pre- and PostProcessing form a clean execution envelope.

What This Is (and Is Not)

PostProcessing is not a callback. It is not an event hook. It is not a side-channel.

It is a commit phase that only exists if all declared semantics succeeded.

Looking Ahead

In the next guide, we will step back and look at validation as one specific use of this model — evaluation without mutation or side effects.

Validation turns out to be a very small special case.