A minimal fault substrate: native throw, family-scoped kinds, no Result monad.
  • TypeScript 91.8%
  • Shell 8.2%
Find a file
PerishCode 8bc49a6209
All checks were successful
guard / guard (pull_request) Successful in 24s
guard / guard (push) Successful in 23s
Adopt wrapperless Runseal profile
shield: adopt wrapperless Runseal profile

Land-Source: runseal-profile-foundation@18e2c01960f1775421346d020139954ae7ff934a
2026-08-03 15:18:02 +08:00
.forgejo Adopt wrapperless Runseal profile 2026-08-03 15:18:02 +08:00
.runseal/resources Adopt wrapperless Runseal profile 2026-08-03 15:18:02 +08:00
docs birth: seat the fault substrate 2026-07-20 15:14:50 +08:00
packages/shield ectropy: migrate the fault substrate (#14) 2026-07-28 04:21:12 +00:00
.gitignore birth: seat the fault substrate 2026-07-20 15:14:50 +08:00
AGENTS.md guard: adopt the errors-only Ectropy contract (#19) 2026-07-29 04:35:14 +00:00
deno.json structure: the package lives under packages/shield (#12) 2026-07-22 12:16:22 +00:00
ectropy.toml Add Plumb to the Shield gate (#18) 2026-07-28 09:57:42 +00:00
README.md birth: seat the fault substrate 2026-07-20 15:14:50 +08:00
runseal.toml Adopt wrapperless Runseal profile 2026-08-03 15:18:02 +08:00

shield

A minimal fault substrate for TypeScript. Native throw, family-scoped kinds, no Result monad, no runtime dependencies.

A business domain declares its failure vocabulary once:

export const fault = family("land", {
  dirty: z.object({ branch: z.string() }),
  missing: z.object({ path: z.string() }),
});

The declaration is the single source of truth: kinds and their payload shapes flow into both the runtime factories and the type of every handler. shield consumes the shape and never validates — validation, if you want it, is the business's own call.

Throw at the disease:

shield.assert(status.clean, () => fault.dirty({ branch }));

Recover locally, or dispose at the boundary:

const text = await io.fault.attempt(() => io.read(path), { missing: () => "" });

await run(main).catch(land.fault.consume({
  dirty: (f) => io.fail(`dirty on ${f.meta.branch}`),
  missing: (f) => io.fail(`no path ${f.meta.path}`),
  ahead: (f) => io.fail(`behind by ${f.meta.by}`),
}));

consume is exhaustive: every kind is handled or the compiler stops you. run absorbs every non-fault into a foreign fault, so a handler always faces a fault, and a bug still crashes loudly unless you route it on purpose.

See docs/constitution.md for the full design.