@xemahq/decision-plane-nest
v0.3.2
Published
Producer-side SDK for the Xema human-decision primitive. One transport, one wire mirror and one consumed-event contract for every service that opens an ask on decision-api — so a producer writes only the two things that are genuinely its own: what the ask
Downloads
859
Readme
@xemahq/decision-plane-nest
The producer side of the Xema human-decision primitive.
A service that needs a human to decide something opens an ask on decision-api
and acts when somebody answers. Every such service needs the same five calls,
the same envelope discipline and the same org header.
⚠️ THIS PACKAGE STILL HAS ZERO CONSUMERS. Re-measured 2026-09-07, across
all 28 repositories enumerated from
.gitmodules, atorigin/develop.
"@xemahq/decision-plane-nest"resolves in exactly one MANIFEST — its own. Every other hit fleet-wide is prose: this README,src/wire.ts's header,.claude/rules/decision-primitive.md, and a docblock inauthorization-apiexplaining why that service does not use it.from '@xemahq/decision-plane-nest'occurs in no executable file anywhere. Positive control, same query shape:"@xemahq/decision-internal-api-client"resolves in 9 manifests inrepos/xema-baseand 1 inrepos/xema-cultivars.Meanwhile eight services reach the decision plane today, every one of them through the GENERATED client
@xemahq/decision-internal-api-client.The SPLIT this package implements is right — extract the ask, leave the resume, per
.claude/rules/decision-primitive.md.What sub-plan
06item 1d landed here on 2026-09-07, and what it did notLANDED. The fail-open
isAvailable()probe,DecisionPlaneAbsentErrorand the@Optional()service-registry injection are DELETED — machinery for a resting state that does not occur. Re-measured by PARSING all eightdistribution.lock.jsoninrepos-infra/xema-distributionsrather than grepping them:decision-apiships in all eight, carried by thedecisionbiome at"installPolicy": "required"in every one,minimalandappliance-leanincluded. Two-sided control, same parse:billing-apiis in two of the eight. Andsrc/wire.ts's 230-line hand mirror of the wire is deleted in favour of importing@xemahq/kernel-contracts/decision, which is the authority and which the carved catalog floor (34.2.0) actually resolves.NOT LANDED — and the blocker is NOT the one previously recorded here. This box used to name D-23-3 as blocking the error rebase. D-23-3 is DISCHARGED: item 8 landed
XemaErrorand one RFC 9457 projection. What blocks the rebase is a PUBLISH —@xemahq/kernel-contracts/httpis in no published version, 36.1.0 included, while this repository's lockfile resolves 34.2.0. Seesrc/errors.ts, which records the measurement and the sequencing.NOT LANDED — the transport. Pointing this façade at the generated client so those eight services can adopt it is still an L1 → L2 edge: this package is layer 1 (
xema-kernel-sdk) and@xemahq/decision-internal-api-clientis layer 2 (xema-base). Keeping it at L1 overTypedServiceClient— which is what it does — is legal by layer and does not solve the problem: it asks a producer to move OFF the canonical generated-client transport, which is whyauthorization-apideclined it in writing. That is sub-plan03's repository-topology question, and it is the one blocker that genuinely stands.
@Module({ imports: [DecisionPlaneModule], providers: [MyAskService] })
export class MyDecisionPlaneModule {}
const decision = await this.decisions.open(orgId, {
idempotencyKey: contentHashOfTheThing,
kind: DecisionKind.CONFIRM,
title: 'Send the drafted reply?',
subjectResourceRef: `resource:mailbox:${mailboxId}`,
subjectClassification: DecisionSubjectClassification.CONFIDENTIAL,
answerCapabilityRef: 'mailops:mail.dispatch@1',
producerRef: holdId,
recipients: [{ kind: RecipientKind.HUMAN, target: { userId: ownerId } }],
options: [ /* … */ ],
});What it deliberately does NOT do
The resume. Reacting to a committed answer is safety-critical and genuinely
producer-specific: the capability gate replays an invocation under a
compare-and-set, mailops dispatches mail through the capability plane,
biome-host-api resumes a lifecycle approval. Extract the ask; leave the
resume is the program's rule, and an SDK that swallowed the resume would be
centralising the half that must not be.
(This sentence named "the Store flips a version lifecycle under its own claim".
Measured 2026-09-05: repos/xema-store-api has no human-decision producer
at all — its only decision symbol is PolicyDecisionKind, the PDP verdict,
which is a different concept the leftmost-substring grep conflates. Replaced
with a resume that exists.)
Delivery. decision-api emits; user-hub-api owns materialisation,
per-category preferences, presence gating and every channel adapter. Nothing
here knows a recipient prefers email.
Withdrawal, and why it exists
decision-api runs no sweeper — every terminal status is DERIVED, which only
ends an ask that has a DEADLINE — and producers routinely open asks without one,
for good reasons (a review nobody got to must not become un-approvable by the
passage of time). Together that makes an unanswered ask immortal.
So withdraw() is not a convenience. A producer that knows its subject is gone
must call it. (This sentence read "and both live producers now do — from a
mailbox disconnect and from a listing archive". This package has zero
consumers, so nothing calls withdraw() on it; three of the seven real
producers call close/withdraw through the generated client instead —
biome-host-api, connector-gateway-api and mailops.) It is best-effort by design (its callers are mid-way
through something the user asked for) and never silent: 404/409 are the state it
exists to reach, everything else is logged, and
GET /internal/decisions/abandoned is what surfaces the remainder.
The wire mirror, and how it was deleted
src/wire.ts USED TO restate decision-api's vocabulary instead of importing
@xemahq/kernel-contracts/decision. That was not a preference:
~~The two consumers sit in repositories pinned to kernel-contracts 14.1.0 and 13.0.0. The
decisionsubpath first ships in 15.0.0, and raising xema-store-api's pin is not a local edit — measured 2026-08-20, nine packages in that lockfile declarepeer @xemahq/kernel-contracts ^12.0.0.~~
That reason expired and nobody re-derived it. Measured 2026-09-05:
repos/xema-cultivars declares '@xemahq/kernel-contracts': ^35.1.0 and
repos/xema-base declares ^36.0.0 — twenty majors past the stated blocker —
and xema-store-api is not a decision producer at all. The fleet upgrade this
paragraph was waiting for HAPPENED, which is how a deletion condition gets met
without anyone noticing it was met.
That is DONE. src/wire.ts no longer mirrors anything: it re-exports
@xemahq/kernel-contracts/decision, which is the authority, and declares only
the two shapes the kernel does not have — CloseDecisionRequest and the
producer's DecisionView/DecisionCommitView read projection. Measured across
kernel-contracts/src/** at origin/develop, those three names return ZERO
occurrences, so the kernel owns the ask a producer OPENS and not yet the ask it
CLOSES or reads back. That is a gap in the kernel, recorded in
.claude/plans/xema-convergence-program/24-merge-wave-sequencing.md §2, and
when it closes these collapse into imports too.
The deletion condition the old header set — "when the fleet upgrade happens" —
had been MET without anyone noticing, which is how a deletion condition rots.
It was re-derived at the version this repository's own lockfile resolves
(34.2.0, which ships dist/decision) rather than at the one the aggregator
links, because a green aggregator build cannot answer that question.
test/wire-parity.spec.ts is deleted with the mirror rather than ported. It
compared the copied literals against decision-api's prisma/schema.prisma, read
as text by walking up to the aggregator root, and SKIPPED when that file was
absent — which is every standalone clone and every carved CI run, i.e. the one
place the package is actually built. test/wire-is-the-kernel.spec.ts replaces
it with a stronger and cheaper assertion: the SDK's enums must be the KERNEL's
objects by REFERENCE, which a restatement cannot satisfy, and which needs no
sibling repository to run.
registry.contract.ts and test/registry-assignability.spec.ts are deleted
with the probe they existed for.
