Browse parts and joints by their use cases, inspection scope and limits. Some entries share a component. Occupation-related parts →
A-01ATsugite
Token verification — Token Verify Tsugite
The Token Verify Tsugite (継手, tsugite) checks the claims of a token received from a backstage authentication platform (OIDC/OAuth, JWT). If any one of alg, aud, iss, exp or the flag for whether the signature was verified (signature_valid) does not match, it does not let the token through (fail-closed). It does not verify the signature itself; the caller verifies it and passes signature_valid.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents a login from ending at "I could log in" while what the token verification actually checked stays invisible. It is a check that always stops, at the decision step, tokens with alg: none or an alg that is not allowed, an aud meant for another party, an issuer that is not pinned, or an expired token.
When to use
- You want to check the alg, aud, iss and exp of an OIDC/OAuth token against rules you have set.
- You want to reliably reject tokens with alg: none or an alg that is not allowed (for example, HS256).
- You verify the signature on your own side (with a pinned JWKS or key), and want a pure function with no network access to decide on that result together with the claims.
- You want to pass the result of the token validity decision on to the Kernels for authorization decisions (policy-facts) or for audit and provenance (token-facts).
When not to use
- You want the token's signature itself to be verified. This Tsugite does not verify signatures. It only receives signature_valid (a boolean) from the caller and rejects the token if that value does not say verified.
- You also want it to fetch the IdP or JWKS. Fetching is not included.
- You want nbf, iat or kid checked, or you want to handle tokens whose aud is an array. aud is matched as a single value; an array aud is not handled.
- You want to decide authorization (who is allowed to do what). Authorization decisions are passed to the policy-facts Kernel.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks alg. alg: none and any alg not in the allow list (allowed_algs), for example HS256, are rejected.
- It checks aud. An aud that does not match the allowed aud (allowed_aud), i.e. aud confusion, is rejected.
- It checks iss. Any issuer other than the pinned iss (allowed_iss) is rejected.
- It checks exp. Tokens past their expiry, including the grace period (leeway_seconds, default 0), and tokens without exp are rejected.
- It checks signature_valid. A token whose signature is not verified (signature_valid is not true) is rejected.
- If any one check fails, it returns { ok: false, errors: [...] } and does not let the token through (fail-closed).
What it does not do / limits
- It does not actually verify the token's signature (verification with a pinned JWKS or key). signature_valid is a value the caller verifies and passes in, and this Tsugite trusts that value when deciding.
- It does not fetch the IdP or JWKS. It is a decision-only part with no network access, and it has no port.
- It does not check nbf, iat or kid. It does not handle an array aud either.
- It checks only five claims: alg, aud, iss, exp and signature_valid. The "revocation" in the catalog one-liner is handled in this Tsugite as rejecting expired tokens by exp (including leeway).
- It is not connected to an actual JWKS or key inside this part. They are pinned at deployment.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use an IdP.
How to connect
Input: call verifyClaims(cfg, claims, now) in src/guards.js (a pure function with no network access). Pass cfg with allowed_algs, allowed_aud, allowed_iss and leeway_seconds, claims as { alg, aud, iss, exp, signature_valid }, and now as the verification time (in seconds). Returns: { ok: true, errors: [] } or { ok: false, errors: [...] }. User provides: verification of the token's signature (with a pinned JWKS or key) and the resulting signature_valid, and fetching of the IdP and JWKS. Side effect: none; there is no port and no external side effect.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
A-02ATsugite
Authorization decision reference — Authentication and Authorization Extension Tsugite
The Authentication and Authorization Extension Tsugite (継手, tsugite), for A-02, takes a permission decision issued by an external decision point as a reference, checks it against the pinned permission policies, carries it to the platform, and matches the returned receipt against the contract. It does not evaluate permissions (RBAC/ABAC). The decision is an external one, and receiving PERMIT does not derive any permission authority. If any one check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a permission decision from an external decision point is handed over, this Tsugite prevents carrying a decision under a policy that is not pinned, deriving permission authority from a receipt that presents itself as a decision, and mistaking receipt of a decision for approval.
When to use
- You want to carry a permission decision issued by an external decision point to the platform as a reference (decision_ref), and only within the pinned permission policies (policy_refs).
- You want to match the result by request_id, request_digest and references before accepting it. The DONE result carries decision, decision_ref and permission_authority.
- You want the handover of a decision to cause no external effect. The side effect of a permission decision reference is NONE.
- Even when a platform failure leaves the result unknown, you want to avoid sending the same request_id twice and resolve it with lookup.
When not to use
- You want it to evaluate, with RBAC/ABAC, whether this person may do this. This Tsugite carries the decision of an external decision point as a reference; it does not include any function for evaluating permissions.
- You want to use a received PERMIT as permission authority. Receiving PERMIT does not derive permission authority, and a receipt that claims to have derived permission_authority is not accepted.
- You want to confirm that the decision is correct. It does not check the permission decision itself (that is the role of the external decision and the platform).
- You want a decision point you can use right away. No actual platform (which decision point) is included, and it is undecided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a part for A-02 alone. Under the contract, this Tsugite serves A-02, A-04, A-05, A-06 and A-07 as one part, and the tests and inspection are shared. A-01 is served by the token-verify Tsugite, and A-03 and A-08 by the session Tsugite.
What it checks
- It checks the policy. It does not send a request under a permission policy that is not pinned. An empty list allows nothing.
- It checks subject references and the form of references. It does not send a subject reference in the form of an email address or phone number, a reference containing a path or .. (path traversal), or extra fields.
- A receipt that claims to have derived permission authority is not accepted.
- If the receipt's request_id, request_digest or references do not match the request, the receipt is not accepted. A receipt with a spoofed request_digest is not accepted either.
- A receipt that increases the external effect, or whose effect is UNKNOWN, is not accepted. If a receipt that cannot be accepted arrives, the client stops (HALTED).
- It does not send the same request_id twice. A platform failure is treated as UNKNOWN, and resending is resolved with lookup.
What it does not do / limits
- It does not evaluate permissions. This Tsugite carries the decision of an external decision point as a reference and does not include any RBAC/ABAC evaluation function. The permission decision is the role of the external decision point, and the facts of the permission policy are the role of the Kernel (policy-facts).
- PERMIT is not permission authority. It derives no permission or authority from having received a decision.
- Receiving a decision is not approval (approval: false).
- It does not include an actual platform (which decision point). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not recognize every direct identifier. Detection covers only the typical forms of email addresses and phone numbers.
- It is not a template for how to use a specific IdP or decision point. It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/identity-ext/v1. Input: pass an AuthzDecisionRefRequest to submit(request) on createIdentityExtClient({ cfg, port }); cfg requires all six pinned lists (policy_refs and the others). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries decision, decision_ref and permission_authority in result. Every result has approval: false. User provides: a port to the platform (submit, and optionally lookup). Side effect: NONE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
A-03ATsugite
Session management — Session and Revocation Propagation Tsugite
The Session and Revocation Propagation Tsugite (継手, tsugite) checks expiry, concurrency and known state before sending OPEN, RESUME, TERMINATE and REVOKE operations to a session store, and matches the store's receipt (SessionReceipt) before advancing the session's state. If anything does not match, it does not send and does not treat the session as usable (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Being logged in is often taken for granted, yet sessions past their expiry, sessions over the concurrency limit, and sessions that should have been revoked or terminated can stay in use. This Tsugite checks session issuing, resuming, terminating and revoking before sending, and does not treat a session as usable unless the store's receipt can be matched.
When to use
- You want to issue, resume, terminate and revoke sessions against an external session store, and match the result before advancing the state.
- You want the maximum session length (max_session_seconds) and the limit on sessions in use at the same time (max_concurrency) always enforced at open time.
- You want a session to be judged usable at a given time only when all of these hold: matched, open, within expiry, and no unsettled operation.
- You want a session treated as unusable until resolved when a store failure leaves the result unknown.
When not to use
- You want the existence of a session to stand in for identity verification or authority. A session is neither identity verification nor authority (authority: NOT_DERIVED, approval: false).
- You want to verify the token itself. That is the role of A-01 (token-verify-tsugite); this Tsugite handles only session operations and revocation propagation.
- You want a session store you can use right away. No actual session store is included, and which one to connect to has not been decided.
- You want the session expiry or concurrency limit itself to be decided. Deciding is the role of the Kernels (session-facts, policy-facts, timestamp-facts); this Tsugite only receives the limits and state and checks them.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks concurrency. An OPEN that would exceed the limit on sessions in use at the same time (max_concurrency) is not sent.
- It checks expiry and time. It rejects expiry rollback and time rollback. OPEN must carry an expiry (expiry) and a concurrency limit (concurrency_limit), and operations other than OPEN may not change expiry or concurrency.
- It rejects resuming a revoked or terminated session. If the store returns RESUMED after a revocation, it does not accept it and stops.
- It matches the receipt (SessionReceipt). If the state contradicts the operation (session_state is determined by op; NONE and UNKNOWN are not matched either), the receipt is for another session, or request_digest is spoofed, it is not accepted and the client goes to HALTED from then on.
- It rejects receipts that bring in authority or identity verification. A receipt that derives authority, or that marks identity as verified (VERIFIED) without a verifier, is not accepted.
- It rejects requests that place a raw secret next to a SecretRef.
- A revocation (REVOKE) receipt must carry a propagation reference, and the revocation stays "incomplete" (REVOKED_PROPAGATION_INCOMPLETE) until it has reached every pinned propagation target with evidence. Details are covered in A-08 (revocation propagation).
- A store failure is treated as UNKNOWN, and that session is treated as unusable until resolved.
What it does not do / limits
- A session is neither identity verification nor authority. Every result carries authority: NOT_DERIVED and approval: false.
- It does not verify tokens. That is the role of A-01 (token-verify-tsugite).
- It does not include an actual session store or propagation targets (each service's revocation intake). Which to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- It does not decide session limits or rules. Deciding is the role of the Kernels (session-facts, policy-facts, timestamp-facts); this Tsugite only checks the limits and state it receives.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED). Note that some points, such as the fields required for OPEN, are stricter than the contract.
- A-03 and A-08 are one part under the contract. Each ID's row (A-03 and A-08) points to this part.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a session store.
How to connect
Contract: erabazu.candidate/certified-tsugite/request-receipt-group/v1. Input: pass a SessionOpRequest (OPEN, RESUME, TERMINATE or REVOKE) to apply(request) on createSessionClient({ cfg, port }); isUsable(session_ref, now) decides whether a session can be used. Returns: one of APPLIED, REFUSED, UNKNOWN, RECEIPT_REJECTED, HALTED, REVOKED_EVERYWHERE or REVOKED_PROPAGATION_INCOMPLETE. Every result has approval: false. User provides: the store and propagation ports (apply and propagation). Side effect: EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
A-04ATsugite
Multi-factor and step-up authentication (carries the challenge and the verification) — Authentication and Authorization Extension Tsugite
The Authentication and Authorization Extension Tsugite (継手, tsugite), for A-04, checks requests for an additional-authentication challenge (CHALLENGE) and its verification (VERIFY) against the pinned factors and credential references, carries them to the platform, and matches the returned receipt against the contract. Verification is done only against a challenge that this Tsugite sent to the same subject. Satisfying additional authentication is not permission or authority. If any one check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When additional authentication is inserted just before an important operation, this Tsugite prevents verifying against a challenge that was never sent or a challenge for another subject, carrying token-shaped values as they are, and mistaking satisfied additional authentication for permission.
When to use
- You want to send an additional-authentication challenge to the platform only just before an important operation, and verify the answer.
- You want to keep additional-authentication factors within the list of allowed factors (allowed_factors).
- You want verification limited to a challenge that this Tsugite sent to the same subject and matched as CHALLENGED. The states are CHALLENGE to CHALLENGED, FAILED or UNKNOWN, and VERIFY to SATISFIED, FAILED or UNKNOWN.
- You want to match the result by request_id, request_digest and references before accepting it. The DONE result carries step_up_state, satisfied and authorization.
- Even when a platform failure leaves the result unknown, you want to avoid sending the same request_id twice and resolve it with lookup.
When not to use
- You want to use satisfied additional authentication as permission for an operation. Satisfying it is not permission or authority, and a receipt that claims to have derived permission is not accepted.
- You want to confirm that the additional authentication is correct or that a credential is valid. It does not check these (that is the role of the external decision and the platform).
- You want to carry the values of secrets or tokens themselves. The bytes of secrets and tokens are not carried; only references are.
- You want an additional-authentication platform you can use right away. No actual platform (which IdP) is included, and it is undecided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a part for A-04 alone. Under the contract, this Tsugite serves A-02, A-04, A-05, A-06 and A-07 as one part, and the tests and inspection are shared. A-01 is served by the token-verify Tsugite, and A-03 and A-08 by the session Tsugite.
What it checks
- It checks factors. It does not send a request for a factor that is not in the allowed list or not in the contract. An empty list allows nothing.
- It checks what is being verified. It does not send a verification against a challenge this Tsugite did not send, a challenge to another subject, or a challenge that failed (REFUSED).
- It checks credential references. It does not send a request for a credential that is not pinned. If a credential reference or assertion_ref has the shape of bytes or a token, it is not sent even if it is pinned.
- It checks subject references and the form of references. It does not send a subject reference in the form of an email address or phone number, a reference containing a path or .. (path traversal), or extra fields.
- A receipt that claims to have derived permission from additional authentication is not accepted.
- A receipt whose request_id, request_digest or references do not match the request, that increases the external effect, or whose effect is UNKNOWN is not accepted. If a receipt that cannot be accepted arrives, the client stops (HALTED).
What it does not do / limits
- Satisfying additional authentication is not permission or authority. The permission decision is the role of the external decision point and of people and organizations; this Tsugite only checks before sending and after receiving.
- It does not check whether the additional authentication is correct or whether credentials are valid (that is the role of the external decision and the platform).
- Satisfying it is not approval (approval: false).
- It does not include an actual platform (which IdP). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not recognize every secret. Detecting token and key shapes is a net that looks only at typical forms; the real protection is the pinned reference lists. Detection of direct identifiers also covers only the typical forms of email addresses and phone numbers.
- It is not a template for how to use a specific IdP. It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/identity-ext/v1. Input: pass a StepUpAuthRequest to submit(request) on createIdentityExtClient({ cfg, port }); cfg requires the six pinned lists. Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries step_up_state, satisfied and authorization in result. Every result has approval: false. User provides: a port (submit, and optionally lookup). Side effect: EXTERNAL_REVERSIBLE for the challenge, NONE for the verification.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
A-05ATsugite
Service-to-service authentication (carries workload authentication) — Authentication and Authorization Extension Tsugite
The Authentication and Authorization Extension Tsugite (継手, tsugite), for A-05, checks workload (service) authentication requests against the pinned workloads and credential references, carries them to the platform, and matches the returned receipt against the contract. It does not carry the bytes of secrets or tokens; it carries only references. It derives no authority from successful authentication. If any one check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When authentication between services is carried to the platform, this Tsugite prevents issuing requests with workloads or credentials that are not pinned, carrying credential bytes or token-shaped values as they are, and mistaking successful authentication for authority.
When to use
- You want to carry authentication requests between services (workloads) to the platform only within the pinned workloads (workload_refs) and credential references (credential_refs).
- You want to handle credentials only as references, not as bytes.
- You want to match the result by request_id, request_digest and references before accepting it. The DONE result carries auth_state and authority.
- Even when a platform failure leaves the result unknown, you want to avoid sending the same request_id twice and resolve it with lookup.
When not to use
- You want the Workload Identity or mTLS mechanism itself. This Tsugite carries workload authentication requests by reference and does not include the function of that mechanism.
- You want to use a workload's successful authentication as authority. It derives no authority, and a receipt that claims to have derived workload authority is not accepted.
- You want to confirm that a credential is valid. It does not check the validity of credentials (that is the role of the external decision and the platform).
- You want to carry the values of credentials or tokens themselves. The bytes of secrets and tokens are not carried; only references are.
- You want an authentication platform you can use right away. No actual platform is included, and it is undecided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a part for A-05 alone. Under the contract, this Tsugite serves A-02, A-04, A-05, A-06 and A-07 as one part, and the tests and inspection are shared. A-01 is served by the token-verify Tsugite, and A-03 and A-08 by the session Tsugite.
What it checks
- It checks workloads. It does not send a request for a workload that is not pinned. An empty list allows nothing.
- It checks credential references. It does not send a request for a credential that is not pinned. If a credential reference or assertion_ref has the shape of bytes or a token, it is not sent even if it is pinned.
- It checks the form of references. It does not send a reference containing a path or .. (path traversal), or extra fields.
- A receipt that claims to have derived workload authority is not accepted.
- If the receipt's request_id, request_digest or references do not match the request, the receipt is not accepted. A receipt with a spoofed request_digest is not accepted either.
- A receipt that increases the external effect, or whose effect is UNKNOWN, is not accepted. If a receipt that cannot be accepted arrives, the client stops (HALTED).
What it does not do / limits
- It does not include the Workload Identity or mTLS mechanism itself. It only checks workload authentication requests by reference and carries them.
- Successful authentication is not authority. It derives no permission or authority from identity or credential state.
- It does not check the validity of credentials (that is the role of the external decision and the platform). Successful authentication is not approval either (approval: false).
- It does not include an actual platform. The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not recognize every secret. Detecting token and key shapes is a net that looks only at typical forms; the real protection is the pinned reference lists.
- It is not a template for how to use a specific IdP. It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/identity-ext/v1. Input: pass a WorkloadAuthRequest to submit(request) on createIdentityExtClient({ cfg, port }); cfg requires the six pinned lists (workload_refs and the others). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries auth_state and authority in result. Every result has approval: false. User provides: a port to the platform (submit, and optionally lookup). Side effect: NONE or EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
A-06ATsugite
API key issuance and rotation (carries references; does not let keys be disclosed) — Authentication and Authorization Extension Tsugite
The Authentication and Authorization Extension Tsugite (継手, tsugite), for A-06, checks requests to issue (ISSUE), rotate (ROTATE) and revoke (REVOKE) API keys against the pinned keys and secret-location references, carries them to the platform, and matches the returned receipt against the contract. It does not carry key bytes, and it does not accept a receipt that discloses key material. If any one check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When API keys for external integrations are issued, rotated and revoked, this Tsugite prevents handling keys or locations that are not pinned, carrying key bytes as they are, accepting key material returned by the platform, and mistaking a key's state for authority.
When to use
- You want to carry requests to issue, rotate and revoke API keys to the platform only within the pinned keys (key_refs) and key locations (secret_refs).
- You want to hand over a new key only by a reference to its location (secret_ref), not by its material. secret_ref is required only for ISSUE and ROTATE, and the states are ISSUE to ISSUED_REF, ROTATE to ROTATED_REF, and REVOKE to REVOKED, or FAILED.
- You want to match the result by request_id, request_digest and references before accepting it. The DONE result carries key_state and key_material_disclosed.
- Even when a platform failure leaves the result unknown, you want to avoid sending the same request_id twice and resolve it with lookup.
When not to use
- You want to receive key material. A receipt that discloses key material is not accepted.
- You want key storage itself. This Tsugite carries issue, rotate and revoke requests to the platform by reference and does not include key storage (which key store is undecided).
- You want to use a valid key as permission or authority. Even a valid credential is not permission or authority.
- You want to confirm that a credential is valid. It does not check the validity of credentials (that is the role of the external decision and the platform).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a part for A-06 alone. Under the contract, this Tsugite serves A-02, A-04, A-05, A-06 and A-07 as one part, and the tests and inspection are shared. A-01 is served by the token-verify Tsugite, and A-03 and A-08 by the session Tsugite.
What it checks
- It checks keys and locations. It does not send a request for a key that is not pinned or a secret location that is not pinned. An empty list allows nothing.
- It checks secret_ref. A key-shaped secret_ref is not sent even if it is pinned. A REVOKE request with a secret_ref attached is not sent either.
- A receipt that discloses key material is not accepted.
- It checks the form of references. It does not send a reference containing a path or .. (path traversal), or extra fields.
- If the receipt's request_id, request_digest or references do not match the request, the receipt is not accepted. A receipt with a spoofed request_digest is not accepted either.
- A receipt that increases the external effect, or whose effect is UNKNOWN, is not accepted. If a receipt that cannot be accepted arrives, the client stops (HALTED).
What it does not do / limits
- It does not carry or receive key material. It handles only references to keys and their locations.
- It derives no permission or authority from a key's state. It does not check the validity of credentials either (that is the role of the external decision and the platform).
- Successful issuance, rotation or revocation is not approval (approval: false).
- It does not include an actual platform (which key store). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not recognize every secret. Detecting token and key shapes is a net that looks only at typical forms; the real protection is the pinned reference lists.
- It is not a template for how to use a specific key storage service. It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/identity-ext/v1. Input: pass an ApiKeyLifecycleRequest to submit(request) on createIdentityExtClient({ cfg, port }); cfg requires the six pinned lists. Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries key_state and key_material_disclosed in result. Every result has approval: false. User provides: a port to the platform (submit, and optionally lookup). Side effect: EXTERNAL_REVERSIBLE (NONE for FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
A-07ATsugite
Delegation (on-behalf-of) reference — Authentication and Authorization Extension Tsugite
The Authentication and Authorization Extension Tsugite (継手, tsugite), for A-07, checks only delegation references backed by evidence received from a person or organization, carries them to the platform, and matches the returned receipt against the contract. ERABAZU does not create delegation authority. Delegation authority comes only from the evidence of an external person or organization. If any one check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When an "on behalf of the subject" delegation is carried to the platform, this Tsugite prevents carrying a delegation without evidence from a person or organization, making ERABAZU itself the origin of a delegation, and accepting a receipt that claims to have created delegation authority.
When to use
- You want to receive delegation evidence issued by a person or organization with recordAuthorityEvidence(evidence_ref, origin) (origin is only HUMAN or ORGANIZATION), and carry to the platform only delegation references backed by that evidence.
- You want to handle the parties to a delegation only by pseudonymous references, not by email addresses or phone numbers.
- You want the handover of a delegation to cause no external effect. The side effect of a delegation reference is NONE.
- You want to match the result by request_id, request_digest and references before accepting it. The DONE result carries delegation_state and authority_origination.
- Even when a platform failure leaves the result unknown, you want to avoid sending the same request_id twice and resolve it with lookup.
When not to use
- You want ERABAZU to create delegation authority, or to hand over authority itself. This Tsugite carries delegation references, and ERABAZU does not create delegation authority.
- You want to carry a delegation without evidence from a person or organization. Such a request is not sent (REFUSED).
- You want to confirm that a delegation is genuine. It does not check the content of the delegation evidence; it only checks whether the reference was received from a person or organization.
- You want a platform you can use right away. No actual platform is included, and it is undecided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a part for A-07 alone. Under the contract, this Tsugite serves A-02, A-04, A-05, A-06 and A-07 as one part, and the tests and inspection are shared. A-01 is served by the token-verify Tsugite, and A-03 and A-08 by the session Tsugite.
What it checks
- It checks delegation evidence. It does not send a delegation without evidence, a delegation based on evidence not received through recordAuthorityEvidence, or a delegation whose origin is ERABAZU.
- A receipt that claims to have created delegation authority is not accepted.
- It checks party references and the form of references. It does not send a reference in the form of an email address or phone number, a reference containing a path or .. (path traversal), or extra fields.
- If the receipt's request_id, request_digest or references do not match the request, the receipt is not accepted. A receipt with a spoofed request_digest is not accepted either.
- A receipt that increases the external effect, or whose effect is UNKNOWN, is not accepted. If a receipt that cannot be accepted arrives, the client stops (HALTED).
- It does not send the same request_id twice. A platform failure is treated as UNKNOWN, and resending is resolved with lookup.
What it does not do / limits
- ERABAZU does not create delegation authority. Delegation authority comes only from the evidence of an external person or organization. This Tsugite carries delegation references and does not include any function for handing over authority.
- It does not check the content of the delegation evidence (whether the delegation is genuine). It only checks whether the reference was received from a person or organization.
- Carrying a delegation is not approval (approval: false).
- It does not include an actual platform. The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not recognize every direct identifier. Detection covers only the typical forms of email addresses and phone numbers.
- It is not a template for how to use a specific IdP. It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/identity-ext/v1. Input: receive evidence from a person or organization with recordAuthorityEvidence(evidence_ref, origin) on createIdentityExtClient({ cfg, port }), then pass a DelegationRefRequest to submit(request). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries delegation_state and authority_origination in result. Every result has approval: false. User provides: a port to the platform (submit, and optionally lookup). Side effect: NONE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
A-08ATsugite
Revocation propagation — Session and Revocation Propagation Tsugite
The Session and Revocation Propagation Tsugite (継手, tsugite) sends a session revocation (REVOKE) to the store, matches the receipt, and keeps the revocation "incomplete" until reach receipts with evidence have arrived from every pinned propagation target. While it is incomplete, the session is still treated as unusable (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents the "logged out, yet the session remains" accident. It prevents treating a revocation accepted in one place as having reached the whole system, and prevents trusting reach notices without evidence or from targets that are not pinned. It is a check that confirms, in a form that can be matched, whether a revocation has reached the whole system.
When to use
- You want to confirm, by reach receipts, whether a logout or revocation has reached every propagation target you set (propagation_targets).
- You want to know which propagation targets it has not reached yet. When incomplete, the result comes with the targets not reached (missing_targets).
- You want revocations to keep being sent even while the client is stopped because of an inconsistent receipt. This Tsugite also accepts revocation of sessions it does not know.
- You want a revoked session never to be resumed afterwards.
When not to use
- You want it to deliver the revocation to the propagation targets itself. It only checks reach by the reach receipts that the port (propagation) returns, and does not include the propagation targets' implementation.
- You want propagation targets or a session store you can use right away. The actual session store and propagation targets (each service's revocation intake) are undecided.
- You want to revoke or verify the token itself. Token verification is the role of A-01 (token-verify-tsugite); this Tsugite handles only session operations and revocation propagation.
- You want to treat a revocation having reached everywhere as identity verification, authority or approval. Every result is authority: NOT_DERIVED and approval: false.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks for missing propagation targets. If even one pinned propagation target has no reach receipt, it does not treat the revocation as complete and returns REVOKED_PROPAGATION_INCOMPLETE.
- It checks the evidence of reach. A reach notice without evidence (evidence_ref) is not accepted as having reached.
- It checks the pinning of propagation targets. A reach notice from a target that is not pinned is not accepted as having reached.
- It matches the revocation receipt. A REVOKE receipt must carry a propagation reference, and if the state contradicts the operation (REVOKE must give REVOKED), the receipt is for another session, or request_digest is spoofed, it is not accepted.
- It rejects anything remaining after revocation. A revoked or terminated session is not resumed, and if the store returns RESUMED after a revocation, it does not accept it and stops.
- Whether the revocation is complete or incomplete, the session is treated as unusable. On a store failure (UNKNOWN), it is also treated as unusable until resolved.
What it does not do / limits
- It has no mechanism of its own for delivering revocations to propagation targets. The shape of the reach receipt (target_ref, session_ref, revoked, evidence_ref) is not in the contract; it is defined on the Tsugite side.
- It does not include an actual session store or propagation targets (each service's revocation intake). The port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- It does not verify or revoke tokens. Token verification is the role of A-01 (token-verify-tsugite).
- A revocation having reached everywhere is neither identity verification nor authority (authority: NOT_DERIVED, approval: false).
- A-08 is one part with A-03 (session management) under the contract. Each ID's row (A-03 and A-08) points to this part.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a session store.
How to connect
Contract: erabazu.candidate/certified-tsugite/request-receipt-group/v1. Input: pass a REVOKE SessionOpRequest to apply(request) on createSessionClient({ cfg, port }); the propagation targets are pinned in cfg propagation_targets. Returns: REVOKED_EVERYWHERE or REVOKED_PROPAGATION_INCOMPLETE (with missing_targets), among others. Every result has authority: NOT_DERIVED and approval: false. User provides: a store port (apply) and a port that returns the list of reach receipts (propagation). Side effect: EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
A-09ATsugite
Organization domain control lookup — Organization Domain Control Lookup Tsugite
The Organization Domain Control Lookup Tsugite (継手, tsugite) has the port look up a marker placed on an organization's domain, either in DNS TXT (`_kokoroamu.<domain>`) or at a fixed HTTPS location (`/.well-known/kokoroamu-org.json`), matches the returned receipt (OrgDomainControlReceipt) against the contract, and carries the answer exactly as received. It does not treat a successful lookup or a found marker as proof of domain control (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When someone says "the domain has been checked", it prevents losing sight of where the lookup read and which answer was trusted. Names and URLs are built from the domain alone; redirects, TLS, DNSSEC and sizes are checked; and the answer is carried exactly as received, together with its digests. It does not mistake a lookup for proof of domain control, and it does not reuse an answer for a different purpose.
When to use
- You want to look up a marker placed on an organization's domain, in DNS TXT (`_kokoroamu.<domain>`, method DNS_TXT) or at a fixed HTTPS location (`/.well-known/kokoroamu-org.json`, method HTTPS_WELL_KNOWN), and pass the answer on to the side that reads it later.
- You want callers to pass only the domain, and not let them decide the DNS name, URL, port number or path.
- You want to use one domain in separate lookups for separate purposes (the current table of purposes is `seat8`). The purpose is part of both the request and the answer, and an answer is not reused for a different purpose (reusable_for_other_purpose false).
- You want to keep the TXT records in the order received and the HTTPS body as bytes (base64), each with its digest.
When not to use
- You want to read the marker, match the purpose and key ID, and decide the validity period. That is the role of the A-10 part (org-domain-claim-primitive) and of rules and people; this Tsugite only performs the lookup.
- You want to use the lookup result as proof of domain control as it stands. An answer from this Tsugite does not recognize domain control (ownership_decision NOT_DERIVED).
- You want to look up an arbitrary URL, port number or path. The request has no such fields and does not accept them.
- You want a lookup mechanism you can use right away. No actual DNS or HTTPS lookup has been made (0 times); the port implementation, the resolver, the list of trusted CAs and the setting values will be decided at a separate gate when connecting.
- You want to keep lookup records for a long time. Records exist only inside this Tsugite and are lost on restart.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the settings and the purpose. It does not send the request (REFUSED) when the request's contract_pin differs from the setting contract_schema_digest, when there are no settings, when the purpose is not in allowed_purposes, or when the resolver is not in resolver_profile_refs. An empty list allows nothing.
- It checks that the name and URL in the receipt are the same as those built from the domain. It does not accept a receipt whose purpose, domain or key ID differs from the request.
- It checks the content of the answer. It does not accept an answer whose TXT order, record count or digest, or whose body size, digest or content type (JSON or text, charset=utf-8 only) does not match. Strings in the receipt and the request are accepted only when they are valid Unicode.
- It checks redirects and TLS. It does not accept an answer that followed a redirect to a different host, to http, or beyond the limit (a redirect that was not followed is REDIRECT_REFUSED). It also does not accept a FOUND or NOT_FOUND answer that failed a TLS check, an answer containing a DNSSEC BOGUS, or a partial answer from a TIMEOUT.
- It checks the connected address (connected_address). It accepts only an address that is global according to the narrowest row containing it in the IANA special-purpose address registry. An answer that connected inside a NAT64 prefix is accepted only when the embedded IPv4 address extracted according to RFC 6052 is also global.
- It matches the markers of not deciding, and the effect. It does not accept a receipt that does not keep claim_evaluation NOT_PERFORMED, ownership_decision NOT_DERIVED and side effect NONE, and it stops after a receipt it cannot match (HALTED after RECEIPT_REJECTED).
- It does not send different content under the same request_id. A port failure or a timeout (limits.timeout_ms plus a 1-second grace period) is treated as UNKNOWN and resolved by sending the same request again (first through port.lookup, if present).
What it does not do / limits
- It does not read the marker (`v=kokoroamu-org1`), match the purpose and key ID, decide the validity period, or recognize domain control. A successful lookup or a found marker is not proof of domain control.
- It does not check whether all resolved addresses were global, whether cookies or credentials were withheld, or whether the name and URL were actually built from the domain (the role of the port). What this Tsugite checks extends only to the connected_address in the receipt being global.
- DNSSEC SECURE or INSECURE, the results of the three TLS checks, and the TTL are what the port reports; this Tsugite checks only their form and combinations.
- The table of global ranges is a copy made by reading the IANA registry as a web page, and it has not been compared with the raw registry bytes. It needs to be compared again with the raw CSV files before publication.
- It does not know the local NAT64 prefix. For an answer from a port that does not return nat64, it cannot check whether an IPv4 address embedded in the local prefix is global.
- It writes nothing (side effect NONE). Having carried or looked something up is not approval (every result carries approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/org-domain-control/v1 (OrgDomainControlRequest / OrgDomainControlReceipt). Input: pass an OrgDomainControlRequest to submit(request) on createOrgDomainControlClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; every result has approval: false. User provides: a lookup port (submit, and optionally lookup) and cfg (contract_schema_digest, allowed_purposes and resolver_profile_refs). Side effect: NONE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
A-10APrimitive
Organization domain mark reading — Organization domain mark reading and matching part (Primitive)
The Organization domain mark reading and matching part (Primitive) is a standalone part that reads the mark a company (organization) has placed on its own domain (a `_kokoroamu.<domain>` line in DNS TXT, or the JSON document at `/.well-known/kokoroamu-org.json` over HTTPS) from the raw answer received by a lookup, matches it by exact match of purpose and key ID, and creates a record with an expiry (claim). Lines it cannot read and lines for other uses are not discarded; they are returned as facts.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Variations in how a mark is written, and marks that have expired, are not visible from the outside. This part does not read marks with unknown fields, duplicate fields or an unknown version, and passes only purposes that are in the caller's allowed_purposes. It prevents the confirmation of one domain from being reused for another purpose and widening authority, and it decides the expiry from the observation time and a number of days.
When to use
- You want to read the list of marks (purpose and key ID) from a DNS TXT answer (txt_records) or from a well-known body (body_base64 and content_type) (parseMarks).
- You want to confirm, by exact string match, whether there is a mark for a given purpose and a given key ID, and create a record (claim) of domain, method, purpose, key_id, observed_at and expires_at (checkClaim).
- You want lines it cannot read and lines for other uses (SPF and so on) kept with a reason (the reason in ignored) instead of being silently discarded.
- You want to confirm whether a record is within its validity period at a time you pass (at) (isClaimCurrent; when outside it, reason is NOT_YET_OBSERVED or EXPIRED).
- You want to create attestation attributes from a record (exactly one domain:<domain> and one purpose:<purpose>, in this order) (claimToAttestationAttributes).
- You want to call it locally as deterministic processing that has no clock, no random numbers and no outside connection. The part does not hold a table of purposes; the caller passes it as allowed_purposes.
When not to use
- You want to confirm that the mark came from that domain (the DNS name, DNSSEC, the HTTPS certificate, redirects). This part reads the answer it is given as belonging to that domain. The lookup and its checks are the role of the lookup part (the A-09 proposal) and of the caller.
- You want to confirm the identity of the domain's owner (that the individual or organization exists). What this part reads is only the mark showing control of the domain.
- You want a domain containing uppercase letters, a trailing dot or leading or trailing white space, or an IP address, to be corrected and accepted. It does not correct the name; it refuses it with INVALID_DOMAIN.
- You want to pass an internationalized domain in Unicode form. It accepts only the xn-- form and does not check the content of the punycode.
- You want a validity period longer than 90 days. valid_days is only an integer from 1 to 90; outside that range it stops with INVALID_VALID_DAYS.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- It reads a DNS TXT line as a mark only when the first field is exactly the fixed version name (`v=kokoroamu-org1`) and the rest is exactly one purpose=<purpose> and one key=<key ID>. It does not read lines with unknown fields, duplicate fields, empty fields, missing fields or an unknown version; it returns them in ignored with a reason (UNKNOWN_FIELD, DUPLICATE_FIELD, MISSING_FIELD, UNKNOWN_VERSION, MALFORMED_MARK and so on).
- It puts a line longer than 4096 characters into ignored as TOO_LONG, a line that is not UTF-8 (in the { undecodable_base64 } form) as NOT_UTF8 without decoding it, and a line that is not a string as NOT_TEXT. It stops with INVALID_RECORDS or TOO_MANY_RECORDS when txt_records is not an array or has more than 64 items.
- For a well-known body, it stops with UNSUPPORTED_CONTENT_TYPE when content_type is not application/json or text/plain (optionally with ; charset=utf-8), and with INVALID_BODY when body_base64 is not canonical base64 or is larger than 64 KiB when decoded. A body that is not UTF-8, is not JSON, has the wrong document shape or has more than 1000 entries is returned as the fact NOT_UTF8, NOT_JSON, MALFORMED_DOCUMENT or TOO_MANY_ENTRIES.
- When purpose is not in allowed_purposes, the result is matched: false and reasons records PURPOSE_NOT_ALLOWED. Even with the same key ID, a mark for another purpose does not pass; it records KEY_UNDER_OTHER_PURPOSE.
- It compares purpose and key ID by exact string match. Key IDs are case-sensitive, and it does no Unicode normalization or case folding.
- When domain, purpose, key_id, allowed_purposes, observed_at, valid_days or marks has the wrong form, it stops with INVALID_DOMAIN, INVALID_PURPOSE, INVALID_KEY_ID, INVALID_ALLOWED_PURPOSES, INVALID_OBSERVED_AT, INVALID_VALID_DAYS or INVALID_MARKS. A domain is up to 242 characters, a time is in UTC Z form from the year 2000 to 9998, and marks is up to 2000 items.
- When the claim passed to isClaimCurrent or claimToAttestationAttributes has the wrong form (extra keys, an expiry that is not after observed_at, more than 90 days), it stops with INVALID_CLAIM. When at has the wrong form, it is INVALID_AT. Only claimToAttestationAttributes throws OrgDomainClaimError.
- It stops with INVALID_INPUT when the input is not an object, and with UNKNOWN_SOURCE when source is neither DNS_TXT nor HTTPS_WELL_KNOWN. When it stops, it returns { ok: false, errors: [{ code }] }.
What it does not do / limits
- matched: true is only the fact that the answer it was given contained a mark for this purpose and this key ID. It does not show the identity of the domain's owner, that the mark came from that domain, or that the attestation may be signed. Whether to sign is decided by the caller.
- It does not perform DNS or HTTPS lookups. It has no outside boundary, no state and no random numbers, and it does not read the clock (all times are input).
- It does not hold a table of purposes. Which purposes to allow (allowed_purposes) is passed by the caller.
- For duplicate keys in JSON it uses the last value, as JSON.parse does; it does not detect duplicates and refuse them.
- It does not decode { undecodable_base64 } values. It does not check whether they are canonical base64 or really not UTF-8 (that is checked on the receiving side), and it does not read lines in that form as marks.
- Creating a record (claim) is not an approval (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for DNS or HTTPS lookups.
How to connect
Call parseMarks, checkClaim and isClaimCurrent (they do not throw) and claimToAttestationAttributes (it throws) in src/org-domain-claim.js. parseMarks takes txt_records, or body_base64 and content_type, and returns marks and ignored. checkClaim takes domain, purpose, key_id, allowed_purposes, marks, observed_at and valid_days, and returns matched, reasons and claim. The shape of the answers it reads follows the contract org-domain-control/v1. There are no side effects, no state and no credentials, and there is no port for the caller to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
A-11AComponent
Organization domain control check — Organization domain control check part (Component)
The Organization domain control check part (Component) is a part that bundles the lookup Tsugite (A-09) and the mark-matching part (Primitive) (A-10) and performs check({ domain, purpose, key_id }) in one call. It first looks up DNS TXT (`_kokoroamu.<domain>`); if there is a mark whose purpose and key ID match exactly and it is within its validity period, the result is MATCHED (dns_txt). Otherwise it looks up the HTTPS well-known location, and if that matches, the result is MATCHED (https_well_known). When both answer and there is no match, the result is NOT_FOUND; when it cannot decide, UNAVAILABLE (fail-closed). MATCHED is not a judgment of domain control.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It is a part that fixes, behind the organization verification screen, the order of lookups and the conditions for refusing. It prevents passing on a mark for another purpose or another key, confusing an answer for a different domain, purpose, key or method, deciding "not there" when it cannot decide, and making its own judgment about the validity period.
When to use
- You want to check, in one call, whether an organization's domain has a mark for a set purpose and key ID (16 lowercase hex digits), looking up DNS first and then HTTPS.
- You want to limit the purposes that may be looked up to a table in the options (allowed_purposes, 1 to 64 entries; currently `seat8`), so that a purpose not in the table is not even looked up.
- You want to receive the result as one of three states, MATCHED, NOT_FOUND and UNAVAILABLE, and handle "both answered and there was no match" separately from "could not decide (can be retried later)".
- You want to leave the judgment of a mark's validity period to isClaimCurrent of A-10, using the time read after the lookup's receipt was received (the record's validity in days is valid_days, 1 to 90).
- On MATCHED, you want to attach the request_id of the receipt of the deciding lookup (receipt_ref) and the time read before the lookup was sent (observed_at) to a later record.
When not to use
- You want to use MATCHED as a judgment of control over the domain or as grounds that signing is allowed. MATCHED is only the fact that "a mark for this purpose and this key ID was in this domain's DNS or well-known location at the time of the lookup".
- You want this part to also enforce a limit on the number of lookups for the same device or domain. That limit is the job of the caller's rules and of a person.
- You connect a lookup port that does not answer directly. This part does not cut off waiting, so check does not finish (the real A-09 client returns UNKNOWN after limits.timeout_ms plus 1 second).
- You need the receipt that receipt_ref points to to be retrievable after a restart. The A-09 client's record is only inside the process and is lost on restart; you rely on the caller's record.
- You want it to also build attestation attributes. claimToAttestationAttributes of A-10 is not used. Building the attributes is the caller's job.
- You need a part checked against real DNS and HTTPS. It has made 0 real lookups.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed, it stops with INVALID_OPTIONS. It refuses with INVALID_INPUT when the input to check is not an object or cannot be read, with INVALID_DOMAIN when domain is not a DomainName of the contract (up to 242 characters), with PURPOSE_NOT_ALLOWED when purpose is not in the table, and with INVALID_KEY_ID when key_id is not 16 lowercase hex digits. When it refuses, it makes 0 lookups, does not read the clock, and state is null.
- When the clock throws, does not return a number, or returns a time outside the years 2000 to 9998, it stops there with CLOCK_FAILED and returns UNAVAILABLE (no further lookups).
- When the answer from A-09 is REFUSED, it reports LOOKUP_REFUSED; when UNKNOWN, LOOKUP_UNKNOWN; when RECEIPT_REJECTED or HALTED, LOOKUP_HALTED; when anything other than DONE (including ALREADY_DONE), LOOKUP_NOT_DONE; and when a lower part throws an exception or rejects, PORT_FAILED; and it does not return MATCHED.
- Even with DONE, when the result does not echo the requested domain, purpose, key ID and method, approval is not false, or result is missing, it does not use the result (RESULT_MISMATCH).
- When the lookup could not answer (TIMEOUT, FAILED, TLS_FAILED, REDIRECT_REFUSED, an unknown state), it reports LOOKUP_FAILED. FAILED means "could not answer" whatever the reason (PRIVATE_ADDRESS, DNSSEC_BOGUS, CONTENT_TYPE_REFUSED and others).
- When parseMarks of A-10 cannot read the marks, it reports MARKS_UNREADABLE; when checkClaim does not return a boolean matched or returns a claim different from what was requested, CLAIM_UNCHECKED; and when isClaimCurrent does not return current: true (NOT_YET_OBSERVED, EXPIRED and others), CLAIM_NOT_CURRENT; and it does not return MATCHED.
- It copies the options and the purpose table when it is created, and also copies the table it passes to A-10. Even if options or the table are rewritten later, the next check does not widen.
What it does not do / limits
- The Organization domain control check part is not a Tsugite. It has no Request or Receipt of its own; the external lookups are made by the bundled A-09 Tsugite under its own contract (org-domain-control/v1). This part itself does not touch the outside, and the lookups only read (side effect NONE).
- The checks of the lookup (receipt matching, redirects, TLS, DNSSEC, the connected address, content type) stay on the A-09 side, and reading, matching and the validity period of marks stay on the A-10 side. That a mark came from that domain is the job of A-09 and the port; this part trusts DONE from A-09.
- MATCHED is not a judgment of domain control, and it is neither authority nor approval (approval: false, authority: NOT_DERIVED, derives_authority: false).
- It does not compare the receipt's queried_at (as declared by the port) against the clock. This part also does not cut off waiting for a lookup.
- It has never made a lookup against real DNS or HTTPS. The tests and fuzzing use stand-ins, and the wiring was checked with the code of the real A-09 and A-10 against the A-09 test stand-in port.
- Where the purpose table is kept, and names for purposes other than the current one, are not decided in this part (left open in the contract).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular DNS service or HTTP client.
How to connect
Create it with createOrgDomainCheck(options) in src/org-domain-check.js; it returns { ok, errors, instance }. options are allowed_purposes, contract_schema_digest, resolver_profile_ref, limits, valid_days, request_prefix, clock and ports. check returns { ok, errors, state, method, observed_at, receipt_ref, approval: false, … }. Ports: domainControl.submit (the client of the A-09 Tsugite) and parseMarks, checkClaim and isClaimCurrent of A-10. No credentials are needed.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-01BTsugite
Signing (KMS/HSM) — KMS Signing Tsugite
The KMS Signing Tsugite (継手, tsugite) fronts a backstage signing API (Cloud KMS/HSM). It accepts only adopted schemas and contexts and RFC 8785-canonicalized payloads, checks the sequence and previous_hash chain before requesting a signature, and verifies the returned signature with a pinned public key (PEM) before returning it. On any mismatch it rejects without calling KMS (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When signing artifacts or records, it prevents signing arbitrary byte strings, signing formats or contexts that have not been adopted, and signing while the signing order (sequence) or the link to the previous one (previous_hash) is broken. It is a check that turns the signing API into a fail-closed part that does not accept arbitrary input.
When to use
- You want to sign artifacts and records only for adopted schemas and contexts and canonicalized payloads.
- You want to arrange signatures in an append-only chain per context (monotonically increasing sequence and a previous_hash chain).
- You want to verify a returned signature on the spot with a pinned PEM, without fetching the public key each time, before using it.
- You want to use Cloud KMS AsymmetricSign (EC_SIGN_P256_SHA256) with authentication that keeps no JSON key (attached SA / ADC) and least privilege per key.
When not to use
- You want to sign arbitrary byte strings or files as they are. canonical_payload is limited to strings canonicalized with RFC 8785, and arbitrary payloads are rejected.
- You also want this Tsugite to do the canonicalization. RFC 8785 canonicalization is done by the caller.
- You want a part tested while connected to an actual Cloud KMS and Firestore. This part's tests use a local P-256 key and an in-memory store; the connection to production Cloud KMS and Firestore has not been checked.
- You want to treat a signature as approval or a decision. This Tsugite only checks and passes the signature on; it grants no authority.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by fusion Kernels.
What it checks
- It checks the schema and context. Schemas and contexts that have not been adopted, and a wrong context (wrong-context), are rejected with 422 without calling KMS.
- It checks the sequence. A sequence that rolls back, is duplicated or is not an integer is rejected without calling KMS.
- It checks previous_hash. Anything that does not link to the SHA-256 of the previous payload (or GENESIS), and violations of the genesis rules (including when the genesis for approval is not defined), are rejected.
- It checks the payload. Requests without canonical_payload, and arbitrary payloads, are rejected.
- It matches the contents of the payload against the request. It signs only when the payload's context and schema_version are the same as the request's, and does not sign a payload that differs.
- A request naming an unknown key is rejected with 400. The key is erabazu_publisher or approval_checkpoint.
- It verifies the signature. If the signature returned by KMS cannot be verified with the pinned public key (PEM), it returns 500 and no signature.
What it does not do / limits
- This part's tests (unit tests and fuzzing) did not connect to production Cloud KMS or Firestore; they used a local P-256 key and an in-memory store. Separately, there is a record from 2026-08-15 of signing a synthetic context (erabazu.synthetic-test) with the production ERABAZU HSM key and verifying it with the pinned PEM.
- It does not canonicalize canonical_payload (RFC 8785). The caller does.
- It does not sign arbitrary input. It is limited to the context, the schema and the canonicalized payload.
- The next sequence and previous_hash returned by GET /latest are only a hint. Whether a signature is made is decided by the checks of POST /sign.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a signing API.
How to connect
Input: send { key, context, schema_version, sequence, previous_hash, canonical_payload } to POST /sign. The entry point is src/server.js (handleSign), and the configuration is app_kms_config.json. Returns: one of 200 (with signature_der_base64 (an ECDSA DER signature), payload_sha256, verified_with_pinned_pem: true and other fields), 400 (unknown key), 422 (fail-closed, with reasons) or 500 (the signature could not be verified with the pinned key; no signature is returned). User provides: a signer port (Cloud KMS in production) and a store port (getLatest and commit; Firestore in production).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-02BPrimitive
Signature verification (pinned) — Signature verification (pinned) part (Primitive)
The Signature verification (pinned) part (Primitive) is a standalone part that verifies, locally, a signature over bytes with a public key that the caller has fixed (pinned). The only algs are Ed25519 and ES256, and keys are taken only from pinned (key_id → { alg, public_key_jwk }); a request cannot bring in a key. verify does not throw an exception and returns { ok, errors }, and a value it cannot accept is stopped with a fixed code (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
For the problem that nobody checks whether what has been received is "genuine", it verifies origin and integrity locally. While doing so, it stops in advance: verifying with a key brought in by the request, confusing one alg for another, the configuration mistake of putting a private-key JWK into pinned, and non-canonical encodings that the Node.js key import would otherwise accept.
When to use
- You want to verify the signature on bytes you received using only public keys that the caller has decided on and fixed (pinned).
- You want to verify a signature whose alg is Ed25519 (R||S per RFC 8032) or ES256 (ECDSA on P-256 with SHA-256; the signature is the 64-byte IEEE P1363 r||s).
- You need verification that does not throw. verify returns { ok, errors }, does not throw an exception, and does not modify its input.
- You want to verify the signature of a JSON value. Turn it into canonical bytes with the I-08 part first, then pass them to this part.
- You want to call it locally, as processing that does not depend on the clock, random numbers or the environment's locale and that has no outside boundary and no state. It has no dependency packages.
When not to use
- You want to confirm who the signer is (identity). A correct signature shows only that the holder of the pinned key signed these bytes.
- You want this part to decide which keys to pin, to handle key rotation and revocation, or to assign key_id values. These are decided outside this part (by the caller and by trust management).
- You want to use the signature bytes as a unique identifier. ES256 signatures are malleable ((r, n - s) is also valid), and low-S is not enforced.
- You want to verify algs other than Ed25519 and ES256 (EdDSA, ES384, RS256, none and so on), ES256 signatures in DER form, or prehashed values.
- You want to handle the whole of JWS (the header, assembling the payload, the base64url separators). This part handles only the verification of the signature bytes.
- You want to align the strictness of Ed25519 (small-order points, non-canonical points, differences such as RFC 8032 versus ZIP-215) with your own rules. It follows the OpenSSL implementation and does not refuse public keys that are small-order points either.
- You use it on a version other than Node.js v24.21.0 (including differences in the OpenSSL version). Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the request is not a plain object it stops with NOT_A_REQUEST, and when it has fields other than alg, key_id, signature and bytes (such as public_key_jwk or seed, which would bring in a key) it stops with UNKNOWN_FIELD.
- When alg is neither "Ed25519" nor "ES256" (including differences in case, and none) it stops with UNSUPPORTED_ALGORITHM, and when key_id is not a string of 1 to 256 characters it stops with INVALID_KEY_ID.
- When signature is not canonical base64url (padding, standard base64 characters, whitespace, or leftover bits) it stops with MALFORMED_SIGNATURE, and when it does not decode to 64 bytes (including DER) it stops with INVALID_SIGNATURE_LENGTH.
- When bytes is not a Uint8Array (a string, another typed array, a DataView or a Proxy), it stops with NOT_BYTES.
- When pinned is not a plain object it stops with INVALID_PINNED_SET, and when a JWK anywhere in pinned (including non-enumerable entries) has private members, it stops with PRIVATE_KEY_REFUSED whatever the key_id.
- When key_id is not an own member of pinned (including prototype names such as __proto__) it stops with UNKNOWN_KEY, and when a pinned entry or JWK has the wrong shape or is a point not on the curve it stops with INVALID_PINNED_KEY.
- When the request's alg differs from the pinned alg it returns ALG_MISMATCH, and when the bytes, the signature or the key differ (including S + L for Ed25519) it returns SIGNATURE_INVALID.
- When inspecting the input itself raises an exception (a getter, a Proxy trap and so on), it returns INSPECTION_FAILED and does not throw. The input is read only once, and the key import is given a JWK that this function has rebuilt.
What it does not do / limits
- It does not include key distribution, deciding what to pin, rotation and revocation (trust management), or assembling JWS or manifests.
- It does not create signatures. Private keys are handled by Tsugite such as KMS, and this part does not accept a JWK that has private members.
- The elliptic-curve computation is left to Node.js crypto (OpenSSL) and has not been checked independently. The fuzz oracle also only calls the same crypto through a different way of importing the key.
- It was checked against only one RFC 8032 example (Example 1), and ES256 was checked with signatures from keys generated at run time instead of public known answers.
- A correct signature is not the signer's identity, legal acceptance, authority or approval (result_is_approval: false). It creates no authority either (derives_authority: false).
- Compatibility with the signature verification inside other parts or Tsugite has not been checked.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular signature library.
How to connect
Call verify(request, pinned) in src/signature-verify.js (it does not throw). Input: a plain object request with only alg, key_id, signature and bytes (a Uint8Array), and a plain object pinned that maps key_id → { alg, public_key_jwk } (public JWKs only). Returns: { ok: true, errors: [] } or { ok: false, errors: [{ code }] } (code is from error_taxonomy). Keys are received through pinned and are not fetched. Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing. The only dependencies are the Node.js built-in crypto and util.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-03BComponent
Encryption at rest (envelope) — Encryption at rest (envelope) part (Component)
The Encryption at rest (envelope) part (Component) is a part that, to put data into envelope encryption, bundles the Tsugite that has KMS generate a DEK and unwrap a wrapped key (kms-envelope) with the Primitive that seals and opens an AES-256-GCM envelope locally (envelope-encrypt). From KMS it receives only a handle and a reference to the wrapped key; the DEK passes from withDataKey inside the port only to the local seal and open, within the same process. If the envelope's key_ref, the wrapped key's reference and the KMS key do not match, it does not send to KMS, and if the encryption context differs, it does not open (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind "it is supposed to be encrypted", it prevents the plaintext DEK from remaining in the part's results or state, sending to KMS an envelope whose wrapped key reference or KMS key does not match, and opening an envelope with a different encryption context, a tampered envelope or an envelope with a different aad.
When to use
- You want to put data (plaintext, Uint8Array) into an envelope with a DEK generated by KMS (encrypt). Inject a client of the kms-envelope Tsugite as ports.kms, and the envelope-encrypt Primitive as ports.seal and ports.open.
- You want to use the plaintext DEK without taking it outside the KMS port. keyAccess.withDataKey(handle, fn) inside the port passes the DEK to fn, and this part does not take the DEK outside fn.
- You want to bind the encryption context to object_ref and context_digest (64 hexadecimal digits) so that an envelope with a different context is not opened.
- You want to store the envelope, the wrapped key (wrapped_key) and the digest of the encryption context together and open them with decrypt. Before opening, it checks that the envelope's key_ref, the wrapped key's reference and the KMS key match.
When not to use
- You want to use having encrypted or opened data as permission to use the data, as authority or as approval. Encryption is not access control, and every result carries approval: false and authorization: NOT_DERIVED.
- You also want it to store wrapped keys and envelopes or to rotate keys. Storing is the caller's role, and key rotation is the role of the B-05 Tsugite.
- You want to rely on the DEK being reliably erased from memory. Confining the DEK inside the port and zeroing it are the port's role and have not been checked.
- You want the same envelope from the same input. The Primitive generates the IV randomly, so the envelope differs every time.
- You handle plaintext larger than 16 MiB or aad larger than 64 KiB. The limits remain those of the Primitive.
- You need a part checked against a real KMS. It has never connected to a real KMS.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed, a reference is invalid or a port function is missing, it stops with INVALID_OPTIONS, when the input is malformed (not bytes, a context_digest that is not 64 hexadecimal digits, the shape of wrapped_key and so on), with INVALID_INPUT, and when a getter throws an exception, with INSPECTION_FAILED.
- When the envelope's key_ref and the wrapped key's reference do not match, or the wrapped key's KMS key and its own kms_key_ref do not match, it stops with WRAPPED_KEY_MISMATCH without sending to KMS.
- When the encryption context differs, it stops with CONTEXT_MISMATCH if KMS finds it, or with KMS_REFUSED without sending if the Tsugite finds it from its own record, and it does not open.
- When the Primitive's open does not return ok: true (a tampered envelope, a different aad and so on), it stops with OPEN_REFUSED (including authentication failures), and when seal does not return ok: true, with SEAL_REFUSED.
- When the KMS Tsugite's result is unknown, it stops with KMS_UNKNOWN, when the Tsugite did not send, with KMS_REFUSED, when the Tsugite stopped because the receipt could not be matched or GENERATED lacks the wrapped key's reference or version, with KMS_HALTED, and when the result is not the desired state (DENIED, KEY_DISABLED, FAILED and so on), with KMS_DENIED.
- When there is no handle or the Tsugite does not answer exactly true that it is usable, it stops with HANDLE_NOT_USABLE, and when withDataKey throws, returns no result or does not pass a 32-byte DEK, with KEY_ACCESS_FAILED.
- When timestamp does not return a date-time string or throws, it stops with CLOCK_FAILED, and when the previous operation has not finished, with BUSY (only one operation at a time).
- Fuzzing that compares against a separately written reference confirmed every time that the opened plaintext is the same as the plaintext that made the envelope, and that the number of times it opened when something differed was 0 (mismatches and fail-open cases were 0).
What it does not do / limits
- The Encryption at rest (envelope) part is not a Tsugite. It has no Request or Receipt of its own; the exchange with KMS is done by the bundled kms-envelope Tsugite under its own contract (kms-envelope/v1). All of those requests have intended_side_effect NONE, and this part itself does not touch the outside.
- The meaning of the bundled parts (being able to wrap or open is not permission to use) stays on each part's side.
- Having encrypted or opened data is not approval and is not authority (approval: false, authorization: NOT_DERIVED, plaintext_key_material: NOT_TRANSPORTED, derives_authority: false).
- It keeps neither the DEK nor the handle, and the results contain no key material. Its only state is a sequence number, which is lost on restart.
- It does not store wrapped keys or envelopes, rotate keys (B-05) or judge whether data may be used. Confining the DEK inside the port and zeroing it at expiry or at the end of the process are the port's role and have not been checked.
- It has never connected to a real KMS. The unit tests and fuzzing use stand-ins, and the wiring with the code of the real two parts was checked against a stand-in port that holds the DEK inside.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular KMS.
How to connect
Create it with createEnvelopeEncryption(options) in src/envelope-encryption.js. Input: kms_key_ref, request_prefix, ports and an optional timestamp. Returns: { ok, errors, instance }; encrypt returns envelope, wrapped_key and encryption_context_digest, decrypt returns plaintext, and every result carries approval: false. User provides (injected through ports): kms (the client of the kms-envelope Tsugite), keyAccess.withDataKey, and seal and open (envelope-encrypt). Side effect: NONE; credentials are held by the ports.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-04BPrimitive
Encryption in transit (TLS/mTLS) — Encryption in transit (TLS/mTLS) configuration verification part (Primitive)
The Encryption in transit (TLS/mTLS) configuration verification part (Primitive) is a standalone part that checks a TLS configuration object against rules R1 to R10 of a fixed profile (erabazu.primitive/tls-config/modern-2026/v1). It does not do TLS itself (it does not connect and does not read certificates). A value that does not meet the rules is stopped with a fixed code and position, and all violations are returned in a fixed order (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
For the problem of "looking at the padlock icon and stopping there", it checks the TLS configuration itself against versioned rules. It does not silently let through TLS 1.0 or 1.1 or SSL, weak ciphers, settings that do not check the peer's certificate or hostname, or certificate or key contents (PEM or base64 blobs) mixed into the configuration.
When to use
- You want to check a TLS configuration (min_version, max_version, ciphers_tls12, reject_unauthorized, verify_hostname, ca_refs, client_auth, alpn) against rules R1 to R10 before using it.
- In a configuration that allows TLS 1.2, you want to limit ciphers to a list of the names of 6 ECDHE AEAD ciphers (an allowlist).
- As a server-side mTLS setting that requires the peer's certificate (client_auth.required is true), you want to check that the references to the trust anchors (client_auth.ca_refs) are present.
- You want to receive the violations not one at a time but all together, in a fixed order (R1 → … → R10), with codes and positions (JSON Pointer).
- You want to call it locally, as processing that does not depend on the clock, random numbers, the environment's locale or outside state and that has no outside boundary. verify does not throw an exception and does not modify its input.
When not to use
- You want the encryption of the connection and mutual authentication themselves (a TLS connection). This part only checks the configuration and does not connect.
- You want to check certificates themselves (expiry, key length, signature algorithm, chain), revocation (CRL, OCSP), or that the peer's name matches.
- You want certificates issued or renewed, or the configuration applied to a server, a load balancer or cloud settings. Issuance and renewal are B-09, and applying the configuration stays on the Tsugite side.
- You want to check an OpenSSL cipher string as it is (separated by :, excluded with !, groups such as HIGH). It accepts only an array of cipher names.
- You want to decide TLS 1.3 cipher suites, key exchange groups (curves), signature algorithms, session resumption or 0-RTT. The profile does not decide them.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the configuration is not a plain object it stops with NOT_A_CONFIG, when there is a key that is not defined (including __proto__) with UNKNOWN_KEY, and when a required key is missing (including a value of undefined) with MISSING_KEY.
- For SSLv2, SSLv3, TLSv1 or TLSv1.1 it stops with VERSION_TOO_OLD, for any other incorrect version value with INVALID_VERSION, and when max_version is below min_version with VERSION_RANGE_INVERTED.
- When TLS 1.2 is allowed but ciphers_tls12 is missing it stops with CIPHERS_REQUIRED, and for an empty array or a cipher string with INVALID_CIPHER_LIST.
- For any cipher other than the 6 allowed ones (CBC, RC4, 3DES, NULL, anon, export, static RSA, DHE, TLS 1.3 names, differences in case or whitespace, holes in the array), it stops with CIPHER_NOT_ALLOWED.
- When reject_unauthorized is not true it stops with REJECT_UNAUTHORIZED_REQUIRED, and when verify_hostname is not true with VERIFY_HOSTNAME_REQUIRED ("true" and 1 are also violations).
- When ca_refs is empty it stops with INVALID_CA_REFS, when a reference has PEM markers with CA_REF_LOOKS_LIKE_PEM, when it is over 128 characters or contains +, = or a line break with CA_REF_LOOKS_LIKE_BLOB, and when it is not the prescribed sequence of characters with INVALID_CA_REF.
- When client_auth has the wrong shape it stops with INVALID_CLIENT_AUTH, when required is true but client_auth.ca_refs is missing or empty with CLIENT_CA_REFS_REQUIRED, when alpn is empty with INVALID_ALPN, and when an id is not printable ASCII with INVALID_ALPN_ID.
- When inspecting a value itself raises an exception (a Proxy trap, a getter that throws), it returns INSPECTION_FAILED and does not throw. Each getter is read only once per value, and the check is done on that copy.
What it does not do / limits
- The item's name refers to encryption of the connection and mutual authentication, but this part only checks the TLS configuration and does not include TLS connections or loading certificates. A configuration that meets the rules does not mean that a connection has been checked.
- It does not check certificates themselves (expiry, key length, signature algorithm, chain), revocation (CRL, OCSP), or that the peer's name matches.
- It has not been checked whether the execution environment (Node.js, OpenSSL, a load balancer and so on) actually enforces this configuration or accepts the way the names are written (the configuration is not passed to tls).
- It does not check what a ca_refs reference points to or whether it can be resolved. Because the check is on appearance, any string of up to 128 characters that matches the prescribed sequence of characters is accepted as a reference.
- The list of allowed ciphers is a judgement made on 2026-09-30. If they later become weak, this is corrected by raising the profile's version.
- A configuration that meets the rules does not mean it may be accepted (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for the TLS settings of a particular server or cloud.
How to connect
Call verify(config) in src/tls-config.js (it does not throw). Input: a plain object with only 8 keys: min_version, max_version, ciphers_tls12, reject_unauthorized, verify_hostname, ca_refs, client_auth and alpn. Returns: { ok: true, errors: [] } or { ok: false, errors: [{ code, path }, ...] } (code is from error_taxonomy, path is a JSON Pointer; all violations are listed in rule order). Turning it into an actual configuration string is up to the caller. Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing. The only dependencies are Node.js built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-05BTsugite
Key management and rotation — Key Management & Rotation Tsugite
The Key Management & Rotation Tsugite (継手, tsugite) checks requests (KeyLifecycleRequest) for key operations on an external KMS or HSM (creating a key, creating a new version, which is rotation, changing the primary, disabling a version, re-enabling it, scheduling destruction, cancelling the schedule, and describing the state), carries them, and matches the returned receipt (KeyLifecycleReceipt) against the contract. It does not carry key material. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents sending a version disablement or a scheduled destruction without evidence of authority from a person or organization, making a version that is not ENABLED the primary, scheduling destruction with too short a waiting period, and mistaking a completed key operation for permission to use the key.
When to use
- You want to create keys on an external KMS or HSM, create new versions (rotation) and change the primary, with each operation checked against the contract before it is carried.
- You want to send version disablement and scheduled destruction only after receiving evidence of authority from a person or organization bound to that key, version and op. The evidence is used up by a single change.
- You want destruction only as a schedule with a waiting period (at least one day, and at least the configured minimum), not immediately, so that CANCEL_DESTROY can return the version to DISABLED.
- You want the version made primary to be limited to versions this Tsugite has matched as ENABLED.
When not to use
- You want to destroy a key immediately. The contract has no op for immediate destruction.
- You want to check the rotation policy (when to rotate, how many generations to keep). That is the role of a local Primitive and is not included in this Tsugite; rotation_policy_ref is only carried.
- You want to encrypt or sign with a key. Encryption is the role of the KMS envelope Tsugite (for B-03, a Component bundles it), and signing is the role of the B-01 KMS signing Tsugite.
- You want to pass key material (including public keys). This Tsugite does not carry key material, and public keys are references only.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a connection to a KMS or HSM that is ready to use. It has never connected to an actual KMS or HSM; the port implementation, credentials and configuration values will be settled at a separate gate when connecting.
What it checks
- It checks the configuration. If there is no configuration, or for a KMS or key spec not in the lists, a scheduled destruction without a minimum (min_destroy_window_seconds), or a waiting period shorter than the minimum or shorter than one day, it does not send.
- It checks the evidence of authority. With no evidence field, evidence not received, a different origin (including anything that is not HUMAN or ORGANIZATION), a subject digest for another key, version or op, or evidence already used up, it does not send a disablement or a scheduled destruction.
- It checks the new primary. For an unknown version, a version that is not ENABLED, or conflicting version records, it does not send SET_PRIMARY_VERSION.
- It checks the form of references and of the request. References containing drives, .., control characters, line separators, whitespace or PEM, a request_id that is not a Ref, an op not in the contract (including DESTROY), fields the op does not use, and missing fields that the op requires are not sent.
- It matches the receipt. A spoofed request_digest, a different request_id, KMS, key or version, a mismatched op, a state outside the table, an increased or unknown effect, and a receipt that refused yet has an effect are not accepted, and it stops (HALTED).
- Receipts that carry key material or a public key itself, and receipts that derive permission to use the key (authorization), are not accepted.
- It checks the destruction time. A destruction time earlier than the waiting period or in the past, a schedule without a time, a receipt that has a time although it is not DESTROY_SCHEDULED, and a CANCEL_DESTROY that does not return to DISABLED are not accepted.
- A send whose result is unknown is resolved by resending the same request (lookup), and nothing else is sent until it is resolved. A request with the same request_id but different content is not sent.
What it does not do / limits
- It does not create evidence of authority. Disabling a version and scheduling destruction require evidence from a person or organization outside the part as input (HUMAN_OR_ORGANIZATION_EVIDENCE_FOR_KEY_DISABLE_AND_DESTROY). It does not read the content of the evidence (who issued it, its term, whether it is genuine) either.
- A completed key operation is neither permission to use the key nor business approval (authorization is NOT_DERIVED, approval: false).
- It does not store key material. This Tsugite carries requests for key operations; it neither carries nor holds key material, and it does not include deciding the rotation policy.
- Whether the KMS's own destruction waiting period is shorter than requested, whether destruction happened as scheduled, and whether a cancellation took effect are known only from the KMS's answer (DESCRIBE).
- That a reference field is not key material can be checked only by its form. A short encoded key string cannot be told apart from a reference.
- The record of version states exists only inside this Tsugite and is lost on restart. If someone else changes a version's state on the KMS, the record becomes outdated. Credentials are held by the port (HELD_BY_PORT).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority. It does not change the contract either (schema authority is NOT_SPECIFIED).
- It is not a template for using any specific cloud KMS or HSM key management API.
How to connect
Contract: erabazu.candidate/certified-tsugite/key-lifecycle/v1. Input: pass a KeyLifecycleRequest to submit(request) on createKeyLifecycleClient({ cfg, port }); before a disablement or a scheduled destruction, pass the evidence with recordAuthorityEvidence. Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the KMS or HSM (submit, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE for a scheduled destruction, NONE for DESCRIBE, EXTERNAL_REVERSIBLE for the others.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-06BTsugite
Trusted timestamp (RFC 3161) — Timestamp Tsugite
The Timestamp Tsugite (継手, tsugite) checks the fields of a time token returned by a time-stamping authority (TSA) against what we requested (hash, nonce), the pinned TSA, and the limits on clock skew and accuracy, and does not trust it if any one does not match (fail-closed). It does not parse RFC 3161 DER; signature verification is done on the JSON of six fields.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When showing when something existed, for contracts or inspections, it prevents trusting as is a time token swapped to another hash, a reused (replayed) token, a token from a TSA that is not pinned, or a time with too much skew or too coarse accuracy. It is a check so the returned time is checked before being passed on to provenance.
When to use
- You want to always confirm that the time token received from the TSA matches the hash we sent (message_imprint) and the nonce.
- You want to pin the TSA you use and check against limits you set on clock skew (max_skew_seconds) and accuracy (max_accuracy_seconds).
- You want to verify the signature over the token's fields with a pinned public key (PEM) (signature in DER form, sha256).
- You want to pass "when it existed" on to the provenance Kernel (provenance-facts).
When not to use
- You want to pass in the RFC 3161 DER (ASN.1) token returned by the TSA as is and have it checked. This Tsugite does not parse DER. The token must be passed with its six fields already extracted, and signature verification also covers the JSON of those six fields.
- You also want it to request timestamps from the TSA, or to fetch and pin the TSA certificate. Communication with the TSA is not included (no port).
- You want to issue time tokens. signToken in src/tsa.js is a test stand-in for a TSA, not a real TSA.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the imprint. If the token's message_imprint does not match the hash we sent (substitution), it is not trusted.
- It checks the nonce. If our nonce is not echoed back unchanged (replay), it is not trusted.
- It checks the TSA. A token from a TSA (tsa_id) that is not pinned is not trusted.
- It checks the time. A token whose skew from the reference time exceeds the limit, whose accuracy (accuracy_seconds) exceeds the limit, or whose gen_time is not an integer is not trusted.
- It checks the hash algorithm. An algorithm that has not been adopted is not trusted.
- It checks the signature. verifyToken does not trust the token unless the signature_valid passed in is true. verifyTokenSignature returns false for a tampered token or a signature by another key, as it cannot be verified with the pinned public key.
What it does not do / limits
- It does not parse the RFC 3161 DER (ASN.1) form. What is signed is the JSON of the token's six fields (message_imprint, hash_algo, tsa_id, nonce, gen_time, accuracy_seconds).
- It does not communicate with an actual TSA, and does not fetch or pin the TSA certificate. These are decided at deployment.
- verifyToken does not verify the signature itself. It receives signature_valid (a boolean) and decides on it. Signature verification is done separately by verifyTokenSignature.
- The tests use an ECDSA P-256 key and a test stand-in for a TSA (signToken). No actual TSA was called.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a TSA API.
How to connect
Input: call verifyToken(cfg, request, token, refTime) in src/guards.js with request { hash, nonce, hash_algo }, token { message_imprint, hash_algo, tsa_id, nonce, gen_time, accuracy_seconds, signature_valid } and the reference time refTime (in seconds). Returns: { ok, errors }. Separately, verifyTokenSignature(pinnedPubPem, fields, derSignature) in src/tsa.js verifies the signature over the six fields and returns a boolean. Not included: parsing RFC 3161 DER, communicating with the TSA, and fetching and pinning the TSA's certificate (decided at deployment). There is no port.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-07BPrimitive
Hashing and content addressing — Hashing and content addressing part (Primitive)
The Hashing and content addressing part (Primitive) is a standalone part that creates and verifies the sha256 of bytes, the typed digest used in contracts (domain, algorithm, value, target), and content addresses (sha256:<hex>). It accepts bytes only. Verification checks the shape, the algorithm, the value (by constant-time comparison), and the expected domain and target, and returns { ok, errors } without throwing an exception (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a unique fingerprint is created from content and verified as the basis for provenance and reconciliation, it prevents confusing bytes with strings, accepting a digest of the wrong shape or with a different algorithm, and verifying against the wrong domain or target.
When to use
- You want to create a sha256 (64 lowercase hex digits) or a content address (sha256:<hex>) from bytes.
- You want to create and verify the typed digest used in contracts ({ domain, algorithm: "sha256", value, target }). domain and target are labels and are not mixed into the hash.
- When verifying a digest against bytes, you want to pass the expected domain and target so that a mix-up is stopped.
- You need verification that does not throw. verifyDigest and parseContentAddress return { ok, errors } and do not throw an exception.
- You want the digest of a JSON value. Turn it into canonical bytes with the I-08 part first, then pass them to this part.
- You want to call it locally, as processing that does not depend on the clock, random numbers or the environment's locale and that has no outside boundary and no state.
When not to use
- You want to pass a string as it is. This part does not accept strings (NOT_BYTES). The caller turns a string into bytes with a fixed character encoding (such as UTF-8) before passing it.
- You want to prove origin by a digest match. A match is not proof of origin. Signature verification is B-02.
- You need an algorithm other than sha256.
- You need a size limit on the bytes. It has no limit; a size limit is decided by the I-06 part or by the caller.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the input is not bytes (Uint8Array), such as a string, a number, an array, an ArrayBuffer or another typed array, it stops with NOT_BYTES.
- When the domain has the wrong shape it stops with INVALID_DOMAIN, and when the target is empty, not a string, or longer than 1024 characters it stops with INVALID_TARGET.
- When the digest is not an object with exactly the four keys (an extra key, a missing key, or no digest at all), it stops with NOT_A_DIGEST.
- When the algorithm is not sha256 it stops with UNSUPPORTED_ALGORITHM, and when the value is not 64 lowercase hex digits (uppercase, or the wrong length) it stops with MALFORMED_VALUE.
- When the sha256 of the bytes differs from the value, it returns VALUE_MISMATCH, using constant-time comparison (crypto.timingSafeEqual).
- When the domain or target differs from what was expected it stops with DOMAIN_MISMATCH or TARGET_MISMATCH, and when expect is not an object it stops with INVALID_EXPECTATION.
- When a content address is not sha256:<64 lowercase hex digits>, it stops with MALFORMED_CONTENT_ADDRESS.
What it does not do / limits
- It does not turn strings into bytes and does not canonicalize JSON. The caller turns a string into bytes, and a JSON value is turned into canonical bytes with the I-08 part before it is passed in.
- A digest match is not proof of origin. It does not include signature verification (B-02) or key management.
- A matching digest does not mean that the content is correct, nor that it may be accepted (result_is_approval: false). It creates no authority either (derives_authority: false).
- It does not handle algorithms other than sha256.
- The digests.js copies kept inside the Tsugite (19 of them) have not yet been replaced with this part; they still have their own copies. For bytes, the value is the same byte for byte as the copies' sha256hex, but the copies also accept strings and hash them as UTF-8, while this part accepts bytes only. Any replacement is separate work, done after the compatibility table has been checked.
- It does not decide the request canonicalization plus sha256 (requestDigestValue), nor which fields go into a digest. Those stay with the caller (each Tsugite or contract).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular hashing library.
How to connect
Call verifyDigest(digest, bytes, expect) in src/digest.js (it does not throw). Input: a digest with exactly the four keys, bytes (a Uint8Array, including a Buffer), and an optional expect ({ domain, target }). Returns: { ok: true, errors: [] } or { ok: false, errors: [{ code }] } (code is from error_taxonomy). sha256Hex, digest and contentAddress throw DigestError on a value they cannot accept; parseContentAddress does not throw. Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing. The only dependency is the Node.js built-in crypto.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-08BPrimitive
Random numbers and nonces (CSPRNG) — Random number and nonce part (Primitive)
The Random number and nonce part (Primitive) is a thin wrapper over the OS CSPRNG (Node.js crypto.randomBytes). It produces randomBytes(n) (1 to 1024 bytes), nonces (hex or base64url), and randomInt(maxExclusive) (uniform, for ranges from 1 to 2^48). What it handles is only argument checking, the output format and the rejection sampling in randomInt; the quality of the randomness itself is left to the OS and Node.js crypto.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It fixes how random numbers are produced, a backstage function where "if it is weak, everything collapses". It fixes the source to the OS CSPRNG so that it cannot be swapped out, accepts no seed from the caller, checks length, encoding and range, and makes randomInt uniform by rejection sampling, which does not create the bias of x mod m. When the source fails, it produces no value and stops (fail-closed).
When to use
- You want to draw bytes of a fixed length (1 to 1024 bytes) from the OS CSPRNG.
- You want to produce a nonce as a string in lowercase hex or in unpadded base64url (the RFC 4648 alphabet for URLs and filenames).
- You want to produce an integer from 0 up to but not including max_exclusive without bias, for ranges from 1 to 2^48 (2^48 included).
- You want to ensure at the entrance that no caller seed gets mixed in. A seed field is refused with UNKNOWN_FIELD.
- You need a way of calling it that does not throw. generate(request) returns { ok, errors, value } and does not throw an exception.
When not to use
- You want the quality of the randomness (unpredictability, lack of bias) to be checked. This part has not checked it and leaves it to the OS and Node.js crypto.randomBytes.
- You want this part to judge what length is enough for your purpose. The caller decides the length, and a 1-byte nonce is also accepted.
- You need reproducible (deterministic) values. Each call draws from the OS CSPRNG, and values cannot be reproduced (by design).
- You want more than 1024 bytes in one call, integers over a range larger than 2^48, or numbers as bigint or strings.
- You use it in forked processes, cloned virtual machines and the like. The behaviour of the OS CSPRNG there has not been checked.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the request is not a plain object (an array, a Map, a class instance and so on), it stops with NOT_A_REQUEST.
- When kind is none of "bytes", "nonce" and "int" (including prototype names) it stops with UNKNOWN_KIND, and when the request has fields other than the fixed ones (including seed) with UNKNOWN_FIELD.
- When length or bytes is not an integer (number) from 1 to 1024 (0, over 1024, a fraction, a string, a bigint), it stops with INVALID_LENGTH.
- When encoding is neither "hex" nor "base64url" it stops with INVALID_ENCODING, and when max_exclusive is not an integer (number) from 1 to 2^48 with INVALID_BOUND.
- When the random source throws, returns a value of the wrong length or a value that is not bytes, or when randomInt discards 64 times in a row, it produces no value and stops with ENTROPY_UNAVAILABLE.
- When inspecting the request itself raises an exception (a getter, a Proxy trap and so on), generate returns INSPECTION_FAILED. Each field is read only once, and only the checked values are used.
What it does not do / limits
- The item's name refers to "generating unpredictable values", but this part has not checked the quality of the randomness (unpredictability, lack of bias). It leaves that to the OS and Node.js crypto.randomBytes. The statistical test is a smoke test that looks for large bias; it is not a formal statistical test such as NIST SP 800-22.
- It does not judge how many bytes to use for which purpose (uniqueness of nonces, strength of tokens). The caller decides.
- Source failure (ENTROPY_UNAVAILABLE) was checked with a substituted source. It has not been checked by making the actual OS CSPRNG fail.
- It accepts no caller seed and no means of swapping the source. The public entry's module.exports is frozen, and the internal makeRandom is not exported.
- Producing a value does not mean that the length is enough for the caller's purpose, nor that the quality of the randomness has been confirmed by a formal test (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular random number library.
How to connect
Call generate(request) in src/random.js (it does not throw). Input: one of { kind: "bytes", length }, { kind: "nonce", bytes, encoding } or { kind: "int", max_exclusive }. Returns: { ok: true, errors: [], value } (a Buffer, a string or an integer) or { ok: false, errors: [{ code }] } (code is from error_taxonomy). randomBytes, nonce and randomInt throw RandomError on a value they cannot accept. Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing. The only dependencies are the Node.js built-in crypto and util.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-09BTsugite
Certificate issuance and renewal (ACME) — Certificate Issuance & Renewal (ACME) Tsugite
The Certificate Issuance & Renewal (ACME) Tsugite (継手, tsugite) checks requests (AcmeRequest) to an ACME (RFC 8555) certificate authority (placing an order, NEW_ORDER, where a renewal is a new order with renewal_of_ref; listing challenges; notifying that a response is ready; checking the order status; requesting issuance, FINALIZE; and retrieving the certificate), carries them, and matches the returned receipt (AcmeReceipt). It does not treat a successful issuance as rightful ownership of the names or as trust (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents requesting an irreversible issuance (FINALIZE) with a CSR whose names do not match the order or without evidence from a person or organization, and using a received certificate whose names or public key differ. Because issuance is publicly recorded in Certificate Transparency, it checks both before sending and after receiving.
When to use
- You want requests for obtaining TLS certificates from an ACME certificate authority and for renewing them (a new order with renewal_of_ref) to be checked against the contract before they are carried.
- You want to send an issuance request only after receiving evidence from a person or organization bound to this directory, order, CSR and name subject (authority_subject_digest).
- You want to request issuance only after confirming that the CSR's names are exactly the order's names and that the sha256 of the CSR content and of its public key match the digests in the request.
- You want to accept a retrieved certificate only after reading back the chain and matching its names and public key against the order and the CSR.
When not to use
- You want to revoke a certificate. Revocation (REVOKE) is not in the v1 contract and needs a separate decision.
- You want to place challenge responses (an HTTP file or a DNS record). That is the role of a separate port; this Tsugite only returns references and digests for the challenges.
- You want it to decide when to renew or whether a certificate has expired. This Tsugite only checks the form and ordering of the validity period; it does not make that decision.
- You want to check the TLS configuration. That is the role of a local Primitive (B-04).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a connection to a certificate authority that is ready to use. It has never connected to an actual certificate authority; the port implementation, the account key binding (JWS signing), nonces and configuration values will be settled at a separate gate when connecting.
What it checks
- It checks the configuration. If there is no configuration or a list is empty, or for a directory, account key or name not in the lists, a wildcard that does not cover the name, or an order whose names are not DNS names, are uppercase, overlap, are empty or are not in ascending order, it does not send.
- It checks the evidence for issuance. With evidence not received, a different origin (including anything that is not HUMAN or ORGANIZATION), or evidence bound to a different order, CSR, set of names or name order, it does not send FINALIZE.
- It checks the CSR. If the CSR cannot be read, the sha256 of its content or public key differs from the digest, names are missing, extra or different, the CN is outside the order, or the CSR has two SANs or two extensionRequests, it does not send FINALIZE.
- It checks the order sequence. An unknown order, an order whose directory or account key has changed, a notification for an order or challenge that is not pending, FINALIZE for an order that is not ready, and retrieval for an order that is not valid or that this Tsugite did not FINALIZE are not sent.
- It checks the form of references and of the request. References containing .., drives, control characters or line separators, an account key reference containing whitespace, a field for the account key's private key, REVOKE, fields of other ops, and a request_id that is not a Ref are not sent.
- It matches the receipt. A spoofed request_digest, a different op, directory or order, a receipt that derives name ownership or trust, an effect that is increased, understated or unknown, and a step backwards in the order or challenge state are not accepted, and it stops (HALTED).
- It matches the certificate. If leaf_names are missing, extra or different, the public key differs from the CSR's, the chain cannot be read back or its sha256 differs, the validity period differs, or the claims disagree with the leaf that was read back, it does not mark the certificate as matched.
- When reading DER, it does not accept non-minimal lengths, high tag numbers or trailing bytes.
What it does not do / limits
- It does not create the evidence for issuance. FINALIZE requires evidence from a person or organization outside the part as input (HUMAN_OR_ORGANIZATION_EVIDENCE_FOR_CERTIFICATE_ISSUANCE). It does not check whether the evidence record really came from a person or organization either.
- A successful issuance or retrieval is neither rightful ownership of the names nor trust (name_ownership and trust_decision are NOT_DERIVED, approval: false). It does not check that the domain is owned.
- It does not run automatic certificate acquisition or renewal by itself. This Tsugite carries ACME requests; it does not include deciding when to renew, placing challenge responses, or revocation (REVOKE).
- It does not check the CSR's self-signature, or the certificate's signature, chain, revocation or trust. The leaf is taken to be the first certificate in the chain.
- The CSR and the certificate are read by a small reader that reads only the DER it needs, and the tests used only P-256 CSRs and certificates built locally. It has not been checked against certificates issued by an actual certificate authority. If a certificate authority changes how the public key is encoded, even the same key will not be marked as matched.
- Order states and received evidence exist only inside this Tsugite and are lost on restart. Names are handled only as lowercase ASCII. Credentials such as the account key are held by the port (HELD_BY_PORT).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority. It does not change the contract either (schema authority is NOT_SPECIFIED).
- It is not a template for an ACME client of any specific certificate authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/acme-certificate/v1. Input: pass an AcmeRequest to submit(request, csr) on createAcmeClient({ cfg, port }) (csr only for FINALIZE); before FINALIZE, pass the evidence with recordAuthorityEvidence. Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the certificate authority (submit and readChain, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE for FINALIZE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-11BPrimitive
Merkle tree (root, inclusion proof) — Merkle tree (root, inclusion proof) part (Primitive)
The Merkle tree (root, inclusion proof) part (Primitive) is a standalone part that builds a root from a sequence of bytes leaves using the Merkle Tree Hash (SHA-256) of section 2.1 of RFC 9162 (Certificate Transparency v2), and creates and checks inclusion proofs and consistency proofs. No operation throws; each returns { ok, errors }. Malformed input is stopped with fixed codes, and a mismatch in verification is returned as verified: false (not an error).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It is a foundation for later checking the content of something that was "bundled and signed". If the root of the bundled tree is signed, an inclusion proof can confirm, partially, that a given leaf is included, and a consistency proof can confirm that the tree after appending has the earlier tree as its beginning. The leaf and node prefixes (0x00 and 0x01) prevent forged leaves that confuse leaves with nodes.
When to use
- You want to build the root of an RFC 9162 tree (64 lowercase hex digits) from a sequence of bytes leaves (merkleRoot).
- You want to create and check an inclusion proof (audit_path) showing that a leaf is included in a root (inclusionProof, verifyInclusion).
- For a record that grows by appending, you want to create and check a consistency proof showing that the old tree is the beginning of the new tree (consistencyProof, verifyConsistency).
- You want verification mismatches (a mixed-up proof, leaf, index or root; a missing or extra proof element) returned as verified: false rather than as an exception.
- You want to call it locally as deterministic processing that has no clock, no random numbers and no outside connection. There are no dependency packages.
When not to use
- You need a transparency log. This part does not store or append to a tree, and does not handle signed tree heads (STH), log monitoring, gossip or log IDs.
- You want to determine the size of the larger tree from the proof alone. A different size whose proof path has the same shape can also give true. Take the size and the root from a signed record.
- You need a hash other than SHA-256, or you want to check proofs for a tree or log larger than 1,048,576 leaves (BAD_SIZE).
- You want to confirm consistency with old_size equal to 0, or consistency between trees of the same size (old_size = new_size). It stops with BAD_SIZE. For the same size, the caller compares the roots directly.
- You need a tree that duplicates the rightmost node when the count is odd (the Bitcoin style). This part does not duplicate; it promotes the rightmost node unchanged to the level above.
- You need speed for creating proofs many times over a large tree. Each time it creates a proof, it rebuilds the tree from the leaves it is given (O(n)), and that speed has not been measured. It also has no limit on the total bytes of the leaves; memory and time are the caller's responsibility.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with NOT_BYTES_LIST when leaves is not an array, when an element is not bytes (Uint8Array) (including holes, getters, other typed arrays, ArrayBuffer, DataView and bytes wrapped in a Proxy), or when the argument is not an object.
- It stops with TOO_MANY_LEAVES, before reading the elements, when there are more than 1,048,576 leaves, and with LEAF_TOO_LARGE, before hashing, when a single leaf is larger than 64 MiB.
- It stops with INDEX_OUT_OF_RANGE when index or leaf_index is not an integer, is negative, or is not less than the tree size. In verifyInclusion too, this is the stricter reading that treats it as an error rather than as a verification failure.
- It stops with BAD_SIZE when tree_size is not an integer from 1 to 1,048,576, or when old_size and new_size are not integers with 0 < old_size < new_size ≤ 1,048,576.
- It stops with BAD_HEX when root, old_root, new_root or a proof element is not a string of 64 lowercase hex digits, and with BAD_PROOF_SHAPE when audit_path or proof is not an array.
- It does not pass rewritten, missing, added or reordered proof elements, or a mixed-up leaf, index or root (verified: false). A proof it has created always gives true with the same root and size.
- When it stops, it returns only the one code it finds first, in a fixed order. It does not call getters, and it does not throw even for values that raise an exception when inspected (a Proxy trap and so on).
What it does not do / limits
- It does not sign (signing the root is B-12). It does not prove time either (B-06). It does not say whose root it is or when it is from.
- verified: true only means that, as long as there is no SHA-256 collision, the given leaf can be said to be included in the tree with the given root and size (or that the old tree is the beginning of the new tree). It does not show that the root is correct, that the content is correct, or that it may be accepted.
- It does not store trees, proofs or records. It keeps no state between calls; storing records and managing appends remain with the caller (H-11 and so on).
- Comparison is not constant-time (on the premise that roots and proofs are not secret). Its strength relies on the difficulty of finding SHA-256 collisions and preimages.
- The roots for 1 to 8 leaves from the certificate-transparency tests are values copied into the tests; they were not checked against the original files fetched for that purpose (they were confirmed by matching values built by hand from the definition).
- Roots, proofs and verification results are not an approval (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for a particular transparency log (a CT log, Trillian and so on).
How to connect
Call merkleRoot, inclusionProof, verifyInclusion, consistencyProof and verifyConsistency in src/merkle.js (none of them throw). The argument is an object holding leaves, index, leaf, leaf_index, tree_size, audit_path, root, old_size, new_size, old_root, new_root and proof; leaves are Uint8Array, and roots and proof elements are 64 lowercase hex digits. It returns { ok: true, errors: [], ... } or { ok: false, errors: [{ code }] }. There are no side effects, no state, no outside connection and no credentials, and there is no port for the caller to provide. Its only dependencies are Node.js built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-12BPrimitive
Signing with a local key (Ed25519) — Signing with a local key (Ed25519) part (Primitive)
The Signing with a local key (Ed25519) part (Primitive) is a standalone part that signs bytes with an Ed25519 private key held by the caller (a Node.js KeyObject), and returns the signature (unpadded base64url) and the sha256 of the SPKI DER of the derived public key (the fingerprint). It accepts the private key only as a KeyObject, and does not export, store or return it. No operation throws; each returns { ok, errors } (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind signing a record, how the key is handled is not visible. This part refuses uses that pass a private key around as bytes, PEM, JWK and so on, and accepts the private key only as a KeyObject. Keys and signatures of the wrong form are stopped with fixed codes, and when it stops, it returns no signature.
When to use
- You want to sign bytes (up to 16 MiB) with an Ed25519 private key held by the caller (sign). What to sign (a Merkle tree root, canonical bytes and so on) is decided by the caller.
- You want to show which key signed by the sha256 of the public key's SPKI DER (public_key_spki_sha256) (sign, publicKeyFingerprint).
- You want a round-trip check right after signing (verify; if it does not match, verified: false, which is not an error).
- You need the same signature every time from the same key and the same message (pure Ed25519 of RFC 8032; it uses no clock and no random numbers).
- You want to sign with a local key without using a KMS or HSM. This part does not talk to a KMS, an HSM or a key vault.
When not to use
- You want to verify a signature with a key the caller has pinned. verify is a round-trip check and is not a substitute for that. Verification with a pinned key is B-02.
- You need key storage, key rotation or revocation, or signing in an HSM or KMS. This part does not handle them.
- You want to pass the private key as bytes, a string, PEM, JWK or a WebCrypto CryptoKey. It stops with KEY_NOT_KEYOBJECT.
- You want to sign with a type of key other than Ed25519 (EC, RSA, X25519, Ed448, a symmetric key). It stops with KEY_TYPE_UNSUPPORTED.
- You want to sign a message larger than 16 MiB. It stops with MESSAGE_TOO_LARGE.
- You need resistance to side channels (timing, power). This has not been checked.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with INVALID_ARGUMENT when the argument is not a plain object or has fields other than the fixed ones. When reading the argument itself raises an exception (a getter or a Proxy trap), it is INSPECTION_FAILED.
- It stops with NOT_BYTES when message is not a Uint8Array, and with MESSAGE_TOO_LARGE when it is larger than 16 MiB.
- It stops with KEY_NOT_KEYOBJECT when the key is not a KeyObject (PEM, DER, JWK, bytes, a CryptoKey, something imitating a KeyObject, a Proxy of a KeyObject, a missing field).
- It stops with KEY_TYPE_UNSUPPORTED when the key is a KeyObject but of the wrong type (a public key for sign, a private key for verify, a symmetric key, EC, RSA, X25519, Ed448).
- It stops with INVALID_KEY_ID when key_id is not a Ref (1 to 200 printable ASCII characters excluding white space, not containing "..", and not starting with / or \).
- It stops with INVALID_SIGNATURE when signature is not a string of canonical unpadded base64url encoding 64 bytes (padding, standard base64, non-zero leftover bits, a wrong length).
- A signature with one bit flipped, an S + L signature, and a signature over another message or by another key give verified: false.
- It reads each field of the argument only once, and uses only the value it has checked even if a getter returns a different value.
What it does not do / limits
- A signature shows only that "someone holding this private key signed these bytes". It is not proof of approval, that the content is correct, that it may be accepted, or who the signer is. Deciding who may sign is outside this part.
- This part does not export the private key (it does not call export), does not store it, does not write it to logs and does not put it in results. However, it does not erase the KeyObject the caller passed (erasing it is left to GC).
- key_id is the caller's label; it does not check that it is tied to the key. To confirm which key it is, use public_key_spki_sha256.
- The Ed25519 computation itself is left to the Node.js crypto module (OpenSSL). What was checked is TEST 1 to 3 in section 7.1 of RFC 8032, and cross-checks against Node's crypto with keys created at run time.
- The strictness of verify (small-order public keys, non-canonical points and so on) follows OpenSSL, and boundaries other than the S + L signature have not been checked. It has not been cross-checked against B-02 either.
- Signatures, fingerprints and verification results are not an approval (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular signing library.
How to connect
Call sign({ message, private_key, key_id }), publicKeyFingerprint({ public_key }) and verify({ message, public_key, signature }) in src/local-sign.js (none of them throw). sign returns { ok: true, errors: [], algorithm: "Ed25519", key_id, signature, public_key_spki_sha256 } (signature is 86 characters of base64url). When it stops, it returns { ok: false, errors: [{ code }] }. The caller creates and holds the KeyObject. There are no side effects, no state and no outside connection, and there is no port for the caller to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
B-13BAdopted
Countersignature verification (.amupkg) adopted part (Adopted)
The Countersignature verification (.amupkg) adopted part (Adopted) is a part that, before an .amupkg is passed to the Runtime, checks the manifest, the self signature and the wi-t.com countersignature against the keys and revocations of the trust bundle from a fixed root (root-usb-1), and returns PASS or REFUSE. It is a part made by another writer, taken in without rewriting the bytes of the original version (33f2b1c); what ERABAZU added is only this manual and the workshop fuzzing.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind "verified", which keys and revocations were looked at is not visible. This part makes anything with a missing or incorrect countersignature REFUSE and does not let it through (fail-closed), fixes the real keys, the fetch locations and the root inside the part so that arguments cannot replace them, and refuses a rollback of the trust and different bytes under the same generation. The result carries the trust it looked at and the digest of the manifest as facts.
When to use
- You want to check the integrity, the self signature, the countersignature, the trust, revocation and the binding of the bytes of an .amupkg (a Uint8Array) before passing it to the Runtime, and receive verdict (PASS or REFUSE), code, stage and facts (verifyAmupkg; it does not throw).
- You want to receive the value for checking, when the package is opened in the Runtime after PASS, whether the returned amupkg_digest equals sha256: + facts.manifest_digest. This comparison is done by the caller.
- You want to fetch the trust bundle and save only what passed the checks to the store port (refreshTrust). When it cannot be fetched, it keeps judging with the saved one or with gen 10 built into the part.
- You want to tell apart how fresh the trust is. New admissions work until the trust's next_update, and already admitted ones work until 30 days after next_update; after that it stops with COUNTERSIG_TRUST_STALE or COUNTERSIG_TRUST_EXPIRED.
- You want to keep a record of admission only for what passed (recordAdmitted), and see the trust, the number admitted and the verifier version (status).
- You need a part whose taken-in bytes can be checked against the original commit (33f2b1c) and the sha256 of its 36 files.
When not to use
- You want to let an .amupkg brought in by a user through with the fixed samples (pinnedDigests). The fixed samples are protected by the code signature of the caller's program, and the part does not check it. pinnedDigests cannot be used for an .amupkg brought in by a user.
- You want to treat it as a result checked in the workshop that a countersignature signed with the real key PASSes. The workshop has not checked this (the workshop's tests and fuzzing use only a disposable root and keys).
- You want to use PASS as a mark of approval, of permission to sell, or of the content being correct. PASS means only that the origin, integrity and scope of review were checked.
- You want to restrict the ZIP file to exactly the same bytes as a whole. PASS is decided by the manifest, the files listed in it and the signatures, so for that use, use facts.archive_digest (the sha256 of the whole ZIP).
- You want to use the text shown to users (title_ja and detail_ja in display) as final text as it is. display is provisional text.
- You want a newer version to be used automatically when the original is fixed. It does not follow automatically (auto_follow: false). A newer version is copied again and inspected again. It is also inspected again when a new generation of the trust (gen 11 or later) is issued.
- You use it on a version other than Node.js v24.21.0. The original is an ES module for Node.js 20 or later, but the workshop has not checked other versions (NOT_VERIFIED).
What it checks
- The order of checks is: integrity, then that the signature files are not in the manifest, the self signature, the fixed samples, the countersignature file, the form of the countersignature, the trust (root and freshness), the countersignature key and signature, revocation, and the binding. When it stops, it returns code and stage (integrity, self_signature, countersignature, trust, pinned).
- It stops with PKG_ARCHIVE_UNSAFE when the ZIP exceeds the limits (96 MiB, 2000 entries, 64 MiB per entry, 192 MiB in total, a ratio of 100), has unsafe names or cannot be read; with AMUPKG_SECTION_INVALID when it does not follow the rules on allowed files and sections; with AMUPKG_DIGEST_MISMATCH when the sha256 in the manifest or a section digest does not match; with AMUPKG_PROFILE_UNSUPPORTED when the manifest profile cannot be handled; and with MANIFEST_LISTS_SIGNATURE_FILE when a signature file is listed in the manifest.
- It stops with SELF_SIGNATURE_MISSING when there is no self signature, with SELF_SIGNATURE_INVALID when it does not match the manifest, and with SELF_KEY_REVOKED when the self key is revoked.
- It stops with its own code when the countersignature file is missing (COUNTERSIG_MISSING) or unreadable (COUNTERSIG_UNREADABLE), when the schema or purpose differs, when it has an expiry (the rule is that it has none), when the scope of review (scope_id, scope_version) is missing, when the countersigner is not wi-t.com, or when it is not bound to the self key and self signature of this .amupkg (COUNTERSIG_SELF_SIGNER_MISMATCH).
- It returns REFUSE when the countersignature key is not in the trust bundle (COUNTERSIGNER_KEY_UNKNOWN), is not dedicated to countersigning, uses the wrong algorithm or is revoked, when the countersignature does not match (COUNTERSIG_SIGNATURE_INVALID), and when that countersignature is revoked (COUNTERSIG_REVOKED).
- It stops with COUNTERSIG_TRUST_UNAVAILABLE when the trust bundle cannot be checked (it cannot be fetched, and neither the saved one nor the built-in one can be used). Fetching the trust is limited to 256 KiB per file and 10 seconds, and redirects are not followed. A rollback of the trust and different bytes under the same generation are refused.
- Fuzzing of 1,000,000 runs in the workshop inspection, which changes a correct .amupkg in one way per run (rewriting, removing or adding a file covered by the manifest, replacing the countersignature, re-creating a signature with another key, rewriting random bytes and so on) and compares the result with the known answer, found 0 mismatches and 0 fail-open cases (Node.js v24.21.0).
What it does not do / limits
- PASS means only that the origin, integrity and scope of review were checked; it is not an approval, a permission to sell, or a statement that the content is correct (result_is_approval: false). It creates no authority either (derives_authority: false).
- The part checks only the manifest, the files listed in it, and the signatures. Bytes in the ZIP outside its files (such as trailing bytes) are outside what is checked, and the Runtime does not use them either. When you want the file's bytes to be identical as a whole, use facts.archive_digest (which changes when bytes are appended at the end).
- The fixed samples (pinnedDigests) are protected by the code signature of the caller's program, and the part does not check it. pinnedDigests cannot be used for an .amupkg brought in by a user.
- The production trust bundle (gen 10) carries the countersignature key wi-t-countersigner-ed25519.v1, and a countersignature not signed with that key gives COUNTERSIG_SIGNATURE_INVALID. The trust built into the part is also gen 10. However, the workshop has not checked that a countersignature signed with the real key PASSes. The workshop's tests and fuzzing do not use the real private key; they use only a disposable root and keys.
- This is not a verification implementation made by ERABAZU. It is a part made by another writer, taken in without rewriting its bytes (checked against the sha256 of 36 files and the record of the original provenance). The content, the order of checks, the stop codes and the API remain as in the original design. The files taken in also include LICENSE, LICENSES/CC0-1.0.md and NOTICE; NOTICE was added by the original writer.
- The end-to-end flow with the Runtime executable was not run in the workshop inspection (there is no executable). It passed in the CI of the original repository (run 37588955237). The original tests are 54 (53 pass, 1 skip).
- It writes the trust and the admission records to the store port, and reads the four trust files through the fetch port (LOCAL_REVERSIBLE). It keeps no state in the part's memory and uses no credentials.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Create an instance with createCountersignatureVerifier({ store, fetch?, now?, log?, pinnedDigests? }) in src/index.mjs (an ES module) and call await instance.verifyAmupkg(bytes, { admit? }). store (read, write) is a required port; fetch (null means it does not fetch), now and log can be left out. It returns { verdict, code, stage, facts, display }. It throws a TypeError only when the factory arguments are malformed or when bytes is not a Uint8Array. Its only dependencies are the Node.js built-ins (node:crypto, WebCrypto).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-01CTsugite
Object storage — Object Store Tsugite
The Object Store Tsugite (継手, tsugite) relays saving (put) and reading (get) of files and blobs to an external object backend. put sends only checked requests and matches the receipt the backend returns (ObjectReceipt) as the contract requires. get computes the digest of the returned bytes itself and hands over the bytes only when the digest matches the expected value and the receipt also matches. If anything does not match, it does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents ending at "it was saved", that is, trusting the backend's receipt or claims as is and treating corrupted content or spoofed stored objects as correct. Content read back is not treated as verified just because the backend claims it matched; this Tsugite computes the digest itself and compares.
When to use
- You want to save files or blobs to an external object backend and read them back.
- You want to confirm content read back with a digest you computed yourself, not with the backend's claim, before using it.
- You want to set the accepted content-types and a size limit, and not let through content with a spoofed kind (content that does not match its magic bytes).
- You do not want the same content stored twice, or you want to protect targets that are never overwritten once written (WORM). When resending before a receipt is settled, it is resolved by a lookup.
- You want saving to stop until a person checks it when a partial write or a spoofed receipt appears.
When not to use
- You want to handle content-types other than application/pdf, image/png, image/jpeg, text/plain and application/json with their content kind confirmed. Only these 5 types can be confirmed by magic bytes, and types that cannot be confirmed are not let through.
- You need delete or lifecycle operations for retention/TTL (C-08) or archiving (C-09). They are not included in this Tsugite (later work).
- You want to treat a successful put as "the content was confirmed correct". A successful put does not mean the content is verified (content_verified: false).
- You want to treat a successful save as approval or a decision. No success is approval.
- You want a store you can use right away. No actual backend is included, and which object store to connect to has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the request's type. Requests with additional fields, values that differ from the fixed values (const), or that violate the side effect conditions are not sent.
- It checks the pinned backend revision. Requests to a backend revision (backend_profile_revision) that is not pinned are not sent.
- It checks the content kind and size. A content-type that does not match the magic bytes (spoofed kind) or a size over the limit is not sent.
- It checks for duplicate saves and overwrites. The same object_ref with the same digest is not stored twice (ALREADY_STORED), and overwrites of WORM targets are not sent.
- It computes the digest of content read by get itself, and if it does not match the expected value, the content is not handed over (GET_REJECTED). It rejects tampering at read time.
- It matches the receipt (ObjectReceipt). A receipt with a spoofed request_digest or a partial write (MISMATCH) is not accepted, and it stops from then on (HALTED).
- A backend failure (a 500 response, or a failure after writing) is treated as UNKNOWN and not as stored. Resending is resolved by a lookup.
- It checks how secrets are passed. SecretRef accepts only secret-ref/v2 locations, and a raw secret placed next to a SecretRef or the HUMAN_CREDENTIAL class is rejected.
What it does not do / limits
- Deciding whether content is corrupted is the role of the Kernel (integrity-facts). This Tsugite only checks before sending and after receiving.
- A successful put does not mean the content is verified (content_verified: false), and no success is approval (approval: false).
- It does not include an actual backend. Which object store to connect to, the port implementation and what to pin (endpoint and receipt format) are undecided and will be settled at a separate gate when connecting.
- It does not include delete or lifecycle extensions for retention/TTL (C-08) or archiving (C-09).
- Content kind can be confirmed by magic bytes only for application/pdf, image/png, image/jpeg, text/plain and application/json.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use an object store.
How to connect
Contract: erabazu.candidate/certified-tsugite/object-store/v1. Input: pass an ObjectPutRequest and its bytes to put(ObjectPutRequest, bytes), or an ObjectGetRequest to get(ObjectGetRequest), on createObjectStoreClient({ cfg, port }). Returns: STORED, GET_VERIFIED, GET_REJECTED, REFUSED, UNKNOWN, HALTED and others. Every result has approval: false. User provides: a port to the object backend (put and get, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE for WORM, EXTERNAL_REVERSIBLE for OVERWRITE, and NONE for get.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-02CTsugite
Storage integrity verification — Storage Integrity Tsugite
The Storage Integrity Tsugite (継手, tsugite) confirms that an object read back from storage has not been corrupted, by comparing the record made at write time (recorded) with the observation at read-back (observed). If any one of hash, size or hash algorithm does not match, it does not trust it (fail-closed). Hash computation and reading from and writing to storage are done by the caller.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents stored objects from silently becoming corrupted (silent bit rot), and prevents missing tampering or substitution and using the object as is. A matching size alone is not treated as matching content; a matching hash is required separately.
When to use
- You want to confirm that an object read back from storage is the same as when it was written (whether it is corrupted, by checksum or ETag).
- You want to confirm integrity as a library on the caller's side, without placing a network relay (proxy).
- You want to accept only adopted hash algorithms and not trust results from any other algorithm.
- You have already computed or obtained the hash and size on your side and want only the comparison done.
When not to use
- You want this Tsugite to compute hashes, or to write to and read back from storage. These are not included; the caller does them and passes the results.
- You want to check an object for which no record (recorded) was kept. Without the hash, size and algo from write time, it cannot compare and does not trust it.
- You want to use it right away matched to a specific storage API's ETag format. The actual storage API and ETag format are decided in config at deployment.
- You want to check with a hash other than SHA-256. The verified scope is SHA-256 content hashing.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It compares hashes. If the recorded and observed hashes do not match (bit rot or tampering), it does not trust the object (ok: false).
- It compares sizes. If the recorded and observed sizes do not match, it does not trust the object.
- Even when the size is the same, it does not trust the object if the content hash differs (a size match alone is not let through).
- It checks the hash algorithm. Different algorithms between record and observation, and algorithms that have not been adopted, are not trusted.
- It checks for missing values. If the hash or size is missing, it does not trust the object.
What it does not do / limits
- It does not compute hashes. It does not write to or read back from storage either. The caller does these and passes the results (recorded and observed).
- It has no port to a backend. This Tsugite is only a pure function that compares the values it is given.
- If require_size is set to false, it no longer requires sizes to match (the default is true).
- The actual storage API and ETag format have not been decided. They are decided in config at deployment.
- Consolidating integrity facts as a Kernel and passing them on to signing targets or provenance is done on the Kernel side (integrity-facts, provenance-facts).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a storage API.
How to connect
Input: call verifyIntegrity(cfg, recorded, observed) in src/guards.js (a pure function). Pass as recorded the { hash, size, algo } recorded at write time, and as observed the { hash, size, algo } observed at read-back. cfg holds allowed_algos (the adopted algorithms) and require_size (default true). Returns: { ok: true, errors: [] } on a match, otherwise { ok: false, errors: [...] }. User provides: hash computation, and reading from and writing to storage (there is no port). Runtime: Node.js, with no dependency packages.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-03CTsugite
Transactional write — Transactional Write Tsugite
The Transactional Write Tsugite (継手, tsugite) checks the pinned backend, the allowed logical target and each operation's payload digest before sending an all-or-nothing (ALL_OR_NOTHING) transaction to the DB, and matches the receipt the DB returns (TransactionalWriteReceipt). A partial commit is treated as abnormal and stops it, and ABORTED is accepted only when it can be matched as having no external effect (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents assuming data "should have been saved" in the DB, that is, treating a half-written transaction as a success. COMMITTED is accepted only when it can be matched that all operations went in, and ABORTED only when it can be matched that nothing went in.
When to use
- You want to write multiple writes to a DB while keeping consistency (either all go in or none do).
- You do not want to trust the DB's receipt as is, and want to match it against the request (request_id, request_digest) and target before treating the write as done.
- You want to limit write destinations to the pinned backend and allowed logical targets, and do not want file system paths used as targets.
- You do not want the same request committed twice. When resending before a receipt is settled, it is resolved by a lookup.
- You want writing to stop until a person checks it when a partial commit or a spoofed receipt appears.
When not to use
- You need the functions of C-05 to C-09, which belong to the same contract storage-persistence/v1. They are not included in this Tsugite.
- You want to treat a successful write as approval or a decision. A write succeeding is not approval.
- You want a DB you can use right away. No actual DB is included, and which backend to connect to has not been decided.
- You want to write to file system paths (a leading / or \, or ..). They are not used as targets.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the destination. Requests to a backend that is not pinned, a target that is not allowed, or a file system path target are not sent.
- It checks operations and payloads. Tampered payloads (digest mismatch), missing or extra payloads, duplicate operations, and operation counts or payload sizes over the limit are not sent.
- It does not accept partial commits. COMMITTED must cover all operations and ABORTED must cover none with no external effect; otherwise the receipt is not accepted and it stops from then on (HALTED).
- It checks the side effect. A receipt may only reduce the side effect declared in the request; a receipt that increases it is not accepted. COMMITTED must match the intended side effect.
- It matches the receipt (TransactionalWriteReceipt). A receipt with a spoofed request_digest or for another target is not accepted, and it stops from then on (HALTED).
- A DB failure is treated as UNKNOWN and not as written. The same request_id is not committed twice, and resending is resolved by a lookup.
What it does not do / limits
- Deciding consistency is the role of the Kernels (reconcile-facts, policy-facts). This Tsugite only checks before sending and after receiving.
- A write succeeding is not approval (approval: false).
- It does not include an actual DB. Which backend to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- It does not include C-05 to C-09 of the same contract storage-persistence/v1. In the draft priority order, they are either not given a Tsugite (decision-only) or deferred (DEFER).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for a DB client.
How to connect
Contract: erabazu.candidate/certified-tsugite/storage-persistence/v1. Input: pass a TransactionalWriteRequest and payloads (as { op_ref: bytes }) to write(TransactionalWriteRequest, payloads) on createTxClient({ cfg, port }). Returns: COMMITTED, ABORTED, REFUSED, UNKNOWN, HALTED and others. Every result has approval: false. User provides: a port to the DB (commit, and optionally lookup). Side effect: EXTERNAL_REVERSIBLE or EXTERNAL_IRREVERSIBLE, as declared in the request (NONE or LOCAL_REVERSIBLE for ABORTED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-04CTsugite
Append-only ledger — Audit Ledger Tsugite
The Audit Ledger Tsugite (継手, tsugite) stacks an immutable event history on an append-only backend. Before sending, it checks the pinned ledger, the sequence and whether the event is a duplicate append, and it advances the head of the ledger only when the returned receipt (AuditAppendReceipt) matches. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When events are stacked on an external append-only store, it prevents the local head of the ledger from drifting from what the store actually holds (a sequence that rolls back or skips, duplicate appends of the same event, writes whose outcome is unknown after a failure). An append whose receipt cannot be matched never advances the head, so the way the ledger continues stays trustworthy.
When to use
- You want to stack an immutable event history, one event at a time, on an external append-only backend.
- You want to append only to one fixed ledger (pinned by ledger_id, ledger_profile_revision and contract_revision).
- You want to keep a single line of continuation in which the sequence never rolls back or skips.
- You do not want the same event to be appended twice. Duplicates are told apart by event_id and the envelope's digest; when resending before a receipt is settled, the append is resolved by a lookup.
- You want to append while checking locally the head of the ledger, any unresolved send and whether it has halted (state() returns { head, pending, halted }).
When not to use
- You want a store that allows rewriting or deletion. This Tsugite handles appends only.
- You want to stack the body of the events itself in the ledger. The envelope carries only the contract's fields, and a body mixed into it is rejected.
- You need a transparency log (Merkle inclusion proofs). That is outside the scope of this Tsugite.
- You want a store you can use right away. No actual backend is included, and which append-only service to connect to has not been decided.
- You want to treat a successful append as approval or a decision. A successful append is not approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the pinned ledger. Appends to a ledger that is not pinned are not sent.
- It checks the sequence. If, seen from the head of the ledger, the sequence rolls back or skips, it does not send and does not accept.
- It stops duplicate appends. If the same event_id with the same content is already appended, it does not send to the backend and returns ALREADY_APPENDED; if the same event_id comes with different content, it returns REFUSED.
- While a send is unresolved, it does not send the next envelope and returns REFUSED. A send whose outcome was unknown is resolved by a lookup.
- A backend failure (a 500 response or a partial write) is treated as UNKNOWN, and the head of the ledger is not advanced.
- If the ledger, sequence, event_id or target in the receipt (AuditAppendReceipt) differs from the envelope that was sent, it does not accept the receipt and does not advance the head of the ledger.
- If a receipt is inconsistent, it stops (HALTED) and does not append until a person checks it.
- It rejects envelopes that contain a body, events over the envelope size limit (max_envelope_bytes), and envelopes with a spoofed kind (content-type spoofing).
What it does not do / limits
- Deciding whether the ledger can be altered is the role of the Kernels (provenance-facts and integrity-facts). This Tsugite only checks before sending and matches the receipt.
- A successful append is not approval (approval: false).
- It does not include an actual backend. Which append-only service to connect to, the port implementation and what to pin (endpoint and receipt format) are undecided and will be settled at a separate gate when connecting.
- It does not handle transparency logs (Merkle inclusion proofs).
- It does not resolve the secret (SecretRef) used for backend authentication. That is the responsibility of the port.
- It is not a part dedicated to C-04. The same single part serves H-01 (append-only audit log), H-08 (tamper-detection chain) and H-02 (access log), and the tests and inspection are shared.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
How to connect
Contract: erabazu.candidate/certified-tsugite/audit-ledger/v1. Input: pass an AuditEventEnvelope to append(envelope) on createLedgerClient({ cfg, port }); cfg holds the pinned ledger and the envelope size limit. Returns: one of APPENDED_VERIFIED, ALREADY_APPENDED, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. state() returns { head, pending, halted }. User provides: a port to the append-only backend (append, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE (an external write that cannot be undone).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-05CTsugite
Idempotent write (WAL) — Storage Lifecycle Tsugite
The Storage Lifecycle Tsugite (継手, tsugite) checks idempotent write (IdempotentWrite) requests to a storage backend against the pinned backend, the allowed logical targets and the payload limit before sending them, and checks the backend's receipt against the contract. The external effect happens at most once; if anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents writing the same content twice when a write to storage is resent, writing different content under the same idempotency key, and treating a write as done without checking the backend's receipt.
When to use
- You want a write to the storage backend to have its external effect at most once, even when it is resent.
- You want to accept a no-effect duplicate result (DEDUP_HIT) only after checking that the stored content is the same as the content you sent.
- You want to check the payload's digest yourself and not send a payload over the size limit.
- When the result is unknown because of a backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You need a transactional write. The C-03 transactional write is a separate part (transactional-write-tsugite).
- You want this Tsugite to decide whether something is a duplicate. Duplicate detection is the role of the Kernel (reconcile-facts).
- You want to write to a location on a file system. Targets are logical targets only; file-system targets are rejected.
- You want a store you can use right away. No actual backend is included, and which storage service to connect to has not been decided.
- You want to treat a completed write as approval or a decision. A completed write is not approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the request's type. Requests with additional fields, values that differ from the fixed values (const), file-system targets, an unpinned backend or a target that is not allowed are not sent.
- A request that writes different content under the same idempotency key is not sent (REFUSED).
- It checks the payload. A payload over the size limit, or one that does not match its digest (tampered), is not sent.
- A duplicate (DEDUP_HIT) receipt that carries a new effect is not accepted. DEDUP_HIT must have no effect (NONE).
- If the stored content (stored_digest) differs from the content sent, the receipt is not accepted, even for DEDUP_HIT. Different content under the same key is treated as an anomaly.
- It checks the receipt. A spoofed request_digest, a receipt for another target, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- A backend failure is treated as UNKNOWN, and the same request_id is never sent twice. While a submission is unresolved, it returns REFUSED and does not send to the backend.
- FAILED, REFUSED and SIMULATED receipts are not accepted unless they have no effect (NONE).
What it does not do / limits
- Deciding whether something is a duplicate is the role of the Kernel (reconcile-facts). This Tsugite only checks before sending and matches after receiving.
- A completed write is not approval (approval: false).
- It does not include an actual backend. Which storage service to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- It is not a separate part for this item alone. Under the contract, C-05 to C-09, H-06 and F-04 are one part (tsugite/storage-persistence/lifecycle); this item is its idempotent-write facet, and F-04 (idempotency key handling) is also served by this facet.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a storage service.
How to connect
Contract: erabazu.candidate/certified-tsugite/storage-persistence/v1. Input: pass an IdempotentWrite request and the bytes to write (required) to submit(request, bytes) on createStorageLifecycleClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries dedup_result. Every result has approval: false. User provides: a storage backend port (submit, and optionally lookup) and the backend, target and limit settings. Side effect: EXTERNAL_* (NONE for DEDUP_HIT).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-06CTsugite
Backup and snapshot — Storage Lifecycle Tsugite
The Storage Lifecycle Tsugite (継手, tsugite) checks snapshot (Snapshot) requests to a storage backend against the pinned backend and the allowed logical targets before sending them, and checks the backend's receipt against the contract. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a snapshot request is sent, it prevents sending a target that is not allowed, and prevents treating a snapshot as taken without checking that the backend's receipt really belongs to that target. Only snapshots this Tsugite has matched can be the source of a simulated restore (C-07).
When to use
- You want to send a snapshot request for a logical target to the storage backend, and accept that the snapshot was taken as a fact only after the receipt is matched.
- You want to match a snapshot receipt together with the digest of that snapshot_ref's content (content_digest).
- You want only matched snapshots to be treated as the source of a simulated (SYNTHETIC) or dry-run restore.
- When the result is unknown because of a backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You need an actual restore from a snapshot (production PITR). A destructive real restore is out of scope; restores are simulated or dry-run only.
- You want to copy a location on a file system. Targets are logical targets only; file-system targets are rejected.
- You want a store you can use right away. No actual backend is included, and which storage service to connect to has not been decided.
- You want to treat a taken snapshot as approval or a decision. A taken snapshot is not approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the request's type. Requests with additional fields, values that differ from the fixed values (const), file-system targets, an unpinned backend or a target that is not allowed are not sent.
- It checks the receipt. A spoofed request_digest, a receipt for another target, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- The effect of a snapshot receipt may be at most a reversible external effect (EXTERNAL_REVERSIBLE). Receipts that increase the effect beyond that are not accepted.
- It keeps matched snapshots in the snapshots of state(), and does not send a restore request from a snapshot that has not been matched.
- FAILED, REFUSED and SIMULATED receipts are not accepted unless they have no effect (NONE).
- A backend failure is treated as UNKNOWN, and the same request_id is never sent twice. While a submission is unresolved, it returns REFUSED and does not send to the backend.
What it does not do / limits
- A matched snapshot does not confirm that you can actually restore from it. A destructive real restore (production PITR) is out of scope.
- Deciding what to snapshot and when is the role of the Kernel. This Tsugite only checks before sending and matches after receiving.
- A taken snapshot is not approval (approval: false).
- It does not include an actual backend. Which storage service to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- It is not a separate part for this item alone. Under the contract, C-05 to C-09, H-06 and F-04 are one part (tsugite/storage-persistence/lifecycle); this item is its snapshot facet.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a storage service.
How to connect
Contract: erabazu.candidate/certified-tsugite/storage-persistence/v1. Input: pass a Snapshot request to submit(request) on createStorageLifecycleClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries snapshot_ref and snapshot_state. Every result has approval: false. User provides: a storage backend port (submit, and optionally lookup) and settings for the pinned backend and the allowed targets. Side effect: EXTERNAL_REVERSIBLE (an external effect that can be undone).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-07CTsugite
Recovery and PITR (simulated and dry-run only) — Storage Lifecycle Tsugite
The Storage Lifecycle Tsugite (継手, tsugite) checks restore (Restore) requests, limited to simulated (SYNTHETIC) and dry-run restores, before sending them, and checks the backend's receipt against the contract. It does not perform a destructive real restore (production PITR) and does not send such a request. Only snapshots this Tsugite has matched can be the source (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a restore request is sent, it prevents restoring from a snapshot that has not been matched, trusting a result that reports a dry-run as applied, and sending a request for a destructive real restore. This Tsugite handles only simulated and dry-run restores; it does not actually return data to a point in time.
When to use
- You want to send a simulated (SYNTHETIC) or dry-run restore request based on a matched snapshot, and match its receipt.
- You want to accept only a no-effect (NONE) receipt as the result of a dry-run restore.
- You want to limit the source of a simulated or dry-run restore to snapshots this Tsugite has matched as CREATED.
When not to use
- You want to actually return to a point in time (production PITR). A destructive real restore is out of scope, and the contract defines only simulated and dry-run restores. This Tsugite does not send such a request.
- You want a means of actually recovering after an incident. This Tsugite does not perform actual recovery.
- You want a restore from a snapshot this Tsugite has not matched.
- You want a store you can use right away. No actual backend is included, and which storage service to connect to has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It does not send requests in a destructive real-restore mode.
- It does not send a restore request from a snapshot this Tsugite has not matched as CREATED.
- It does not accept a receipt that reports a dry-run request as applied. APPLIED_SYNTHETIC is accepted only for a simulated (SYNTHETIC) request.
- The effect of a restore receipt may be at most no effect (NONE) or a locally reversible effect (LOCAL_REVERSIBLE), and only NONE for dry-run. Receipts that increase the effect are not accepted.
- A spoofed request_digest or a receipt for another target is not accepted, and it stops from then on (HALTED).
- It checks the request's type. Requests with additional fields, values that differ from the fixed values (const), file-system targets, an unpinned backend or a target that is not allowed are not sent.
- FAILED, REFUSED and SIMULATED receipts are not accepted unless they have no effect (NONE).
- A backend failure is treated as UNKNOWN, and the same request_id is never sent twice. While a submission is unresolved, it returns REFUSED and does not send to the backend.
What it does not do / limits
- It does not perform actual recovery. A destructive real restore (production PITR) is out of scope, and the contract defines only simulated and dry-run restores. The restore_state attached to DONE is the result of a simulated or dry-run restore.
- The fuzzing does not call a backend, and the tests use a stand-in for the storage backend. Returning an actual backend to a point in time has not been verified.
- Deciding what to restore and when is the role of the Kernel. This Tsugite only checks before sending and matches after receiving.
- A matched simulated restore is not approval (approval: false).
- It does not include an actual backend. Which storage service to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- It is not a separate part for this item alone. Under the contract, C-05 to C-09, H-06 and F-04 are one part (tsugite/storage-persistence/lifecycle); this item is its simulated-restore facet.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/storage-persistence/v1. Input: pass a Restore request (simulated or dry-run) to submit(request) on createStorageLifecycleClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries restore_state. Every result has approval: false. User provides: a storage backend port (submit, and optionally lookup). Side effect: NONE or LOCAL_REVERSIBLE (NONE for dry-run); actual recovery is not included.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-08CTsugite
Retention and TTL — Storage Lifecycle Tsugite
The Storage Lifecycle Tsugite (継手, tsugite) checks retention (Retention) requests (EVALUATE, RETAIN, EXPIRE) against the pinned retention policies and logical targets before sending them, and checks the receipt against the contract. Irreversible deletion (EXPIRE) is permitted by the allow_expire setting (default false) and is sent only for targets without a legal hold (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When deletions based on retention periods are sent to the backend, it prevents sending deletions that are not permitted or deletions of targets under legal hold, treating an expiry evaluation alone as a deletion, and treating data as deleted without checking the backend's receipt.
When to use
- You want to send deletion (EXPIRE) requests that follow a retention policy to the backend only when the setting permits it.
- You want to receive the fact of a legal hold (a Kernel or human decision) and stop sending EXPIRE for that target from then on.
- You want to ask the backend only for an evaluation of whether data has expired (EVALUATE), without deleting.
- You want to treat data as deleted (deleted: true) only when the EXPIRE receipt is matched as EXPIRED.
When not to use
- You want this Tsugite to decide retention periods or legal holds. Those decisions belong to Kernels and people; this Tsugite only receives the facts and enforces them.
- You want the mechanism that deletes data automatically when it expires. This Tsugite only checks and sends the retention requests it is given, and it does not send EXPIRE unless the allow_expire setting is true.
- You want to move data to another tier on expiry. Moving between tiers is an Archive request, the archive and tiering facet (C-09).
- You want a store you can use right away. No actual backend is included, and which storage service to connect to has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- When the allow_expire setting is not true, it does not send EXPIRE.
- It does not send EXPIRE for a target under legal hold.
- An EVALUATE receipt that involves deletion is not accepted. EXPIRED for EVALUATE is only an expiry evaluation, not a deletion.
- A receipt that returns EXPIRED for RETAIN is treated as an anomaly and not accepted.
- Requests for a retention policy that is not in the pinned list are not sent.
- It checks the request's type. Requests with additional fields, values that differ from the fixed values (const), file-system targets, an unpinned backend or a target that is not allowed are not sent.
- It checks the receipt. A spoofed request_digest, a receipt for another target, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- FAILED, REFUSED and SIMULATED receipts are not accepted unless they have no effect (NONE). A backend failure is treated as UNKNOWN, and the same request_id is never sent twice.
What it does not do / limits
- It does not decide retention periods or legal holds. That is the role of the Kernels (redact-facts and policy-facts) and people; this Tsugite only receives those facts and enforces them.
- A completed deletion is not approval (approval: false).
- It does not include an actual backend. Which storage service to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- It is not a separate part for this item alone. Under the contract, C-05 to C-09, H-06 and F-04 are one part (tsugite/storage-persistence/lifecycle); this item is its retention facet, and H-06 (retention enforcement) is also served by this facet.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a storage service.
How to connect
Contract: erabazu.candidate/certified-tsugite/storage-persistence/v1. Input: pass a Retention request to submit(request) on createStorageLifecycleClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries decision, deleted and effect. Every result has approval: false. User provides: a storage backend port (submit, and optionally lookup) and settings for the pinned retention policies (retention_policies) and allow_expire. Side effect: EXTERNAL_IRREVERSIBLE for EXPIRE, NONE otherwise.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-09CTsugite
Archive and tiering — Storage Lifecycle Tsugite
The Storage Lifecycle Tsugite (継手, tsugite) checks tier-transition (Archive) requests to a storage backend against the pinned tiers and the allowed logical targets before sending them, and checks the backend's receipt against the contract. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a request to move data to another tier is sent, it prevents sending to a tier that is not pinned or a target that is not allowed, and prevents treating data as moved without checking the backend's receipt.
When to use
- You want to send the backend a request to move old data to a pinned tier.
- You want to accept the result of the move (transition_state and archive_ref) as a fact only after the receipt is matched.
- When the result is unknown because of a backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want this Tsugite to decide which data to move and when. That decision is the role of the Kernel; this Tsugite only checks before sending and after receiving.
- You want the move to include irreversible deletion. An archive's effect is at most a reversible external effect (EXTERNAL_REVERSIBLE); irreversible deletion is handled by EXPIRE in retention (C-08).
- You want to move data to a location on a file system. Targets are logical targets only; file-system targets are rejected.
- You want a store you can use right away. No actual backend is included, and which storage service to connect to has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- Requests to move data to a tier that is not pinned are not sent.
- It checks the request's type. Requests with additional fields, values that differ from the fixed values (const), file-system targets, an unpinned backend or a target that is not allowed are not sent.
- The effect of an archive receipt may be at most a reversible external effect (EXTERNAL_REVERSIBLE). Receipts that increase the effect beyond that are not accepted.
- A spoofed request_digest or a receipt for another target is not accepted, and it stops from then on (HALTED).
- FAILED, REFUSED and SIMULATED receipts are not accepted unless they have no effect (NONE).
- A backend failure is treated as UNKNOWN, and the same request_id is never sent twice. While a submission is unresolved, it returns REFUSED and does not send to the backend.
What it does not do / limits
- Deciding what to move and when is the role of the Kernel. This Tsugite only checks before sending and matches after receiving.
- A completed move is not approval (approval: false).
- It does not include an actual backend. Which storage service to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- It is not a separate part for this item alone. Under the contract, C-05 to C-09, H-06 and F-04 are one part (tsugite/storage-persistence/lifecycle); this item is its tier-transition facet.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a storage service.
How to connect
Contract: erabazu.candidate/certified-tsugite/storage-persistence/v1. Input: pass an Archive request to submit(request) on createStorageLifecycleClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries transition_state and archive_ref. Every result has approval: false. User provides: a storage backend port (submit, and optionally lookup) and settings for the pinned backend, the tiers (tiers) and the allowed targets. Side effect: EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-10CComponent
Raw data storage (append-only) — Raw storage (append-only, content-addressed, partitioned) part (Component)
The Raw storage part (Component) is a part that places fetched raw data, without fitting it to a schema, append-only into a separate storage target for each retention_class (pii, business, evidence). It builds fetch.json with the fetch record part (Primitive) (N-09), checks that the bytes passed in match the record's name, byte_size and sha256, and writes the payload in the record's order, and fetch.json last, with the idempotent write Tsugite (C-05). The idempotency key is the path. read reads with the C-01 Tsugite, attaching the expected sha256, and returns the bytes only when the digest matches. There are no delete or overwrite operations.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind "it has been saved", no one checks whether the data remains as it was. This part prevents placing bytes that do not match the record, overwriting a key once placed with different content, mixing PII with other partitions, and using bytes that were read even though they differ from what was placed.
When to use
- You want to place the fetched files (1 to 10000) and fetch.json append-only, once, under the keys raw/<source>/<folder_stamp>/payload/<name> and raw/<source>/<folder_stamp>/fetch.json (store).
- You want a separate storage target for each retention class, with a backend and a client for the PII partition that are separate from the other partitions. Every write goes only to the target of the partition for that fetch's retention_class.
- You want a PARTIAL fetch to keep what was fetched, the same as any other. The rest comes with the next fetch, in a different folder with a different fetched_at.
- You want to read stored raw data with the expected sha256 attached, have the part recompute the sha256 itself after the Tsugite's check, and receive a copy of the bytes only when it matches (read).
- You want to resend a write whose result is unknown as the same request and continue with the remaining writes (resolvePending). Storing the same fetch again continues from where it stopped, with the already written parts reported as DEDUP_HIT.
When not to use
- You want it to also count retention periods, delete on expiry, or enforce the rules on PII retention periods. This part only places data in separate partitions. Retention and deletion are on the side of the backend's policy (C-08 and others).
- You want to check that the fetched content is the same as the content on the platform. It only checks that the bytes passed in match the name, byte_size and sha256 in the N-09 record.
- You want to check that the PII partition is really a separate storage. It checks only that the backend_profile_ref strings and the client objects differ; it cannot tell when two names point to the same thing.
- You want this part to enforce a file size limit. The limit is set by max_payload_bytes of the C-05 Tsugite. Each send of a large file makes a copy of it.
- You want this part to detect, with RAW_CONFLICT, keys written by other processes or before a restart. Its record of keys is only inside the process; such keys are refused by the Tsugite and the backend (neither overwrites).
- You want two instances with the same request_prefix to use the same client. The Tsugite answers the same request_id with ALREADY_DONE, and a different request comes back with the earlier answer. Make request_prefix unique per client.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed, there are not exactly 3 partitions, two partitions share the same storage target, pii shares a backend or client with another partition, or a port function is missing, it stops with INVALID_OPTIONS.
- When the input to an operation is malformed (missing or extra fields, files is not a plain array of 1 to 10000 items, duplicate names, bytes is not a Uint8Array, the read key does not start with raw/, and so on), it stops with INVALID_INPUT, and when a getter throws an exception, with INSPECTION_FAILED.
- When N-09 does not build a record, it stops with FETCH_RECORD_REFUSED (with the N-09 errors attached); when N-09 throws an exception, with FETCH_RECORD_PORT_FAILED; and when the answer is inconsistent (the sha256 of canonical, the canonical form, schema, input fields, folder_stamp, the shape of files), with FETCH_RECORD_INVALID; and it sends nothing.
- When the files passed in do not match the files in the record, it sends nothing (FILE_MISMATCH). errors[0] carries name and reason (MISSING, SIZE, DIGEST, EXTRA).
- When this part has previously sent a different sha256 for a key in the write plan, it sends nothing (RAW_CONFLICT) (append-only; no overwrite).
- When the result of a write is unknown, it keeps the same request with STORE_UNKNOWN and stops store with STORE_PENDING while that is pending. When the Tsugite did not send, it reports WRITE_REFUSED, and when the Tsugite halted, the receipt could not be matched, or the result is not APPLIED or DEDUP_HIT, WRITE_FAILED; and it does not move on.
- In read, it reports OBJECT_NOT_FOUND when the object does not exist, DIGEST_MISMATCH when the digest does not match (GET_REJECTED from the Tsugite, or a different sha256 when recomputed by the part itself), READ_UNKNOWN when the result is unknown, READ_REFUSED when the Tsugite did not send, and READ_FAILED on a halt or a malformed result; and it does not return the bytes.
- When timestamp does not return an existing date-time in RFC 3339 UTC (Z) or throws, it sends nothing (CLOCK_FAILED), and when the previous operation has not finished, it stops with BUSY.
What it does not do / limits
- The Raw storage part is not a Tsugite. It has no Request or Receipt of its own; the external effects of writing and reading are caused by the bundled C-05 and C-01 Tsugite under their own contracts. This part itself does not touch the outside.
- The meaning of the bundled parts (recorded ≠ the fetch is correct, written ≠ accepted, read ≠ the content is correct) stays on each part's side.
- There are no delete or overwrite operations. It sends only IdempotentWriteRequest and ObjectGetRequest. It does not count retention periods and does not delete.
- Being written or read is not proof that the content is correct, and it is neither acceptance nor approval (approval: false, authority: NOT_DERIVED, derives_authority: false).
- Whether the C-05 key and the C-01 object_ref point to the same stored object (whether raw data that was written can be read with read) is the job of the backend profile. The tests checked this with stand-ins that point to the same stored object.
- In this part's tests it is not connected to the real N-09, C-05 or C-01. The unit tests and fuzzing use stand-ins, and the wiring with the real ones is checked by a separate wiring check. The state (the record of sent keys, the pending write) is only inside the process and is lost on restart.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for a particular data lake or object storage.
How to connect
Create it with createRawStore(options) in src/raw-store.js. It takes partitions { pii, business, evidence }, request_prefix, ports and an optional timestamp, and returns { ok, errors, instance }. The operations are store, read, resolvePending and status, and every result carries approval: false. The ports the user provides are fetchRecord (N-09) and, for each partition, write.<class>.submit (the client of the C-05 Tsugite) and read.<class>.get (the client of the C-01 Tsugite). Side effects are caused by the bundled Tsugite (EXTERNAL_REVERSIBLE), and credentials are held by the ports.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
C-11CComponent
Generational backup per retention class — Per-partition generational backup and restore check part (Component)
The Per-partition generational backup and restore check part (Component) is a part that takes backups for each retention class (pii, business, evidence) with a separate storage target, a separate client and a separate generation count. backup has the snapshot Tsugite (C-06) create a snapshot, and records only snapshots that could be matched as generation records with the idempotent write Tsugite (C-05). verifyRestore sends only simulated restores (C-07, DRY_RUN or SYNTHETIC), and marks a generation as "checked as restorable" only when the digest of the restored content matches the generation's content_digest through the C-02 Tsugite.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
A backup is not tested until it is restored. This part prevents turning a snapshot that cannot be matched into a generation, mixing PII with other partitions, overwriting a generation, and treating an unchecked backup as "restorable".
When to use
- You want to have a snapshot created for each partition and record a snapshot that could be matched, once, as the next generation at backup/<class>/<seq>.json (backup). Generation numbers start at last_seq + 1 and count only generations that were written.
- You want to send a SYNTHETIC simulated restore from a recorded generation and match the digest of the restored content returned by readRestored against the generation's content_digest (verifyRestore).
- You want to receive the fact that the generations have exceeded keep_generations as over_limit. Old generations are not deleted.
- You want to list, for each partition, the generations, the check results (restore_verified) and what is pending (status).
- You want a send whose result is unknown to be held as pending for that partition only, and resent as the same request (resolvePending). While it is pending, the other partitions do not stop.
When not to use
- You want to restore to production (a restore that actually overwrites). It sends only DRY_RUN or SYNTHETIC, and whether a restore works in production has not been checked.
- You want old generations deleted on expiry. keep_generations only returns the fact over_limit; deleting is the job of the backend's retention (C-08 and others).
- You want to treat a backup just taken as "restorable". Until the digest match in verifyRestore (SYNTHETIC, with readRestored) is done, restore_verified is false. When there is no readRestored, or with DRY_RUN, digest_verified is null.
- You want it to also check that a snapshot's content_digest correctly represents the content, or that readRestored really computed the digest from the restored content. This part only matches the returned values against each other.
- You want to run verifyRestore directly on generations taken before a restart. The state is only inside the process; after a restart, the count is restarted by passing last_seq, and generations from the earlier process are not targets of verifyRestore.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed, there are not exactly 3 partitions, two partitions share the same storage target, keep_generations or last_seq is malformed, or a port function is missing, it stops with INVALID_OPTIONS; when the input to an operation is malformed, with INVALID_INPUT; and when a getter throws an exception, with INSPECTION_FAILED.
- When the result of a snapshot is unknown, it reports SNAPSHOT_UNKNOWN (keeping the same request); when the Tsugite did not send, SNAPSHOT_REFUSED; and when the state is not CREATED, the Tsugite halted, or the result or content_digest is malformed, SNAPSHOT_FAILED; and it does not make it a generation.
- When the result of writing a generation record is unknown, it reports RECORD_UNKNOWN (keeping the same request); when the Tsugite did not send (including different content for the same key), RECORD_REFUSED; and when the Tsugite halted or the result is not APPLIED or DEDUP_HIT, RECORD_FAILED; and it does not count it as a generation.
- In verifyRestore, when the snapshot_ref is not a generation recorded by this part, it stops with UNKNOWN_GENERATION.
- When the result of a restore is unknown, it stops with RESTORE_UNKNOWN; when the Tsugite did not send or the backend refused the restore, with RESTORE_REFUSED; and when the Tsugite halted, the restore_state is unknown, or it claimed to have applied a DRY_RUN, with RESTORE_FAILED.
- When the restored digest does not match the generation's content_digest (C-02 does not return ok: true, it is not sha256, or the value differs), it reports RESTORE_DIGEST_MISMATCH (digest_verified: false); when readRestored throws or returns a malformed value, RESTORED_READ_FAILED; and when the C-02 function throws, INTEGRITY_PORT_FAILED (both with digest_verified: null); and it does not count it as checked.
- When that partition has a send whose result is unknown, it stops with CLASS_PENDING; calling resolvePending when nothing is pending returns NOTHING_PENDING; when timestamp does not return an existing date-time, it stops with CLOCK_FAILED; and when the previous operation has not finished, with BUSY.
What it does not do / limits
- The Per-partition generational backup and restore check part is not a Tsugite. It has no Request or Receipt of its own; the external effects of snapshots, simulated restores and writing generation records are caused by the bundled Tsugite (C-05, C-06, C-07) under their own contracts. This part itself does not touch the outside.
- The meaning of the bundled parts (created ≠ the content is correct, simulated restore ≠ production restore, matched ≠ approval) stays on each part's side.
- It does not send delete, retention or archive requests, and does not delete old generations. Generations are not overwritten.
- The results are not approval (approval: false, authority: NOT_DERIVED, derives_authority: false).
- When a generation record was stored but the result was malformed, the key for that seq remains in the backend and the record of the next backup keeps being refused (fail-closed). Fixing it is the caller's job, by aligning last_seq.
- In this part's tests it is not connected to the real storage-lifecycle or storage-integrity Tsugite or to a backend. The unit tests and fuzzing use stand-ins, and the wiring with the real ones is checked by a separate wiring check.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for a particular backup product.
How to connect
Create it with createBackup(options) in src/backup.js; it returns { ok, errors, instance }. options are policies { pii, business, evidence } (target, keep_generations and an optional last_seq), request_prefix, ports and an optional timestamp. The operations are backup, verifyRestore, resolvePending and status, and the results carry approval: false. Ports: storage.<class>.submit for each partition (the client of the storage-lifecycle Tsugite), verifyIntegrity (C-02) and an optional readRestored. Credentials are held by the ports.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
D-01DTsugite
Email delivery (transactional) — Email Delivery Tsugite
The Email Delivery Tsugite (継手, tsugite) sends confirmation, payment-receipt and reminder mail to a mail backend only after checking the pinned recipient, verified consent, redaction record, body digest, headers free of recipient spoofing and idempotency key, then matches the backend's receipt (NotificationReceipt) against the contract. If anything does not match, it does not send (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents email from ending at "it was delivered": sending mail with a spoofed recipient, mail without consent or garbled mail, and mistaking "it was sent" for "it arrived". The sent state (REQUESTED) is not delivery and is treated as delivered: false.
When to use
- You want to send emails such as confirmations, payment receipts and reminders through an external mail backend.
- You want to limit recipients to a pinned recipient table (recipient_ref and email address).
- You do not want to send to recipients without verified consent, or to recipients with a recorded bounce or complaint.
- You do not want the same email to be sent twice. When resending before a receipt is settled, the send is resolved by a lookup.
- You want sending to stop until a person checks it when a spoofed receipt appears.
When not to use
- You want to send by SMS, push or chat. This Tsugite handles email only; other channels are out of scope.
- You want this Tsugite to confirm delivery (D-05) or judge bounces and complaints (D-06). Those belong to the Kernel side; this Tsugite only receives their results.
- You want to send to several people with Cc or Bcc. There must be exactly one To that matches the pinned recipient, and Cc, Bcc and Resent-* are not sent.
- You want to treat a successful send as delivery or approval. Being sent is not delivery, and delivery is not approval. Consent is not Authority either.
- You want a mail backend you can use right away. No actual mail backend (SMTP or a provider's API) is included, and which one to connect to has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks for recipient spoofing. Mail whose To does not match the pinned recipient, that has two To addresses, that has Cc or Bcc, or whose headers carry CR/LF injection is not sent. Nothing is sent to a recipient that is not pinned.
- It checks consent. Sends without consent, or with consent that has not been verified, are not sent.
- It does not send to recipients with a recorded bounce or complaint.
- It checks the body. A body that does not match its digest (tampering) or that exceeds the size limit is not sent.
- It checks for duplicate sends. The same idempotency_key is not sent again (ALREADY_SENT).
- It checks the receipt (NotificationReceipt). A receipt with a spoofed request_digest, delivered without evidence, UNKNOWN upgraded to sent, or a mismatched side effect is not accepted, and it stops from then on (HALTED).
- A backend failure is treated as UNKNOWN, not as a send. A resend is resolved by a lookup.
- It checks how secrets are passed. A request that places a raw secret next to a SecretRef is rejected.
What it does not do / limits
- Deciding recipient permission, delivery consent and body minimization is the role of the Kernels (policy-facts, consent-facts and redact-facts). This Tsugite only receives their results and checks against them.
- Being sent is not delivery (REQUESTED is delivered: false). No result is approval (approval: false).
- It does not include an actual mail backend (SMTP or a provider's API). The port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- It does not handle channels other than email (SMS, push, chat).
- It does not confirm delivery (D-05) or judge bounces and complaints (D-06). It only receives those results.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a mail API.
How to connect
Contract: erabazu.candidate/certified-tsugite/request-receipt-group/v1. Input: pass a NotificationRequest, the bytes and verifiedConsents to send(NotificationRequest, bytes, verifiedConsents) on createMailClient({ cfg, port }); bounces and complaints are passed with recordSuppression. Returns: one of SENT, ALREADY_SENT, FAILED, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the mail backend (send, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE (an external send that cannot be undone).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
D-02DTsugite
SMS delivery — SMS and Push Delivery Tsugite
The SMS and Push Delivery Tsugite (継手, tsugite) checks an SMS notification request (NotificationRequest, channel sms) against the pinned recipient table, verified consent, the redaction record, the idempotency key, the body digest and limit, and the form of an SMS body before handing it to the sending platform, and matches the returned receipt (NotificationReceipt) against the contract. It carries SMS and does not include identity verification. If any check fails, it does not send (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When sending SMS, it prevents sending to numbers that are not pinned or malformed numbers (numbers not in E.164), sending without consent or a redaction record, sending the same notification twice, and mistaking "requested" for "delivered" or "read".
When to use
- You want to send notification SMS only to numbers (E.164) in the pinned recipient table.
- You want to confirm before sending that there is verified consent and a record of body minimization (both can be made required in the settings).
- You do not want the same notification sent twice. The idempotency key is required, and when resending before a receipt is settled, the send is resolved by a lookup.
- You want to stop sending to recipients that had a bounce or complaint (recordSuppression).
- You want to handle requested, delivered, read and acknowledged separately, each by its own evidence.
When not to use
- You want to use it for identity verification. This Tsugite carries SMS notification requests and does not include identity verification. An SMS being delivered or read is not verification of the person's identity.
- You want to send on the email or webhook channel. The only channels are sms and push. D-01 is the mail Tsugite and D-04 is the notify Tsugite.
- You want to treat a successful send or delivery as approval or a sign-off. Being sent or delivered is not approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a sending platform you can use right away. No actual sending platform (which SMS service) is included, and it has not been decided.
What it checks
- It checks recipients. Recipients not in the pinned recipient table, suppressed recipients (after a bounce or complaint), requests whose channel does not match the recipient's channel, and requests on the email or webhook channel are not sent.
- It checks the form of the recipient table. An SMS recipient must be in E.164 (+ followed by 7 to 15 digits starting with the country code). If even one recipient in the table is malformed (not E.164, or containing whitespace), the whole table is not used.
- It checks the SMS body. A body that is not valid UTF-8, or that contains control characters other than line breaks, is not sent.
- It does not send a body whose digest has been tampered with, or a body over the limit (max_payload_bytes).
- It checks consent and minimization. When they are made required in the settings, requests without consent or with consent that has not been verified, and requests without a redaction record, are not sent.
- Requests without an idempotency key are not sent. The same idempotency key is not sent twice (ALREADY_SENT).
- For authentication to the sending platform it carries only the location of a SecretRef. A request that puts the secret itself in the SecretRef field is not sent.
- It matches the receipt (NotificationReceipt). A delivered, read or acknowledged state without evidence, a send without a message_id, a mismatched side effect and a spoofed request_digest are not accepted, and it stops from then on (HALTED). A sending-platform failure is treated as UNKNOWN, not as a send.
What it does not do / limits
- It does not verify identity. It carries SMS notification requests and does not include identity verification. Delivered or read is not verification of the person's identity.
- Being sent is not delivery. delivered on SENT is true only in a state that has evidence of delivery. Merely requested (REQUESTED) is not delivered.
- It does not look at SMS length (limits from message splitting or character encoding). The only limit is in bytes.
- Deciding whether consent is valid and whether minimization is sufficient is the role of the Kernels (consent-facts and redact-facts), and recipient permission is the role of policy-facts. This Tsugite only checks before sending and after receiving.
- It does not include an actual sending platform. Which service to connect to, the port implementation and the recipient table are undecided and will be settled at a separate gate when connecting.
- Being sent or delivered is not approval (approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/request-receipt-group/v1 (channel sms). Input: pass a NotificationRequest, the bytes and verifiedConsents to send(NotificationRequest, bytes, verifiedConsents) on createSmsPushClient({ cfg, port }). Returns: one of SENT, ALREADY_SENT, FAILED, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the sending platform (send, and optionally lookup) and the recipient table recipients in cfg. Side effect: EXTERNAL_IRREVERSIBLE (an external send that cannot be undone).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
D-03DTsugite
Push notifications — SMS and Push Delivery Tsugite
The SMS and Push Delivery Tsugite (継手, tsugite) checks a push notification request (NotificationRequest, channel push) against the pinned recipient table (device references), verified consent, the redaction record, the idempotency key, the body digest and limit, and the form of a push body before handing it to the sending platform, and matches the returned receipt (NotificationReceipt) against the contract. It does not let the push body replace the destination, and if any check fails, it does not send (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When sending push notifications, it prevents putting a destination (such as token, to or topic) in the body and redirecting to a device that is not pinned, sending without consent or a redaction record, sending the same notification twice, and mistaking "requested" for "delivered" or "read".
When to use
- You want to send push notifications only to device references (references without whitespace) in the pinned recipient table.
- You want the destination decided only by the recipient table, and do not want the body (JSON) to change it.
- You want to confirm before sending that there is verified consent and a record of body minimization (both can be made required in the settings).
- You do not want the same notification sent twice. The idempotency key is required, and when resending before a receipt is settled, the send is resolved by a lookup.
- You want to stop sending to recipients that had a bounce or complaint (recordSuppression).
- You want to handle requested, delivered, read and acknowledged separately, each by its own evidence.
When not to use
- You want to confirm the meaning of the push body's contents (whether it is a correct notification). This Tsugite only stops replacement of the destination.
- You want to send on the email or webhook channel. The only channels are sms and push. D-01 is the mail Tsugite and D-04 is the notify Tsugite.
- You want to treat a successful send or delivery as approval or a sign-off. Being sent or delivered is not approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a sending platform you can use right away. No actual sending platform (which push service) is included, and it has not been decided.
What it checks
- It checks recipients. Recipients not in the pinned recipient table, suppressed recipients (after a bounce or complaint), requests whose channel does not match the recipient's channel, and requests on the email or webhook channel are not sent.
- It checks the form of the recipient table. A push recipient is a device reference without whitespace, and if even one recipient in the table is malformed, the whole table is not used.
- It checks the push body. A body that is not a UTF-8 JSON object, or a body that has token, to, topic, registration_ids, condition or target (replacing the destination), is not sent.
- It does not send a body whose digest has been tampered with, or a body over the limit (max_payload_bytes).
- It checks consent and minimization. When they are made required in the settings, requests without consent or with consent that has not been verified, and requests without a redaction record, are not sent.
- Requests without an idempotency key are not sent. The same idempotency key is not sent twice (ALREADY_SENT).
- For authentication to the sending platform it carries only the location of a SecretRef. A request that puts the secret itself in the SecretRef field is not sent.
- It matches the receipt (NotificationReceipt). A delivered, read or acknowledged state without evidence, a send without a message_id, a mismatched side effect and a spoofed request_digest are not accepted, and it stops from then on (HALTED). A sending-platform failure is treated as UNKNOWN, not as a send.
What it does not do / limits
- Being sent is not delivery. delivered on SENT is true only in a state that has evidence of delivery. Merely requested (REQUESTED) is not delivered.
- It does not look at the meaning of the push body's contents (whether it is a correct notification). It only stops replacement of the destination.
- Deciding whether consent is valid and whether minimization is sufficient is the role of the Kernels (consent-facts and redact-facts), and recipient permission is the role of policy-facts. This Tsugite only checks before sending and after receiving.
- It does not include an actual sending platform. Which service to connect to, the port implementation and the recipient table are undecided and will be settled at a separate gate when connecting.
- Being sent or delivered is not approval (approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific SMS or push service.
How to connect
Contract: erabazu.candidate/certified-tsugite/request-receipt-group/v1 (channel push). Input: pass a NotificationRequest, the bytes and verifiedConsents to send(NotificationRequest, bytes, verifiedConsents) on createSmsPushClient({ cfg, port }). Returns: one of SENT, ALREADY_SENT, FAILED, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the sending platform (send, and optionally lookup) and the recipient table recipients in cfg. Side effect: EXTERNAL_IRREVERSIBLE (an external send that cannot be undone).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
D-04DTsugite
Webhook delivery — Notification Delivery Tsugite
The Notification Delivery Tsugite (継手, tsugite) only decides, before an event notification such as a Webhook is sent outward, whether it may be sent. It checks the pinned recipient, allowed event, idempotency key, signed payload and absence of duplicates; if any is missing, it decides "do not send" (fail-closed). It does not send, resend or sign payloads; it only decides before sending.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
As the backstage plumbing of integrations that send notifications outward, it uses a pre-send decision to prevent sending to recipients that are not pinned (arbitrary URLs; SSRF), sending events that are not allowed, sending the same notification twice, and sending payloads without a signature.
When to use
- You want to place it in front of your own sending code so that it decides whether a notification such as a Webhook may be sent.
- You want to limit destinations to the set of pinned recipients and reject sending to arbitrary URLs (anti-SSRF).
- You want to limit the event types that may be sent to an allowed list (allowlist).
- You keep your own set of idempotency keys already sent and want to stop a duplicate send of the same notification before it is sent.
When not to use
- You want a part that actually sends Webhooks, resends them on failure, or signs payloads. This Tsugite does none of these. It only decides before sending.
- You want to confirm by a receipt that a sent notification arrived. Checking delivery receipts is not included.
- You want to check the payload signature itself. Whether a payload is signed is decided from the boolean the caller passes (payload_signed).
- You use it with the idempotency key or signature requirement turned off. Setting require_idempotency_key or require_signed_payload to false removes that check.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the recipient. A notification to anything outside the set of pinned recipients is judged not to be sent (sending to arbitrary URLs is rejected; SSRF).
- It checks the event. A notification of an event type that is not allowed is judged not to be sent.
- It checks the idempotency key. A notification without an idempotency key is judged not to be sent (when require_idempotency_key is at its default, true).
- It checks the signature. A notification whose payload_signed is not true (unsigned) is judged not to be sent (when require_signed_payload is at its default, true).
- It checks for duplicates. A notification whose key is in the given set of idempotency keys already sent is judged a duplicate send and not to be sent.
What it does not do / limits
- It does not send Webhooks. It does not resend on failure. It does not sign payloads. The user does these.
- It does not check delivery receipts. This Tsugite only decides before sending.
- The idempotency key and signature requirements can be turned off in configuration. Setting require_idempotency_key or require_signed_payload to false removes that check, and a missing idempotency key or an unsigned payload no longer stops a notification (both default to true).
- It does not keep the set of idempotency keys already sent (delivered). The caller passes it.
- The actual delivery API and signature scheme have not been decided. They are set in config at deployment.
- Delivery consent and approval paths, and delivery provenance, belong to the Kernels (consent-facts and provenance-facts).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a notification API.
How to connect
Input: call evaluate(cfg, request, delivered) in src/guards.js (a pure function that decides before sending). Pass request as { recipient, event_type, idempotency_key, payload_signed } and delivered as the set of idempotency keys already sent ({ key: 1 }); cfg holds allowed_recipients, allowed_events, require_idempotency_key and require_signed_payload. Returns: { ok: true, errors: [] } if it may be sent, otherwise { ok: false, errors: [...] }. User provides: sending, resending, signing and checking receipts (there is no port).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
D-05DTsugite
Delivery and read confirmation — Messaging Extension Tsugite
The Messaging Extension Tsugite (継手, tsugite) checks a delivery-evidence query (a DeliveryEvidence request) against the contract's types before carrying it to the backing service, and matches the returned receipt against the contract. It treats each stage (requested, delivered, read and acknowledged) as separate evidence, and does not mistake delivery for business acceptance (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When recording delivered and read as facts, it prevents accepting evidence for a stage other than the one queried, and deriving business acceptance from delivery evidence.
When to use
- You want to query the backing service for delivered, read and acknowledged as evidence per stage, and record them only after matching.
- You do not want confirmation of a higher stage to be taken to imply confirmation of a lower stage or business acceptance.
- For consent or audit, you want to keep the merely-requested state and the delivered state separate.
- You want it to stop there when a receipt is inconsistent (HALTED).
- You do not want the same query sent twice. When resending before a receipt is settled, the send is resolved by a lookup.
When not to use
- You want to confirm that the receiving side processed it (accepted it as business). This Tsugite does not confirm that.
- You want to include the message body. This Tsugite does not handle message contents; it carries only references.
- You want to send the notifications themselves. Sending is handled by the mail Tsugite (D-01), the SMS and push Tsugite (D-02 and D-03) and the notify Tsugite (D-04).
- You want to treat delivery as approval or a sign-off. Delivered is not approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a backing service you can use right away. No actual backing service (which delivery-evidence mechanism) is included, and it has not been decided.
What it checks
- It accepts only evidence for the stage queried (evidence_kind). A receipt that returns evidence for a different stage is not accepted, and it stops.
- A receipt that claims business acceptance (one that derives business acceptance from delivery evidence) is not accepted, and it stops.
- It checks references. Empty references, references with a path (containing ..) and additional fields are rejected. An empty list allows nothing.
- It checks the side effect. A delivery-evidence query has no side effect (NONE). Receipts that increase the external effect and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted. After a receipt that cannot be matched, it stops (HALTED).
- The same request_id is never sent twice. A backing-service failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not include a mechanism that produces delivery evidence itself. It only carries delivery-evidence queries to the backing service and matches the returned receipt. Which delivery-evidence mechanism to connect to is undecided and will be settled at a separate gate when connecting.
- Delivered, read and acknowledged are not business acceptance. It does not confirm that the receiving side processed it.
- Judging business acceptance is the role of the Kernels and of people. This Tsugite only checks before sending and after receiving.
- Delivered is not approval (approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific queue or messaging platform.
How to connect
Contract: erabazu.candidate/certified-tsugite/messaging-ext/v1. Input: pass a DeliveryEvidence request to submit(request) on createMessagingExtClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries evidence_kind, evidence_state, confirmed and business_acceptance in its result. Every result has approval: false. User provides: a port to the backing service (submit, and optionally lookup) and the pinned lists in cfg. Side effect: NONE (no side effect).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
D-06DTsugite
Bounce and complaint records — Messaging Extension Tsugite
The Messaging Extension Tsugite (継手, tsugite) checks records of undeliverable messages (bounces) and complaints (a BounceComplaint request) against the contract's types before carrying them to the backing service, matches the returned receipt against the contract, and returns whether the recipient was suppressed (suppression_state and suppressed). It does not treat a temporary deferral (DEFERRAL) as a reason to suppress, and if any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When recording bounces and spam reports (complaints), it prevents treating even a temporary deferral as a reason to suppress a recipient, accepting a receipt whose effect is unknown or a receipt for a different request, and sending the same record twice.
When to use
- You want to record bounces and spam reports (complaints) on the backing service and receive the suppressed state (suppression_state and suppressed).
- You do not want a recipient suppressed because of a temporary deferral (DEFERRAL).
- You want to hand suppressed recipients to the sending Tsugite (mail, SMS and push) on your side. This Tsugite returns the state for doing so.
- You want it to stop there when a receipt is inconsistent (HALTED).
When not to use
- You want it to take care of stopping sends as well. This Tsugite only returns the state; handing suppressed recipients to the sending Tsugite (mail, SMS and push) is the user's role.
- You want to include the message body. This Tsugite does not handle message contents; it carries only references.
- You want to treat a suppression as approval or a sign-off. No result is approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a backing service you can use right away. No actual backing service (which queue, publish-subscribe or delivery-evidence mechanism) is included, and it has not been decided yet.
What it checks
- A record that tries to suppress a recipient because of a temporary deferral (DEFERRAL) is rejected. DEFERRAL and records that do not suppress (NOT_SUPPRESSED) have no side effect (NONE).
- It checks the side effect. The side effect of recording a bounce or complaint is NONE or EXTERNAL_REVERSIBLE. Receipts that increase the external effect beyond the intent and receipts whose effect is unknown (UNKNOWN) are not accepted.
- It checks references. Empty references, references with a path (containing ..) and additional fields are rejected. An empty list allows nothing.
- A receipt for a different request (a spoofed request_digest) is not accepted. After a receipt that cannot be matched, it stops (HALTED).
- The same request_id is never sent twice. A backing-service failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not hand suppressed recipients to the sending Tsugite (mail, SMS and push). This Tsugite only returns the state.
- It carries bounce and complaint records and does not include functions for judging the contents of complaints or managing sender reputation.
- Deciding the policy for suppressing and resending is the role of the Kernel (policy-facts) and of people. This Tsugite only checks before sending and after receiving.
- It does not include an actual backing service. The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- No result is approval (approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific queue or messaging platform.
How to connect
Contract: erabazu.candidate/certified-tsugite/messaging-ext/v1. Input: pass a BounceComplaint request to submit(request) on createMessagingExtClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries event, suppression_state and suppressed in its result. Every result has approval: false. User provides: a port to the backing service (submit, and optionally lookup) and the pinned lists in cfg. Side effect: NONE or EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
D-07DTsugite
Message queue — Messaging Extension Tsugite
The Messaging Extension Tsugite (継手, tsugite) checks queue operations (a Queue request: ENQUEUE, DEQUEUE or ACK) against the pinned queues and the idempotency key before carrying them to the backing service, and matches the returned receipt against the contract. It does not enqueue twice, and does not mistake enqueuing for delivery. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a queue is used for asynchronous hand-off, it prevents enqueuing to a queue that is not pinned, enqueuing the same message twice, enqueuing different content under the same idempotency key, and mistaking enqueuing for processing by the receiving side.
When to use
- For asynchronous hand-off, you want to carry enqueue (ENQUEUE), dequeue (DEQUEUE) and ACK only for pinned queues.
- You do not want the same message enqueued twice. idempotency_key is required for ENQUEUE, and a duplicate (DEDUP_HIT) is treated as having no new effect.
- You want to confirm that the receipt's state answers the operation (op) before accepting it.
- You want it to stop there when a receipt is inconsistent (HALTED).
When not to use
- You want to confirm that the dequeuing side processed it (accepted it as business). This Tsugite does not confirm that.
- You want to include the message body. This Tsugite does not handle message contents; it carries only references.
- You want to treat enqueuing as approval or a sign-off. Enqueued is not approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a backing service you can use right away. No actual backing service (which queue mechanism) is included, and it has not been decided.
What it checks
- Requests to a queue that is not pinned are not sent.
- It checks references. Empty references, references with a path (containing ..) and additional fields are rejected. An empty list allows nothing.
- An ENQUEUE without idempotency_key is not sent. A request with the same idempotency key but different content is not sent (REFUSED).
- A receipt that is a duplicate (DEDUP_HIT) yet carries a new effect, and a DEDUP_HIT for DEQUEUE, are not accepted.
- A receipt whose state does not answer the operation is not accepted (only ENQUEUE→ENQUEUED or DEDUP_HIT, DEQUEUE→DEQUEUED, ACK→ACKED, or FAILED).
- It checks the side effect. Queue operations are EXTERNAL_REVERSIBLE (NONE for DEDUP_HIT and FAILED). Receipts that increase the external effect beyond the intent and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted. After a receipt that cannot be matched, it stops (HALTED).
- The same request_id is never sent twice. A backing-service failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not include the queue itself. It is a Tsugite that carries enqueue and dequeue requests. Which backing service to connect to, the port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- Enqueuing and dequeuing do not mean the receiving side processed it (accepted it as business). It does not confirm that.
- Detecting duplicates and deciding the policy for suppressing and resending are the role of the Kernels (reconcile-facts and policy-facts) and of people. This Tsugite only checks before sending and after receiving.
- Enqueued is not approval (approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific queue or messaging platform.
How to connect
Contract: erabazu.candidate/certified-tsugite/messaging-ext/v1. Input: pass a Queue request to submit(request) on createMessagingExtClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries queue_state in its result. Every result has approval: false. User provides: a port to the backing service (submit, and optionally lookup) and the pinned lists in cfg. Side effect: EXTERNAL_REVERSIBLE (NONE for DEDUP_HIT and FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
D-08DTsugite
Pub/Sub fan-out — Messaging Extension Tsugite
The Messaging Extension Tsugite (継手, tsugite) checks publish and subscribe operations (a PubSub request: PUBLISH, SUBSCRIBE or UNSUBSCRIBE) against the pinned topics and event schemas before carrying them to the backing service, and matches the returned receipt against the contract. It does not mistake publishing for being received (received: NOT_OBSERVED). If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When publish and subscribe are used for one-to-many delivery, it prevents publishing or subscribing to topics that are not pinned, publishing events whose schema is not pinned, and mistaking publishing for the subscribers having received it.
When to use
- You want to carry publish (PUBLISH), subscribe (SUBSCRIBE) and unsubscribe (UNSUBSCRIBE) only for pinned topics.
- You want every published event to carry a pinned event schema (event_schema_ref).
- You want to handle publishing and being received separately.
- You do not want the same publish sent twice. When resending before a receipt is settled, the send is resolved by a lookup.
- You want it to stop there when a receipt is inconsistent (HALTED).
When not to use
- You want to confirm that subscribers received or processed it. received is not observed (NOT_OBSERVED), and it does not confirm business acceptance either.
- You want to include the message body. This Tsugite does not handle message contents; it carries only references.
- You want to treat publishing as approval or a sign-off. Published is not approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a backing service you can use right away. No actual backing service (which publish-subscribe mechanism) is included, and it has not been decided.
What it checks
- Requests for a topic or an event schema that is not pinned are not sent.
- A PUBLISH without an event schema (event_schema_ref) is not sent.
- It checks references. Empty references, references with a path (containing ..) and additional fields are rejected. An empty list allows nothing.
- A receipt whose state does not answer the operation is not accepted (only PUBLISH→PUBLISHED, SUBSCRIBE→SUBSCRIBED, UNSUBSCRIBE→UNSUBSCRIBED, or FAILED).
- It checks the side effect. Publish and subscribe are EXTERNAL_REVERSIBLE (NONE for FAILED). Receipts that increase the external effect beyond the intent and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted. After a receipt that cannot be matched, it stops (HALTED).
- The same request_id is never sent twice. A backing-service failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It is a Tsugite that carries publish and subscribe requests to the backing service, and does not include the one-to-many distribution (fan-out) function itself.
- Publishing is not being received (received: NOT_OBSERVED).
- It does not include an actual backing service. Which publish-subscribe mechanism to connect to, the port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- Judging business acceptance is the role of the Kernels and of people. This Tsugite only checks before sending and after receiving.
- Published is not approval (approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific queue or messaging platform.
How to connect
Contract: erabazu.candidate/certified-tsugite/messaging-ext/v1. Input: pass a PubSub request to submit(request) on createMessagingExtClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries pubsub_state and received: NOT_OBSERVED in its result. Every result has approval: false. User provides: a port to the backing service (submit, and optionally lookup) and the pinned lists in cfg. Side effect: EXTERNAL_REVERSIBLE (NONE for FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
D-09DTsugite
Dead-letter handling — Messaging Extension Tsugite
The Messaging Extension Tsugite (継手, tsugite) checks dead-letter operations (a DeadLetter request: PARK, REDRIVE or PURGE) against the pinned dead-letter queues (dlq_refs) before carrying them to the backing service, and matches the returned receipt against the contract. It carries an irreversible deletion (PURGE) only when the setting allow_purge: true is present, and if any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When dead-letter queues are used to set failed messages aside, it prevents operating on dead-letter queues that are not pinned, performing an irreversible deletion (PURGE) that the settings do not allow, and accepting a receipt whose effect is larger than intended.
When to use
- You want to carry PARK and REDRIVE to pinned dead-letter queues to set failed messages aside.
- You want to carry an irreversible deletion (PURGE) only when it has been explicitly allowed in the settings (allow_purge defaults to false).
- You do not want the same operation sent twice. When resending before a receipt is settled, the send is resolved by a lookup.
- You want it to stop there when a receipt is inconsistent (HALTED).
When not to use
- You want it to judge whether something may be deleted. Judging whether something may be deleted is the role of the Kernels and of people.
- You want to include the message body. This Tsugite does not handle message contents; it carries only references.
- You want to treat a successful set-aside or deletion as approval or a sign-off. No result is approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a backing service you can use right away. No actual backing service (which dead-letter mechanism) is included, and it has not been decided.
What it checks
- Requests to a dead-letter queue that is not pinned are not sent.
- A PURGE is not sent unless the setting allow_purge: true is present.
- It checks references. Empty references, references with a path (containing ..) and additional fields are rejected. An empty list allows nothing.
- A receipt whose state does not answer the operation is not accepted (only PARK→PARKED, REDRIVE→REDRIVEN, PURGE→PURGED, or FAILED).
- It checks the side effect. PARK and REDRIVE are EXTERNAL_REVERSIBLE, PURGE is EXTERNAL_IRREVERSIBLE, and FAILED is NONE. Receipts that increase the external effect beyond the intent and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted. After a receipt that cannot be matched, it stops (HALTED).
- The same request_id is never sent twice. A backing-service failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- What PURGE deletes cannot be undone (EXTERNAL_IRREVERSIBLE). This Tsugite only checks whether the settings allow it.
- It is a Tsugite that carries dead-letter operation requests, and does not include a function that finds failed messages and sets them aside on its own.
- Judging whether something may be deleted is the role of the Kernels and of people. This Tsugite only checks before sending and after receiving.
- It does not include an actual backing service. The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- No result is approval (approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific queue or messaging platform.
How to connect
Contract: erabazu.candidate/certified-tsugite/messaging-ext/v1. Input: pass a DeadLetter request to submit(request) on createMessagingExtClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries dlq_state in its result. Every result has approval: false. User provides: a port to the backing service (submit, and optionally lookup) and the pinned lists in cfg. Side effect: EXTERNAL_REVERSIBLE for PARK and REDRIVE; EXTERNAL_IRREVERSIBLE for PURGE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
E-01ETsugite
Scheduled trigger (cron) — Job Control Tsugite
The Job Control Tsugite (継手, tsugite) checks requests (Request) to the execution platform against pinned lists before sending them, and matches the returned receipts against the contract. As a scheduled trigger (cron), it checks requests to register, update or disable a schedule (ScheduleTrigger), sends them to the execution platform's scheduler, and matches the schedule state and the next fire in the receipt. The firing itself is done by the execution platform.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When daily or hourly runs are left to an external scheduler, it prevents registering unpinned schedules or trigger specs, misreading a disabled schedule as still having a next fire, and treating a schedule as registered without matching the receipt. It is a check so that a successful registration is not mistaken for business authorization.
When to use
- You want requests to register, update or disable daily or hourly schedules on an external scheduler to be checked against pinned schedules (schedule_refs) and trigger specs (trigger_spec_refs) before sending.
- You do not want to trust the scheduler's receipt as is, and want to match that the receipt's state answers the request (op) (REGISTER→REGISTERED, UPDATE→UPDATED, DISABLE→DISABLED) or is a FAILED with no effect before using it.
- You do not want to send the same request twice. A send whose result was unknown is resolved by a lookup.
- You want further sends to stop (HALTED) when a receipt cannot be matched.
When not to use
- You want to confirm that the schedule fired on time. This Tsugite only looks at the registration receipt, not at whether it fired.
- You want a scheduler itself. No actual execution platform is included, and which scheduler to connect to has not been decided.
- You want to treat a registered schedule as business authorization to run that job. Deciding what may run and when is the role of the Kernel (policy-facts).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks references. It does not send unpinned schedules or trigger specs, empty references, or references containing a path or "..". An empty list allows nothing.
- It accepts only a receipt state that answers the request (op) (REGISTER→REGISTERED, UPDATE→UPDATED, DISABLE→DISABLED) or a FAILED with no effect (no side effect and no next fire). It does not accept any other state.
- It does not accept a receipt in which a disabled or failed schedule carries a next fire (next_fire_ref).
- When the receipt's request_id, request_digest or references do not match the request (request_digest spoofing), it does not accept the receipt.
- It does not accept receipts that increase the external effect or whose effect is unknown (UNKNOWN). The effect must be no more than the request's intent on both axes: how far it reaches and whether it can be undone.
- If a receipt cannot be matched, it stops (HALTED) and sends nothing further. It never sends the same request_id twice; a failure of the execution platform is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- It does not check whether the schedule fired on time (it looks only at the registration receipt). The firing itself is done by the execution platform.
- Deciding what may run and when (policy-facts) and detecting duplicates (reconcile-facts) are the roles of the Kernels. Business authorization, retry authorization and compensation approval belong to the Kernels and to people; this Tsugite only checks before sending and after receiving.
- Enqueued, executed and completed are not approval. Every result carries approval: false.
- It does not include an actual execution platform (which scheduler, queue or lock mechanism). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific scheduler or queue.
How to connect
Contract: erabazu.candidate/certified-tsugite/workflow-jobs/v1. Input: pass a ScheduleTrigger request to submit(request) on createWorkflowClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries schedule_state and next_fire_ref. User provides: a port to the execution platform (submit, and optionally lookup) and cfg schedule_refs and trigger_spec_refs. Side effect: EXTERNAL_REVERSIBLE (NONE for duplicates, denials and failures).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
E-02ETsugite
Delayed job — Job Control Tsugite
The Job Control Tsugite (継手, tsugite) checks requests (Request) to the execution platform against pinned lists before sending them, and matches the returned receipts against the contract. As a delayed job, it checks a request for work to run N minutes later (DelayedJob) against the pinned jobs, sends it to the execution platform, and matches the enqueue state (enqueue_state) and the worker assignment (worker_assignment_ref) in the receipt. Waiting and then running the work is done by the execution platform.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When work to run later, such as a reminder, is handed to an external execution platform, it prevents enqueuing unpinned jobs, enqueuing the same work twice, and misreading a failed enqueue as having a worker assigned. It is a check so that a successful enqueue is not mistaken for business authorization.
When to use
- You want to hand work to run N minutes later to an external execution platform and send only pinned jobs (job_refs).
- You do not want the same work enqueued twice. A duplicate (DEDUP_HIT) is taken as having no new effect, and different content is never sent under the same idempotency key.
- You do not want to trust the execution platform's receipt as is, and want to match it against the contract before treating the work as enqueued.
- You want to resolve a send whose result was unknown by a lookup, without sending it twice.
When not to use
- You want to confirm that the enqueued work ran at the scheduled time. This Tsugite only looks at the enqueue receipt, not at whether the work ran.
- You want the mechanism that waits and runs the work itself. No actual execution platform is included, and which mechanism to connect to has not been decided.
- You want to treat a successful enqueue as business authorization to run that work. Deciding what may run and when is the role of the Kernel (policy-facts).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks references. It does not send unpinned jobs, empty references, or references containing a path or "..". An empty list allows nothing.
- It does not send a request that reuses an idempotency key with different content. It does not accept a receipt in which a duplicate (DEDUP_HIT) carries a new effect.
- It does not accept a receipt in which a failed enqueue carries a worker assignment (worker_assignment_ref).
- When the receipt's state does not answer the request, it does not accept the receipt.
- When the receipt's request_id, request_digest or references do not match the request (request_digest spoofing), it does not accept the receipt.
- It does not accept receipts that increase the external effect or whose effect is unknown (UNKNOWN). The effect must be no more than the request's intent on both axes: how far it reaches and whether it can be undone.
- If a receipt cannot be matched, it stops (HALTED) and sends nothing further. It never sends the same request_id twice; a failure of the execution platform is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- It does not check whether the enqueued work ran (it looks only at the enqueue receipt). Waiting and running are done by the execution platform.
- Deciding what may run and when (policy-facts) and detecting duplicates (reconcile-facts) are the roles of the Kernels. Business authorization, retry authorization and compensation approval belong to the Kernels and to people; this Tsugite only checks before sending and after receiving.
- Enqueued, executed and completed are not approval. Every result carries approval: false.
- It does not include an actual execution platform (which scheduler, queue or lock mechanism). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific scheduler or queue.
How to connect
Contract: erabazu.candidate/certified-tsugite/workflow-jobs/v1. Input: pass a DelayedJob request to submit(request) on createWorkflowClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries enqueue_state and worker_assignment_ref. User provides: a port to the execution platform (submit, and optionally lookup) and cfg job_refs. Side effect: EXTERNAL_REVERSIBLE (NONE for duplicates, denials and failures).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
E-03ETsugite
Task queue and worker — Job Control Tsugite
The Job Control Tsugite (継手, tsugite) checks requests (Request) to the execution platform against pinned lists before sending them, and matches the returned receipts against the contract. As a task queue and worker, it checks a request for a heavy task (TaskEnqueue) and its content bytes against the pinned queues, the content digest and the size limit, sends them to the execution platform's queue, and matches the enqueue state and the worker assignment in the receipt. Running the task is done by the execution platform.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When heavy work is put on a queue to run in the background, it prevents enqueuing content that was tampered with on the way or is too large, enqueuing the same task twice, and misreading a failed enqueue as having a worker assigned.
When to use
- You want to enqueue tasks only to pinned queues (queue_refs) to run heavy work in the background.
- You want to check the digest and size limit (max_payload_bytes) of the content (bytes) yourself before sending it to the queue.
- You do not want the same task enqueued twice. A duplicate (DEDUP_HIT) is taken as having no new effect, and different content is never sent under the same idempotency key.
- You want to resolve a send whose result was unknown by a lookup, without sending it twice.
When not to use
- You want to confirm that the enqueued task ran. This Tsugite only looks at the enqueue receipt, not at whether the task ran.
- You want a queue mechanism itself. No actual execution platform is included, and which queue to connect to has not been decided.
- You want to treat a successful enqueue as business authorization to run that work. Deciding what may run and when is the role of the Kernel (policy-facts).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks references. It does not send unpinned queues, empty references, or references containing a path or "..". An empty list allows nothing.
- It checks the content. When the request's payload_digest (target: task_ref) does not match the content bytes (tampering), it does not send. TaskEnqueue requires bytes.
- It does not send content that exceeds the limit (max_payload_bytes).
- It does not send a request that reuses an idempotency key with different content. It does not accept a receipt in which a duplicate (DEDUP_HIT) carries a new effect.
- It does not accept a receipt in which a failed enqueue carries a worker assignment (worker_assignment_ref).
- When the receipt's request_id, request_digest or references do not match the request (request_digest spoofing), it does not accept the receipt.
- It does not accept receipts that increase the external effect or whose effect is unknown (UNKNOWN). The effect must be no more than the request's intent on both axes: how far it reaches and whether it can be undone.
- If a receipt cannot be matched, it stops (HALTED) and sends nothing further. It never sends the same request_id twice; a failure of the execution platform is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- It does not check whether the enqueued task ran (it looks only at the enqueue receipt). The task is run by the execution platform.
- Deciding what may run and when (policy-facts) and detecting duplicates (reconcile-facts) are the roles of the Kernels. Business authorization, retry authorization and compensation approval belong to the Kernels and to people; this Tsugite only checks before sending and after receiving.
- Enqueued, executed and completed are not approval. Every result carries approval: false.
- It does not include an actual execution platform (which scheduler, queue or lock mechanism). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific scheduler or queue.
How to connect
Contract: erabazu.candidate/certified-tsugite/workflow-jobs/v1. Input: pass a TaskEnqueue request and its bytes (required) to submit(request, bytes) on createWorkflowClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries enqueue_state and worker_assignment_ref. User provides: a port to the execution platform (submit, and optionally lookup) and cfg queue_refs and max_payload_bytes (required). Side effect: EXTERNAL_REVERSIBLE (NONE for duplicates, denials and failures).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
E-04ETsugite
Workflow orchestration (saga) — Job Control Tsugite
The Job Control Tsugite (継手, tsugite) checks requests (Request) to the execution platform against pinned lists before sending them, and matches the returned receipts against the contract. As workflow orchestration (saga), it checks requests to move a saga step (SagaStep) forward (FORWARD) or to compensate it (COMPENSATE) against the pinned sagas and steps, sends them to the execution platform, and matches the step state in the receipt. Coordinating the saga is done by the execution platform, and a successful compensation is not mistaken for a person's approval of the cancellation.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a multi-step process fails midway and is rolled back, it prevents sending a compensation without a compensation-step reference, sending steps with an unknown effect or irreversible steps without permission, and treating successful compensation as a person's approval of the cancellation.
When to use
- You want to send the steps of a multi-step process only with pinned sagas (saga_refs) and steps (step_refs, including compensation steps).
- When rolling back after a midway failure, you want to send compensation requests (COMPENSATE) only with a compensation-step reference.
- You want to match that the step state in the receipt answers the request (FORWARD→EXECUTED, FAILED or SKIPPED; COMPENSATE→COMPENSATED, FAILED or SKIPPED).
- You want to send steps with irreversible effects only when explicitly set in the configuration (allow_irreversible: true). The default is false.
When not to use
- You want to treat successful compensation as a person's approval of the cancellation. Approval belongs to the Kernels and to people; from the receipt it accepts only NOT_DERIVED.
- You want to treat a successfully executed step as business authorization. It does not accept receipts that derive business authorization.
- You want saga coordination (the state machine for multi-step processes) itself. Coordination is on the execution platform side; no actual execution platform is included, and which one to connect to has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks references. It does not send unpinned sagas or steps, empty references, or references containing a path or "..". An empty list allows nothing.
- It does not send a compensation (COMPENSATE) without a compensation-step reference.
- It does not send steps whose intended effect is UNKNOWN. It sends steps with irreversible effects only when the configuration sets allow_irreversible: true.
- When the step state in the receipt does not answer the request, it does not accept the receipt.
- It does not accept receipts that derive compensation approval or business authorization (it accepts only NOT_DERIVED).
- When the receipt's request_id, request_digest or references do not match the request (request_digest spoofing), it does not accept the receipt.
- It does not accept receipts that increase the external effect or whose effect is unknown (UNKNOWN). The effect must be no more than the request's intent on both axes: how far it reaches and whether it can be undone.
- If a receipt cannot be matched, it stops (HALTED) and sends nothing further. It never sends the same request_id twice; a failure of the execution platform is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- Saga coordination (the state machine for multi-step processes) is the role of the execution platform; this Tsugite does not have it. It does not roll back either; it only checks and sends compensation requests and matches the receipts.
- Successful compensation is not a person's approval of the cancellation. No success is approval, and every result carries approval: false.
- Deciding what may run and when (policy-facts) and detecting duplicates (reconcile-facts) are the roles of the Kernels. Business authorization, retry authorization and compensation approval belong to the Kernels and to people; this Tsugite only checks before sending and after receiving.
- It does not include an actual execution platform (which scheduler, queue or lock mechanism). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific scheduler or queue.
How to connect
Contract: erabazu.candidate/certified-tsugite/workflow-jobs/v1. Input: pass a SagaStep request to submit(request) on createWorkflowClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries step_state, compensation_authorization and business_authorization. User provides: a port to the execution platform (submit, and optionally lookup) and cfg saga_refs, step_refs and allow_irreversible. Side effect: as intended for each step (NONE for SKIPPED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
E-05ETsugite
Distributed lock — Job Control Tsugite
The Job Control Tsugite (継手, tsugite) checks requests (Request) to the execution platform against pinned lists before sending them, and matches the returned receipts against the contract. As a distributed lock, it checks requests to acquire (ACQUIRE), renew (RENEW) or release (RELEASE) a lock (Lock) against the pinned locks, this Tsugite's holder (holder_ref) and a TTL within the limit, sends them to the execution platform, and matches the lock state in the receipt. Mutual exclusion itself is the execution platform's role, and it does not do leader election.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When mutual exclusion for concurrent runs is left to an external lock mechanism, it prevents touching another holder's lock, taking a lock with no TTL or a TTL over the limit, and misreading an unknown result (UNKNOWN) as "held". It is a check so that a lock not held is never treated as held.
When to use
- You leave mutual exclusion for concurrent runs to an external lock mechanism and want to handle pinned locks (lock_refs) only with this Tsugite's holder (holder_ref).
- You want locks to have a TTL and to send only ACQUIRE and RENEW requests that do not exceed the TTL limit (max_lock_ttl_seconds).
- You want "held" (held: true) only when the receipt's state is ACQUIRED or HELD. It is never derived from UNKNOWN.
- You want to accept a fencing token (fencing_token_ref) only in receipts for a lock that is held (ACQUIRED or HELD).
When not to use
- You want to use it for leader election (E-06). This Tsugite's contract has no leader election kind, and it has neither election terms nor observation of other holders. E-06 is handled by the Component that bundles this Tsugite's distributed locks (the leader election part).
- You want to rely on this Tsugite for correct mutual exclusion. The correctness of lock exclusion is the role of the execution platform; this Tsugite only checks the consistency of the holder, the TTL and the receipt.
- You want a lock mechanism itself. No actual execution platform is included, and which lock mechanism to connect to has not been decided.
- You want to treat holding a lock as business authorization to run that work. Deciding what may run and when is the role of the Kernel (policy-facts).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks references. It does not send unpinned locks, empty references, or references containing a path or "..". An empty list allows nothing.
- It does not send lock requests for a holder other than this Tsugite's holder. If another holder has the lock, the result is DENIED and is taken as having no effect.
- It does not send ACQUIRE or RENEW without a TTL, or requests with a TTL over the limit (max_lock_ttl_seconds).
- When the receipt's state does not answer the request (other than ACQUIRE→ACQUIRED, HELD, DENIED, UNKNOWN; RENEW→HELD, EXPIRED, DENIED, UNKNOWN; RELEASE→RELEASED, EXPIRED, DENIED, UNKNOWN), it does not accept the receipt.
- It does not accept receipts with a fencing token on a lock that is not held, such as a released lock. It never derives "held" from UNKNOWN.
- It does not accept requests of a leader election (E-06) kind, because the contract has no such kind.
- When the receipt's request_id, request_digest or references do not match the request (request_digest spoofing), it does not accept the receipt.
- It does not accept receipts that increase the external effect or whose effect is unknown (UNKNOWN), and stops (HALTED) on a receipt it cannot match. It never sends the same request_id twice; a failure of the execution platform is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- It does not do leader election. E-06 is handled by the Component that bundles this Tsugite (the leader election part). This Tsugite itself has no election kind.
- The correctness of lock exclusion is the role of the execution platform. This Tsugite only checks the consistency of the holder, the TTL and the receipt.
- Deciding what may run and when (policy-facts) and detecting duplicates (reconcile-facts) are the roles of the Kernels. Business authorization, retry authorization and compensation approval belong to the Kernels and to people; this Tsugite only checks before sending and after receiving.
- Enqueued, executed and completed are not approval. Every result carries approval: false.
- It does not include an actual execution platform (which scheduler, queue or lock mechanism). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific scheduler or queue.
How to connect
Contract: erabazu.candidate/certified-tsugite/workflow-jobs/v1. Input: pass a Lock request to submit(request) on createWorkflowClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries lock_state, held and fencing_token_ref. User provides: a port to the execution platform (submit, and optionally lookup) and cfg lock_refs, holder_ref and max_lock_ttl_seconds. Side effect: EXTERNAL_REVERSIBLE (NONE for duplicates, denials and failures).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
E-06EComponent
Leader election — Leader election part (Component)
The Leader election part (Component) is a part that bundles the E-05 distributed lock Tsugite to elect a leader, so that only one runs in a redundant setup. It campaigns, renews before the deadline and resigns, and answers "leader" only when it holds the lock, has a fencing token, and is before the lease deadline (the time before sending + ttl − safety margin). On a denial, an expiry, an unknown result, a failure or the clock going backwards, it immediately stops being the leader (fail-closed). Being the leader is not authority.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind availability, it prevents two participants contending for the same lock from both claiming to be the leader at the same time (when the lock infrastructure is correct), seeing the deadline as longer by the delay before the lock's result comes back, and claiming to be the leader without a fencing token.
When to use
- You want only one participant to run the work in a redundant setup. Inject a client of the E-05 distributed lock Tsugite (one that has submit(LockRequest)) as lock.
- You want to campaign (campaign), renew before the deadline (renew), resign (resign) and check whether it is the leader now (status), with operations that do not throw an exception.
- You want to receive a fencing token (fencing_token_ref) while it is the leader and pass it to the write target. When leader is true, fencing_token_ref is always present.
- You want to estimate the deadline conservatively as the time read before requesting the lock + ttl − safety margin. The time is taken only from the injected monotonic clock (clock.now()).
- You want it to stop being the leader locally at once when it resigns, regardless of the result of releasing the lock.
When not to use
- You want to use being the leader as authority or approval. Every result carries approval: false and authority: NOT_DERIVED.
- You want this part to stop writes by an old leader. Stopping writes by an old leader with a fencing token is the role of the write target; this part only passes the token on.
- You want to treat "leader" as assurance of the latest state of the lock infrastructure. "Leader" is this participant's local judgement.
- You want to stay the leader across a restart. The state is only inside this process, and after a restart it is not the leader until it campaigns again.
- You need a part checked against a real lock infrastructure. It has never connected to a real infrastructure and was checked only with a stand-in lock.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed (ttl outside 1 to 3600, a safety margin of ttl or more, a lock without submit, a clock without now, a reference containing ".."), it stops with INVALID_OPTIONS.
- When the previous operation has not finished, it stops the second one with BUSY (only one operation at a time).
- When it tries to renew while not the leader, it stops with NOT_LEADER, and on a renewal after the deadline with LEASE_EXPIRED, without sending to the lock. When the lock had expired on the infrastructure's side, the result is also LEASE_EXPIRED.
- When another participant holds the lock, it returns LOCK_DENIED, and when the result is not DONE (UNKNOWN, REFUSED, ALREADY_DONE, HALTED and so on) or held is not true, it returns LOCK_UNKNOWN, and it is not the leader.
- When it holds the lock but there is no fencing token, it returns NO_FENCING_TOKEN, and when the lock client throws an exception, it returns PORT_FAILED, and it does not claim to be the leader.
- When the clock goes backwards, it discards the lease and stops with CLOCK_WENT_BACKWARDS.
- It extends the deadline only when status is DONE, held is true and there is a fencing token. Fuzzing checked every time that two participants contending for the same lock never claim to be the leader at the same time.
What it does not do / limits
- The Leader election part is not a Tsugite. It has no Request or Receipt of its own; the external effect of acquiring, renewing and releasing the lock is caused by the bundled E-05 Tsugite under its own contract (workflow-jobs/v1). This part itself does not touch the outside.
- The meaning of the bundled E-05 Tsugite (matching the receipt, not approval) stays on the Tsugite's side.
- Being the leader is not authority and is not approval (approval: false, authority: NOT_DERIVED, derives_authority: false).
- It does not guard writes with the fencing token. Stopping writes by an old leader is the role of the write target.
- It does not check the correctness of the lock infrastructure (linearizability, clock skew, behaviour during a partition). Safety relies on that correctness and on the safety margin estimate the caller decides.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular coordination service.
How to connect
Create it with createLeaderElection(options) in src/leader.js. Input: lock_ref, holder_ref, ttl_seconds, renew_margin_ms, lock, and an optional clock and timestamp. Returns: { ok, errors, instance }; campaign, renew and resign (Promise) and status return { ok, errors, leader, fencing_token_ref, approval: false, … }. User provides: lock.submit(request) (the client of the E-05 Tsugite), injected into this part. Side effect: caused by the bundled Tsugite (EXTERNAL_REVERSIBLE); credentials are held by the injected client.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
E-07ETsugite
Debounce and throttle — Job Control Tsugite
The Job Control Tsugite (継手, tsugite) checks requests (Request) to the execution platform against pinned lists before sending them, and matches the returned receipts against the contract. As debounce and throttle, it checks a throttle (Throttle) request against the pinned throttle policies, sends it to the execution platform, and matches the decision in the receipt (decision, allowed and retry_after_ref). The throttle decision itself is made by the execution platform.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When the decision to suppress repeated clicks or multiple launches is left to an external execution platform, it prevents querying with an unpinned policy, accepting an inconsistent receipt in which an allow (ALLOW) decision carries a retry time (retry_after_ref), and using a decision without matching the receipt.
When to use
- You want to query the execution platform for decisions to suppress repeated clicks and multiple launches only with pinned throttle policies (throttle_policies).
- You do not want to trust the execution platform's decision as is, and want to match the request and receipt (request_id, request_digest) before using it.
- You want throttle queries to be treated as having no external effect (NONE). Receipts that carry an effect are not accepted.
When not to use
- You expect this Tsugite to make the throttle decision itself. The decision is the role of the execution platform; no actual execution platform is included, and which one to connect to has not been decided.
- You need a request kind dedicated to debounce. The contract's kind is Throttle; there is no kind dedicated to debounce.
- You want to treat an allowed decision (allowed) as business authorization to run that work. Deciding what may run and when is the role of the Kernel (policy-facts).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks references. It does not send unpinned throttle policies, empty references, or references containing a path or "..". An empty list allows nothing.
- It does not accept a receipt in which an allow (ALLOW) decision carries a retry time (retry_after_ref).
- When the receipt's state does not answer the request, it does not accept the receipt.
- When the receipt's request_id, request_digest or references do not match the request (request_digest spoofing), it does not accept the receipt.
- It does not accept receipts that increase the external effect or whose effect is unknown (UNKNOWN). The effect must be no more than the request's intent on both axes: how far it reaches and whether it can be undone.
- If a receipt cannot be matched, it stops (HALTED) and sends nothing further. It never sends the same request_id twice; a failure of the execution platform is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- It does not make throttle decisions. The decision is made by the execution platform; this Tsugite only checks and sends the request and matches the decision in the receipt.
- Deciding what may run and when (policy-facts) and detecting duplicates (reconcile-facts) are the roles of the Kernels. Business authorization, retry authorization and compensation approval belong to the Kernels and to people; this Tsugite only checks before sending and after receiving.
- Enqueued, executed and completed are not approval. Every result carries approval: false.
- It does not include an actual execution platform (which scheduler, queue or lock mechanism). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific scheduler or queue.
How to connect
Contract: erabazu.candidate/certified-tsugite/workflow-jobs/v1. Input: pass a Throttle request to submit(request) on createWorkflowClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries decision, allowed and retry_after_ref. User provides: a port to the execution platform (submit, and optionally lookup) and cfg throttle_policies. Side effect: none (NONE).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
E-08ETsugite
Job status query — Job Control Tsugite
The Job Control Tsugite (継手, tsugite) checks requests (Request) to the execution platform against pinned lists before sending them, and matches the returned receipts against the contract. As a job status query, it checks a job status (JobStatus) query against the pinned jobs, sends it to the execution platform, and matches the status (status), whether it can be retried (retryable) and the failure reference (failure_ref) in the receipt. It does not mistake completion for business acceptance, or "retryable" for retry authorization.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When asking the execution platform for a job's status to answer "is it done?", it prevents mistaking completion for business acceptance or retryable for retry authorization, and accepting a failure reference that cannot be traced.
When to use
- You want to query the execution platform about progress and completion only with pinned jobs (job_refs).
- You want to accept a failure reference (failure_ref) only when it resolves to an ai-execution-error record.
- You want to keep completion and retryable separate from business acceptance and retry authorization. From the receipt it accepts only NOT_DERIVED.
- You want queries to be treated as having no external effect (NONE). Receipts that carry an effect are not accepted.
When not to use
- You want to check the content of the failure record. It does not check the content of the ai-execution-error record that failure_ref points to (only whether it resolves).
- You want to treat completion as business acceptance, or retryable as retry authorization. Both belong to the Kernels and to people.
- You want a mechanism that holds job status itself. No actual execution platform is included, and which one to connect to has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks references. It does not send unpinned jobs, empty references, or references containing a path or "..". An empty list allows nothing.
- When failure_ref cannot be resolved, or there is no means to resolve it (port.resolveFailureRef), it does not accept the receipt and stops thereafter (HALTED).
- It accepts failure_ref only for FAILED or CANCELLED.
- It does not accept receipts that derive retry authorization or business acceptance (it accepts only NOT_DERIVED).
- When the receipt's state does not answer the request, it does not accept the receipt. It never derives execution or authorization from an UNKNOWN state.
- When the receipt's request_id, request_digest or references do not match the request (request_digest spoofing), it does not accept the receipt.
- It does not accept receipts that increase the external effect or whose effect is unknown (UNKNOWN). The effect must be no more than the request's intent on both axes: how far it reaches and whether it can be undone.
- If a receipt cannot be matched, it stops (HALTED) and sends nothing further. It never sends the same request_id twice; a failure of the execution platform is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- It does not check the content of the ai-execution-error record that failure_ref points to (only whether it resolves).
- Completion is not business acceptance, and retryable is not retry authorization. No success is approval, and every result carries approval: false.
- Deciding what may run and when (policy-facts) and detecting duplicates (reconcile-facts) are the roles of the Kernels. Business authorization, retry authorization and compensation approval belong to the Kernels and to people; this Tsugite only checks before sending and after receiving.
- It does not include an actual execution platform (which scheduler, queue or lock mechanism). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific scheduler or queue.
How to connect
Contract: erabazu.candidate/certified-tsugite/workflow-jobs/v1. Input: pass a JobStatus request to submit(request) on createWorkflowClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries status, retryable, failure_ref, retry_authorization and business_acceptance. User provides: a port to the execution platform (submit, optionally lookup, and resolveFailureRef, which is required when there is a failure_ref) and cfg job_refs. Side effect: none (NONE).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
E-09EComponent
Read-only aggregation run — Read-only aggregation run part (Component)
The Read-only aggregation run part (Component) is a part that checks and reads its inputs and writes the aggregation result as a secondary copy. Every input is read with the C-01 Tsugite with the expected sha256 attached, and only inputs whose digest matches are read as JSON. The aggregator (fn) receives a frozen copy. The output of fn is turned into RFC 8785 bytes and written, together with the lineage record (the H-10 part (Primitive)), to content-addressed keys with the idempotent write Tsugite (C-05). fn runs inside the same process; this is not isolation for security.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Which inputs the numbers in an aggregate table came from does not show in the table. This part keeps the set of inputs, the aggregator's version and the hash of the output as lineage. It also prevents aggregating with inputs that have not been checked, letting the aggregator rewrite stored objects, and overwriting the location of an input with the output.
When to use
- You want to check and read 1 to 1000 inputs (key and sha256), aggregate them with a trusted aggregator fn, and write the output once to out/<tool ref>/<digest of the input set>/<sha256 of the output>.json (run).
- You want to pass fn only a frozen array [{ key, sha256, data }] in key order, and not the original bytes or the values that were read themselves.
- You want to keep, next to the output, a lineage record that ties together the input Refs (<key>@sha256:<hex>), the aggregator's ref and version, and the hash of the output.
- You want to use another aggregator's output as the input of the next aggregation. The output location is per aggregator, and inputs that do not overlap its own output location can be read.
- You want to resend a write whose result is unknown as the same request (resolvePending). Doing the same run again makes the output DEDUP_HIT.
When not to use
- You want to run an untrusted aggregator in isolation. fn runs in the same process and can touch process, require, global values, the clock, random numbers and the network, and has no time or memory limit. The only thing this part protects is "not letting the inputs be rewritten".
- You want to check that the aggregation is correct or that fn is deterministic. The lineage is a record of the inputs used, the aggregator's version and the hash of the output, and is not proof that the aggregation is correct.
- You want fn to return fractional numbers. The output is RFC 8785 (rfc8785-restricted/v1), so fractional numbers are not accepted.
- You want this part to enforce size limits on inputs and outputs. The limits are set in the configuration of the read and write Tsugite.
- You want duplicate keys in the input JSON, or numbers beyond the safe integer range, to be checked. Inputs are read as JSON.parse reads them, and are not checked as the values passed to fn.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed, it stops with INVALID_OPTIONS; when the input to run is malformed, with INVALID_INPUT; when the same key appears twice or more, with DUPLICATE_INPUT; and when an input key is the same as or under the output location out/<tool ref>, with INPUT_OUTPUT_OVERLAP; and it reads nothing.
- When the C-01 Tsugite returns NOT_FOUND, it reports INPUT_NOT_FOUND; when it returns GET_REJECTED or the sha256 recomputed by the part itself differs, INPUT_DIGEST_MISMATCH; when the result is unknown, READ_UNKNOWN; when the Tsugite did not send, READ_REFUSED; and on a halt or a malformed result, READ_FAILED; and it does not call fn and writes nothing.
- When the bytes of an input are not JSON in UTF-8 (including a BOM), it does not call fn (INPUT_NOT_JSON).
- When fn throws an exception (including an attempt to rewrite the frozen inputs in strict mode), it reports AGGREGATOR_FAILED, and when the value from fn cannot be canonicalized with RFC 8785 or is not a synchronous value, OUTPUT_NOT_JSON (with reason attached); and it writes nothing.
- When H-10 throws an exception, it reports LINEAGE_FAILED; when H-10 does not return ok: true, LINEAGE_REFUSED; and when the record's shape, count, output digest and size, tool, recorded_at or schema do not match this run, LINEAGE_INVALID; and it writes nothing.
- When the result of a write is unknown, it holds it as pending with WRITE_UNKNOWN and stops run with WRITE_PENDING while that is pending. When the Tsugite did not send, it reports WRITE_REFUSED, and when the Tsugite halted or the result is not APPLIED or DEDUP_HIT, WRITE_FAILED.
- When timestamp does not return an existing date-time in RFC 3339 UTC (Z), it reports CLOCK_FAILED; when the previous operation has not finished, BUSY; and calling resolvePending when nothing is pending returns NOTHING_PENDING.
What it does not do / limits
- The Read-only aggregation run part is not a Tsugite. It has no Request or Receipt of its own; the external effect of writing is caused by the bundled C-05 Tsugite under its own contract (reading is NONE). This part itself does not touch the outside.
- The meaning of the bundled parts (read ≠ the content is correct, lineage is not proof that the aggregation is correct, written ≠ accepted) stays on each part's side. The aggregator itself and the correctness of the aggregation are outside this part.
- Being written is not proof that the aggregation is correct and is not approval (approval: false, authority: NOT_DERIVED, aggregation_correctness: NOT_VERIFIED, derives_authority: false).
- It does not isolate fn (it is not a sandbox). It sets no time or memory limit on fn either.
- Whether the C-05 key and the C-01 object_ref point to the same stored object, and whether the output can later be read with C-01, is the job of the backend configuration.
- In this part's tests it is not connected to a real object-store or storage-lifecycle backend or to the real code of H-10. The unit tests and fuzzing use stand-ins. The state (the pending write) is only inside the process and is lost on restart.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for a particular aggregation tool or BI product.
How to connect
Create it with createSandboxRun(options) in src/sandbox-run.js; it returns { ok, errors, instance }. options are output_target, request_prefix, ports and an optional timestamp. run({ inputs, aggregator: { ref, version, fn } }) attaches output_key, lineage_key, written and other fields when the write succeeds, and the results carry approval: false. Ports: read.get (the client of the C-01 Tsugite), lineage (recordLineage of H-10) and write.submit (the client of the C-05 Tsugite). Side effects are caused by the bundled Tsugite, and credentials are held by the ports.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
E-10EPrimitive
Conservative-side duration projection — Conservative-side duration projection part (Primitive)
The Conservative-side duration projection part (Primitive) is a standalone part that, before tasks are passed to P-05 (constraints, assignment and time slots), maps each task's estimate range, duration { min, max }, to its upper bound { min: max, max: max }. { unknown: true } is left unchanged, and the other fields are copied as they are. It keeps the original min and max in projected and returns the conversion profile and the digest of the projected input. It does not round or shorten; out-of-range and malformed values are stopped with a fixed code and position (fail-closed). It does not call P-05.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind a schedule that comes out as "it fits", whether tasks were placed at the optimistic length (min) cannot be seen. P-05 places a task at the length duration.min, so passing tasks through this projection makes them be placed at the length max. For example, an indivisible task of 10 to 20 minutes is treated as 20 minutes and does not go into a 10-minute slot. Which length the judgment used can be recorded with projection_profile and projected_tasks_digest.
When to use
- You want to map the duration of a list of tasks (0 to 200) into a form that is placed at the upper bound of the estimate, max, before passing them to P-05 (projectDurations). Every task with { min, max } becomes { max, max }; if min = max it stays as it is.
- You want to keep a record, with the original values, of which tasks had their length changed. projected lists only the changed tasks, as { id, from: { min, max }, to: { min, max } }, in list order.
- You want to trace afterwards which input was passed to P-05. It returns the UTF-8 sha256 of the canonical JSON of the projected tasks (keys in UTF-16 code-unit order, no whitespace) as projected_tasks_digest.
- You want tasks whose length is unknown ({ unknown: true }) kept as they are without guessing, and their ids listed in unknown.
- You want the other fields of a task (requires, resource, window and so on) copied into new data with the same values. The input is not modified.
- You want to call it locally, as processing that does not throw an exception and has no state. It uses no clock, random numbers or environment locale, and the same input always gives the same answer.
When not to use
- You want to judge with the optimistic length (min left as it is). That judgment does not go through this part. P-05 does not use max on its own; the caller explicitly passes tasks through this projection.
- You want to know whether tasks can be placed. This part does not call P-05 and says nothing about whether placement is possible. P-05's answer (feasible and so on) remains P-05's fact as it is.
- You want the shape of tasks (requires, resource, window and so on) checked. This part only copies those fields as they are; P-05 checks them.
- You want to check whether the max of an estimate is correct. This part does not look at it.
- You want to handle more than 200 tasks, lengths over 100,000,000 minutes, or fields nested more than 32 levels deep. It stops with a fixed code.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When tasks is not a plain array (including subclasses of Array) it stops with INVALID_TASKS, and when it has more than 200 entries with TOO_MANY_TASKS.
- When id is missing, is a getter, is not a string of 1 to 128 code units, or contains a lone surrogate, it stops with INVALID_ID, and when the same id appears twice with DUPLICATE_ID (at the position of the second one).
- When duration is missing or a getter, is neither { min, max } (with no other keys) nor { unknown: true }, min or max is not an integer or is out of range, min is less than 1, or max is less than min, it stops with INVALID_DURATION. It does not round or shorten.
- When an element is not a plain object (a hole, null, an inherited one), when there is a symbol key or a key with a lone surrogate, when another field holds a getter, a function, undefined, a non-integer number, an unsafe integer, a non-plain object such as a Date, or a hole, or when nesting exceeds 32 levels, it stops with MALFORMED_TASK.
- When inspecting a value itself raises an exception (a Proxy trap and so on), it stops with INSPECTION_FAILED without throwing. Getters are not called.
- When it stops, it returns the code and position (path, a JSON Pointer) of the first problem found in a fixed order, and returns no projected tasks.
- Fuzzing (1,000,000 runs) that compares the results against an independent oracle written in a different way (isWellFormed, sorting with a comparison function, linear search of an array) confirmed that mismatches (in the projected tasks, projected, unknown, the digest, and the stopping code and path) and fail-open cases were 0.
- It was checked locally, with one example only, that when an indivisible task of 10 to 20 minutes and a 15-minute slot are passed to the real P-05 (solveSchedule), the result is feasible as is, and infeasible (INDIVISIBLE_TOO_LONG) after projection by this part.
What it does not do / limits
- The connection with P-05 was checked locally with one example only. The part's bundle does not include P-05, so this is not in the unit tests.
- It projects only duration. It checks neither whether the max of an estimate is correct nor the shape of the other fields of a task.
- It does not place anything. The projected result is not a statement of whether placement is possible, is not an approval (result_is_approval: false), and creates no authority (derives_authority: false).
- It has no external boundary, no state, no clock and no random numbers. Side effect: NONE, and it uses no credentials.
- The input and output forms were defined in v1; there is no existing implementation (new). The contract of P-05 has not been changed.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular scheduler.
How to connect
Call projectDurations(tasks) in src/duration-projection.js (it does not throw and does not modify its input). Input: a plain array of plain objects { id, duration, ... } (up to 200 entries). Returns: { ok: true, errors: [], projection_profile, tasks, projected, unknown, projected_tasks_digest } or { ok: false, errors: [{ code, path }] }. Passing the projected tasks to P-05 is done by the caller. There is no side effect, no state, no external connection and no credential; the only dependency is the Node.js built-in crypto, and there is no port for the caller to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
F-01FPrimitive
Rate limiting and quotas — Rate limiting and quota part (Primitive)
The Rate limiting and quota part (Primitive) is a standalone part that limits the number or volume of calls with a per-key token bucket. It holds the bucket level as an integer and refills it without using fractions. When there are not enough tokens it returns RATE_LIMITED and the shortest retry_after_ms after which waiting makes the tokens just available, and it does not change the state (fail-closed). The state is per instance and only in this process's memory.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind returning "limit reached", when limiting how fast calls are made within one process, it prevents letting extra calls through because of rounding, reducing the bucket even though there are not enough tokens, and using up memory by remembering new keys without limit.
When to use
- You want to limit the number or volume of calls per key within one process (take(key, n)).
- You want to return, when it stops a call, the shortest number of milliseconds (retry_after_ms) after which waiting lets the call through exactly. One ms earlier, it does not get through.
- You want to see what remains without reducing the bucket (peek does not create a new key); you want to forget a key and return it to full (reset); or you want to forget only full buckets (prune).
- You want to inject a clock to make results deterministic in tests. If the sequence of values from the injected clock is the same, the same sequence of operations gives the same results.
- You need a way of calling it that does not throw. The factory and the instance methods do not throw exceptions.
When not to use
- You need rate limiting whose counts are shared across multiple processes or machines. The state is per instance and only in this process's memory, and is not shared with other processes, instances or machines. It is not distributed rate limiting.
- You want the counts to survive a restart. When the process restarts, the counts are lost and every key returns to full.
- You want to query or change a provider's quotas or limits. That is an outside boundary and the role of a separate Tsugite.
- You need counting by time window (fixed window or sliding window) or a different limit for each key.
- You want to share one instance between threads (worker_threads). It assumes use within one event loop.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When there are not enough tokens, it returns RATE_LIMITED with remaining and retry_after_ms, and does not change the bucket.
- It stops with INVALID_OPTIONS when options is not an object or reading it raises an exception, with UNKNOWN_OPTION for an unknown option, and with INVALID_OPTION (with the name in field) when a type or range is wrong. It does not silently ignore a misspelling.
- When the time from empty to full exceeds a safe integer number of milliseconds, it cannot return retry_after_ms exactly, so it stops with REFILL_TOO_SLOW.
- It stops with INVALID_KEY when key is not a string of 1 to 256 characters, with INVALID_AMOUNT when n is not a safe integer of 1 or more, and with REQUEST_TOO_LARGE when n exceeds capacity.
- When a new key would exceed max_keys, it stops with TOO_MANY_KEYS without creating state.
- It stops with CLOCK_WENT_BACKWARDS when the clock goes back before the newest time it has received, with CLOCK_FAILED when clock.now() throws, and with INVALID_CLOCK_VALUE when it does not return a safe integer.
What it does not do / limits
- It does not share counts across multiple processes or machines. The state is only in this process's memory and is lost on restart. Distributed rate limiting (which needs shared storage) is not this part.
- Of the "quota" in its name, it does not query or change a provider's quotas or limits. That is an outside boundary and the role of a separate Tsugite. This part only does the counting inside the process.
- It has not been checked that it matches how a provider's API counts quotas or rate limits.
- It cannot detect a clock that has run ahead (a future value). The monotonicity of the default clock (process.hrtime.bigint) is left to Node.js.
- It does not decide the business meaning of who is allowed how much. That is the role of the Kernel and the caller.
- Letting a call through is neither permission to make that call nor proof that quota remains on the provider's side (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular rate-limiting library or service.
How to connect
Call createRateLimiter(options) in src/rate-limit.js (it does not throw). options is only { capacity, refill_tokens, refill_interval_ms, max_keys, clock }, and clock is optional. Returns: { ok, errors, instance }. The instance's take(key, n) returns { ok, errors, remaining, retry_after_ms }, and peek, reset and prune return remaining, removed and keys. When it stops, the code in errors is from error_taxonomy. Side effect: NONE. There is no outside connection, no credential and no outside interface for the caller to supply; the state is in the process's memory, and the only dependencies are Node.js built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
F-02FPrimitive
Circuit breaker — Circuit breaker part (Primitive)
The Circuit breaker part (Primitive) is a standalone part that counts failures in a window of the most recent calls and moves CLOSED → OPEN → HALF_OPEN → CLOSED or OPEN. The caller asks allow() before calling and passes the result with record({ outcome }). Whether something is a failure is decided by the caller. A result with no matching allow is stopped with UNKNOWN_TRIAL and not counted (fail-closed). The state is per instance and only in this process's memory.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When temporarily stopping calls to a counterpart that keeps failing, within one process, it prevents HALF_OPEN from closing on its own because of a late result from an earlier call or a trial from an earlier round, and prevents the state from changing when it stops a call.
When to use
- You want to temporarily stop calls to a counterpart that keeps failing, within one process. The caller asks allow() before calling and passes the result with record.
- You want to go OPEN based on the number of FAILURE results (failure_threshold or more) among the most recent window_size results.
- You want to return, while OPEN, the exact number of milliseconds until the next trial is possible (retry_after_ms).
- You want to tie HALF_OPEN trials together with trial_id so that you never mistake which result belongs to which trial.
- You want to inject a clock to make results deterministic in tests. It uses no random numbers (trial_id is a sequence number).
- You need a way of calling it that does not throw. The factory and the instance methods do not throw exceptions.
When not to use
- You need a breaker whose state is shared across multiple processes or machines. The state is per instance and only in this process's memory, and is not shared with other processes, instances or machines.
- You want to keep OPEN across a restart. When the process restarts, it returns to CLOSED with an empty window and counts again, even if the counterpart is still down.
- You want to count by time window (the last N seconds), by failure rate or by slow calls. It has a count-based window only.
- You want HALF_OPEN to wait for several successful trials before closing. It decides by the result of the first trial that arrives.
- You cannot always record the result of a trial. Trials have no deadline, and if no result arrives it stays HALF_OPEN_BUSY and does not issue the next trial.
- You want to share one instance between threads (worker_threads), or you use it on a version other than Node.js v24.21.0 (NOT_VERIFIED).
What it checks
- While OPEN, allow() returns CIRCUIT_OPEN and retry_after_ms. One ms earlier, it does not issue a trial.
- For a result with no matching allow (no trial_id in HALF_OPEN or OPEN, an unknown trial_id, the trial_id of a trial from an earlier round or of one that has become invalid, or a trial_id in CLOSED), it stops with UNKNOWN_TRIAL without counting it and does not change the state.
- When this round has issued half_open_max_trials trials and their results have not yet arrived, it stops with HALF_OPEN_BUSY.
- It stops with INVALID_RECORD when the argument to record is not an object with only { outcome, trial_id? } or trial_id is not a string, and with INVALID_OUTCOME when outcome is not "SUCCESS" or "FAILURE" (case-sensitive).
- It stops with INVALID_OPTIONS when options is not an object, with UNKNOWN_OPTION for an unknown option, and with INVALID_OPTION when a type or range is wrong (including failure_threshold exceeding window_size).
- It stops with CLOCK_WENT_BACKWARDS when the clock goes back before the newest time it has received, with CLOCK_FAILED when clock.now() throws, and with INVALID_CLOCK_VALUE when it does not return a safe integer.
What it does not do / limits
- It does not share state across multiple processes or machines. The state is only in this process's memory and is lost on restart. A shared breaker is not this part.
- Of "stopping cascading failures" in its name, it does not decide which results count as failures (classification by looking at exceptions or responses). It only counts the SUCCESS or FAILURE that the caller decided.
- It does not decide what to do when it stops a call (an alternative candidate from F-07, a retry from F-03 and so on), and it does not make the call itself. That is the caller's role.
- state() does not read the clock. Even after open_ms has passed, it returns OPEN until allow() is called.
- It cannot detect a clock that has run ahead (a future value). The monotonicity of the default clock (process.hrtime.bigint) is left to Node.js. It does not claim that its transitions or defaults are the same as those of other circuit breaker libraries.
- allow() letting a call through is neither permission to make that call nor proof that the counterpart has recovered (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular circuit breaker library.
How to connect
Call createCircuitBreaker(options) in src/circuit-breaker.js (it does not throw). options is only failure_threshold, window_size, open_ms, half_open_max_trials and clock (optional). On the returned instance, allow() returns state, trial_id? and retry_after_ms?; record({ outcome, trial_id? }) returns state; and state() returns the counts in the window, opened_at and so on. Every result has { ok, errors }, and when it stops, the code in errors is from error_taxonomy. Side effect: NONE. There is no outside connection, no credential and no outside interface for the caller to supply; the state is in the process's memory.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
F-03FPrimitive
Retry (exponential backoff) — Retry (exponential backoff) part (Primitive)
The Retry (exponential backoff) part (Primitive) is a standalone part that counts the retries of one operation and decides the time of the next attempt. It computes the delay as capped exponential backoff in integer ms and, with jitter "full", chooses it with an injected random source. It does not run the operation and does not wait. A retry of an operation that is not idempotent (idempotent: false) is stopped with NOT_IDEMPOTENT (fail-closed). The state is per instance and only in this process's memory.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When retrying a transient failure that "sometimes fixes itself", it prevents retrying an operation that is not idempotent, letting a forgotten idempotent setting through, retrying without end or overflowing the delay, and silently ignoring unknown keys.
When to use
- You want to manage, within one process, the number of attempts at one operation (max_attempts, including the first attempt) and the time of the next attempt (not_before_ms).
- You want to compute the capped exponential backoff delay min(max_ms, base_ms × multiplier^(attempt-1)) in integer ms (computeDelay is a pure function with no state).
- You want jitter "full" to choose an integer delay in [0, delay] with an injected random source.
- You want to allow only the first attempt of an operation that is not idempotent and stop its retries (idempotent: false).
- You want to inject a clock and a random source to reproduce results in tests.
- You need a way of calling it that does not throw. The factory, computeDelay and the instance operations do not throw exceptions.
When not to use
- You want to count the total retries across multiple processes or instances. The state is per instance and only in this process's memory, and is not shared across processes or instances.
- You want to keep the number of attempts across a restart. The state is lost when the process ends.
- You want the part to run the operation or to wait (with a timer) until not_before_ms. Both are the caller's role.
- You need Equal Jitter, Decorrelated Jitter or a multiplier that is not an integer (such as 1.5). jitter is none or full only, and multiplier is integers only.
- You need unpredictable random quality. For the injected random source, it only checks that the return value is an integer in range.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- After RETRYABLE_FAILURE is recorded for an operation with idempotent: false, the run becomes HALTED and the next attempt is stopped with NOT_IDEMPOTENT (even if attempts remain). When idempotent is missing or not a boolean, it stops with INVALID_IDEMPOTENT (it sets no default).
- It stops with TOO_EARLY (with not_before_ms and retry_after_ms) for an attempt before not_before_ms, and with RETRIES_EXHAUSTED when the number of attempts has reached max_attempts.
- It stops when attempts and records do not alternate (nextAttempt during an attempt gives ATTEMPT_IN_FLIGHT; record with no attempt gives NO_ATTEMPT_IN_FLIGHT), and stops an attempt or record after the run has finished with RUN_FINISHED.
- When the input to record is not only { outcome }, or outcome is not SUCCESS, RETRYABLE_FAILURE or FINAL_FAILURE, it stops with INVALID_OUTCOME.
- It stops with INVALID_OPTIONS when options or policy is not a plain object, with UNKNOWN_OPTION (with key) for an unknown key, and with the corresponding INVALID_* code when attempt, base_ms, max_ms, multiplier, jitter or max_attempts is out of range.
- It stops with INVALID_RANDOM when random is not a function, and with RANDOM_OUT_OF_RANGE when it does not return an integer in [0, delay] or throws.
- It stops with INVALID_CLOCK when clock has no now(), with CLOCK_INVALID when clock.now() does not return a safe integer of 0 or more or throws, and with CLOCK_WENT_BACKWARDS when it goes back from the value read before.
- An operation that was stopped (ok: false) does not change the number of attempts, the stage or not_before_ms.
What it does not do / limits
- The state is only in this process's memory; it is not shared between processes or instances and is lost on restart. It cannot be used to count retries across multiple processes.
- Of "retrying transient failures" in its name, it does not carry out the retry. Running the operation and waiting (with a timer) until not_before_ms are the caller's.
- It does not decide whether a failure is transient (RETRYABLE or FINAL) or whether a retry is acceptable for the business. It uses idempotent and outcome exactly as the caller declares them and does not check whether they are correct.
- It does not read the counterpart's Retry-After or rate-limit responses (that is on the Tsugite side). Limiting the overall waiting time is the role of F-06, and stopping the call itself is the role of F-02.
- For clock monotonicity it only stops when the clock goes back; keeping the clock monotonic is up to whoever injects it. The fuzz reference model was written separately from the same specification by the same author as the implementation, so a misreading of the specification present in both would not be found.
- Even if nextAttempt is ok, that is not permission to run the operation. retry_allowed: true is not a judgement that a retry should be made for the business (result_is_approval: false, derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular retry library.
How to connect
Call createRetryRun(options) in src/retry.js (a factory that does not throw). options is only max_attempts, base_ms, max_ms, multiplier, jitter, idempotent (required), clock and random. On the returned instance, nextAttempt() returns attempt and not_before_ms, record({ outcome }) returns state and retry_allowed, and snapshot() returns the state. computeDelay(policy, random) is a pure function that returns delay_ms. Every result has { ok, errors }, and when it stops, the code is from error_taxonomy. Side effect: NONE. There is no outside connection, no credential and no outside interface for the caller to supply; the state is in the process's memory.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
F-04FPrimitive
Idempotency key handling — Idempotency key handling part (Primitive)
The Idempotency key handling part (Primitive) is a standalone part that compares a new request (stage, op_id and the content digest) against a given ledger (requests received before and the digests of their results) and sorts it into one of: first time (NEW), a resend of the same request with a result (DUPLICATE_SAME), a resend of the same request with no result yet (DUPLICATE_PENDING), or different content under the same ID (CONFLICT). The stages are REQUEST, ATTEMPT and DELIVERY, and each stage is counted separately. Input it cannot accept is stopped with a fixed code and position (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
This identification is there to keep a resend from running the same processing twice. It prevents passing a request with the same ID but different content as a resend of the same request, treating a resend whose result is unknown as a new run, and silently skipping ledger rows of the wrong shape.
When to use
- You want to identify whether a new request is a resend of a request in the given ledger, by exact match of the idempotency key (stage, op_id) and the content digest (payload_digest) (classify).
- You want to return the earlier result digest for a resend of the same request. The result_digest of DUPLICATE_SAME is exactly the result_digest of that ledger row.
- You want a resend to a row that has no result yet (result_digest is null) to come back as DUPLICATE_PENDING, a marker not to run it again. It is not a decision to run it again; it is a marker for waiting for reconciliation.
- You want to identify a request with the same ID but different content as CONFLICT. This differs from identification that decides duplicates by ID alone, without looking at differences in content under the same ID.
- You want request receipt (REQUEST), attempts (ATTEMPT) and delivery (DELIVERY) counted separately by stage. Across different stages, the same op_id is not compared. counts returns the number of distinct op_ids per stage.
- You want to call it locally, as processing that does not throw an exception, does not modify its input, and has no state. The same input always gives the same answer.
When not to use
- You want to store or read the ledger, or find duplicates against other calls or a stored ledger. It compares only within its arguments; storing and reading the ledger is done outside this part, on the side of the Host and so on.
- You want to settle a race between two requests that arrive at the same time. Which goes first is decided by the side that writes the ledger (the Host).
- You want to reject, record or report a request, or carry out a resend. CONFLICT only returns a marker for a request to be rejected; rejecting, recording and reporting are the caller's work. It does not carry out resends, keep a clock or store anything either.
- You want op_ids that look the same (é and e plus a combining character, A and a, differences in leading or trailing whitespace) to be treated as the same. op_id is compared only by exact match of UTF-16 code units.
- You want to pass a digest in upper case or with a sha256: prefix. The digest is not converted; anything other than 64 lower-case hexadecimal digits stops with INVALID_DIGEST. The rules for making a digest from content (I-08 canonicalization, B-07 hashing and so on) are decided by the caller, and both the ledger and the request are made by the same rules.
- You want to handle a ledger of more than 100,000 rows in one call. The caller splits it. Once split, duplicates against rows in the other parts are not identified.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When request is not a plain object (including inherited ones, class instances and a null prototype), or stage, op_id or payload_digest is not an own data property (missing or a getter), it stops with MALFORMED_REQUEST.
- When ledger is not a plain array it stops with INVALID_LEDGER, when it has more than 100,000 rows with TOO_MANY_ENTRIES, and when a row is not a plain object (including holes and getter rows) or its four fields are not own data properties with MALFORMED_ENTRY.
- When stage is none of REQUEST, ATTEMPT and DELIVERY (including lower case and boxed strings) it stops with INVALID_STAGE, when op_id is not a string or is empty with INVALID_OP_ID, and when it exceeds 256 UTF-16 code units with OP_ID_TOO_LONG.
- When payload_digest is not a string of 64 lower-case hexadecimal digits (upper case, a prefix, the wrong number of digits, non-hexadecimal characters, a boxed string, an array, null), or result_digest is neither a string of that form nor null, it stops with INVALID_DIGEST.
- When the ledger has two rows with the same (stage, op_id), it stops with DUPLICATE_LEDGER_ENTRY (at the position of the second one), even if the content is the same. When inspecting a value itself raises an exception (a Proxy trap and so on), it is INSPECTION_FAILED.
- Names such as __proto__ and constructor are also compared as ordinary op_ids (compared with a Map, not used as object property names). Getters are not called.
- When it stops, it returns the code and position (path, a JSON Pointer) of the first problem found in a fixed order, and returns neither decision nor the other fields.
- Fuzzing (1,000,000 runs) that compares the results against an independent oracle written to search the ledger from the front in order, without using Map or Set, confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- NEW only means "not in the given ledger"; it does not mean that the request may be run. Duplicates against a ledger that was not passed in (one held elsewhere, or not yet stored) are not known.
- It does not look at whether the content digest was really made from the content (it only compares the digests passed in). It does not look at whether the result digest of DUPLICATE_SAME is correct either.
- Accepting exactly the limit of 100,000 rows was checked only by unit tests (the fuzzing covers only inputs over the limit).
- It has no external boundary, no state, no clock and no random numbers, and stores nothing. Side effect: NONE, and it uses no credentials.
- The result of the identification is not an approval (result_is_approval: false) and creates no authority (derives_authority: false).
- The input and output forms were defined in v1; there is no existing implementation (new).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular idempotency key library.
How to connect
Call classify(request, ledger) in src/idempotency-ledger.js (it does not throw and does not modify its input). Input: a plain object { stage, op_id, payload_digest } and a plain array of rows { stage, op_id, payload_digest, result_digest } (up to 100,000 rows). Returns: { ok: true, errors: [], decision, matched_index, result_digest, counts } or { ok: false, errors: [{ code, path }] }. There is no side effect, no state, no external connection and no credential; the only dependencies are Node.js built-ins, and there is no port for the caller to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
F-05FPrimitive
Concurrency limit (bulkhead) — Concurrency limit (bulkhead) part (Primitive)
The Concurrency limit (bulkhead) part (Primitive) is a standalone part that limits, per compartment, how many run at the same time (max_concurrent) and the length of the waiting queue (max_queue). If a slot is available it hands out a token; if the slots are full but the queue has room it hands out a ticket; if both are full it stops with BULKHEAD_FULL (fail-closed). The limits are per instance and only in this process's memory; it is not a distributed limit.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When dividing work into compartments within one process so that runaway work in one part does not drag down other work in the same process, it prevents handing out slots beyond the limit, and opening extra slots through a double release or a fake token.
When to use
- You want to limit, within one process, how many run at the same time and the length of the waiting queue for each compartment divided by name. Compartments are independent; one compartment being full does not affect the others.
- When the slots are full, you want to put work in a waiting queue and promote it in FIFO order. The promoted token goes into promoted in the result of release, and the waiting side learns of it with poll.
- You have a compartment that should refuse at once without waiting (max_queue 0).
- You want to remove work from the waiting queue (cancel). A cancelled ticket is not promoted.
- You need a way of calling it that does not throw. The factory and the instance operations do not throw exceptions.
When not to use
- You need a concurrency limit across multiple processes or machines (a distributed semaphore or shared counting). The state is per instance and only in this process's memory, and is not shared across processes or instances.
- You want the slot counts to survive a restart. The state is lost on restart.
- You need a waiting deadline (timeout). Stopping excessive waits is for F-06 or the caller.
- You want to wake the waiting side with a notification or a Promise. This part does not wake the waiting side.
- You want the slot of a token whose release was forgotten to be reclaimed automatically. There is no reclamation by deadline and no leak detection.
- You need priorities, weights or fairness adjustment. Inside a compartment it is FIFO only.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When both the compartment's slots and its waiting queue are full, it stops with BULKHEAD_FULL (with the name in compartment). It does not hand out slots beyond the limit.
- It stops with UNKNOWN_TOKEN when the token is not the very one this instance handed out (a fake of the same shape, one from another instance, or a ticket mistaken for a token), and with TOKEN_ALREADY_RELEASED for a token already released, and does not open a slot.
- It stops with UNKNOWN_TICKET for a ticket this instance did not hand out, with TICKET_CANCELLED for one already cancelled, and with TICKET_ALREADY_GRANTED for a cancel after promotion.
- When the compartment name given to acquire is not among the compartments set at creation (including prototype names such as constructor and toString), it stops with UNKNOWN_COMPARTMENT.
- It stops with INVALID_OPTIONS when options is not a plain object, with UNKNOWN_OPTION for an unknown key, with INVALID_COMPARTMENTS when there are 0 or 257 or more compartments, and with the corresponding INVALID_* code when the shape or range of a compartment name, a compartment value, max_concurrent or max_queue is wrong.
- It stops with INVALID_CLOCK when clock has no now(), with CLOCK_INVALID when clock.now() does not return a safe integer of 0 or more or throws, and with CLOCK_WENT_BACKWARDS when it goes back from the value read before.
- An operation that was stopped (ok: false) does not change the slots, the waiting queue, tokens or tickets.
What it does not do / limits
- It does not limit the total across multiple processes or machines. The state is only in this process's memory and is lost on restart. A distributed limit has an outside boundary, so it belongs on the Tsugite side, not in this part.
- Of "isolating runaway parts" in its name, it does not detect or stop runaway work. It only limits, per compartment, how many run at the same time and the length of the waiting queue.
- It has no waiting deadline, no way of waking the waiting side, and no reclamation of slots whose release was forgotten.
- A token is a handle within the same process; it is not proof of ownership, authority or permission to run. It does not stop the receiver from passing it on to others (result_is_approval: false, derives_authority: false).
- The fuzz reference model was written separately from the same specification by the same author as the implementation, so a misreading of the specification present in both would not be found.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular semaphore or pool library.
How to connect
Call createBulkhead({ compartments, clock? }) in src/bulkhead.js (a factory that does not throw). On the returned instance, acquire(name) returns a token (queued: false) or a ticket (queued: true), release(token) returns compartment and promoted, poll(ticket) returns granted and a token, cancel(ticket) returns compartment, and snapshot() returns active and queued for each compartment. Every result has { ok, errors }, and when it stops, the code in errors is from error_taxonomy. Side effect: NONE. There is no outside connection, no credential and no outside interface for the caller to supply; the state is in the process's memory.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
F-06FPrimitive
Timeouts and deadline propagation — Timeout and deadline propagation part (Primitive)
The Timeout and deadline propagation part (Primitive) is a standalone part that builds absolute deadlines on an injected monotonic clock (clock.now(), in milliseconds) and passes deadlines and cancellation from parent to child. A child's deadline never extends beyond its parent's. Cancellation is a mark within this process and does not mean that outside processing has stopped. Values it cannot accept and a clock that goes back are stopped with fixed codes (fail-closed). The state is only in this process's memory.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When calls are nested within one process, it prevents a child's deadline from extending beyond its parent's, an expiry from being undone because the clock went back, and cancellation from failing to reach descendants, and it makes excessive waiting (hangs) readable.
When to use
- You want to create a deadline for processing within one process and pass the remaining time (remaining_ms) on to what you call. It is a candidate to become the common part that passes the remaining time to Tsugite that call outside APIs.
- You want a child's deadline to be min(the parent's deadline, now + its own timeout) so that propagation never widens the deadline (child).
- You want to mark itself and all its descendants as cancelled (cancel). cancel does not read the clock, so cancellation works even if the clock is broken.
- You want to read the deadline's state (ACTIVE, EXPIRED or CANCELLED) and the cancellation's reason and origin (SELF or ANCESTOR) (status, expired).
- You want to inject a clock to make results deterministic in tests. It uses no wall clock (Date), no timers and no random numbers.
- You need a way of calling it that does not throw. Neither the factory nor the instance methods throw exceptions.
When not to use
- You need deadline propagation across processes or in a distributed way (sending it in headers and so on). The state is only in this process's memory, with no sharing between processes, no distributed sharing and no persistence.
- You want deadlines to survive a restart. When the process ends, the state is lost.
- You want to cancel outside processing (a counterpart's API and so on). Cancellation is only a mark within this process; outside cancellation is done by the caller or a Tsugite under its own contract.
- You need a notification (timer) at the deadline time. The caller only finds out when it reads.
- You hold one tree for a long time and create more than 10,000 deadlines in it. There is no operation to release a deadline, and finished deadlines stay in memory and count toward the number.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When a clock reading is smaller than the largest value read in that tree, it stops with CLOCK_WENT_BACKWARDS without changing its records. A deadline that has once become EXPIRED never returns to ACTIVE.
- When you try to create a child of an expired or cancelled deadline, it stops with DEADLINE_ALREADY_OVER.
- When timeout_ms is not a safe integer from 1 to 2,147,483,647 (0, negative, fractional, NaN, a string or above the upper limit), it stops with INVALID_TIMEOUT.
- It stops with INVALID_CLOCK when clock has no now(), with INVALID_CLOCK_READING when clock.now() throws or does not return a finite number from 0 to MAX_CLOCK_MS, and with CLOCK_MISMATCH when a clock different from the one of the parent's tree is passed.
- When parent is not a deadline instance created by this part (including copies), it stops with INVALID_PARENT.
- It stops with INVALID_OPTIONS when the options are not an object, have a key that is not allowed or throw when read, and with INVALID_REASON when reason_code is not 1 to 64 characters of uppercase letters, digits and _ starting with an uppercase letter.
- When the number of deadlines in a tree reaches 10,000 (MAX_DEADLINES_PER_TREE), it stops with TOO_MANY_DEADLINES.
What it does not do / limits
- The state is only in this process's memory; it is not shared between processes or in a distributed way, and it is lost when the process ends. The "deadline propagation" in its name is only propagation from parent to child within this process; it does not propagate across processes (sending it in headers and so on).
- Of the "timeout" in its name, it does not stop processing. Cancellation is a mark within this process (local_only: true), and it does not handle cancelling outside processing or whether that succeeds. Outside cancellation is on the side of each Tsugite and its contract.
- It does not notify at the deadline time (it has no timers).
- It cannot check whether the injected clock is truly monotonic (it only stops readings that go back). The monotonicity of the default clock depends on Node.js's process.hrtime.bigint. Compatibility with the deadline shape of any Tsugite or library has not been checked.
- Remaining time, expiry and cancellation are only facts on this part's clock and marks; they do not mean that outside processing has stopped, has finished or may be redone (result_is_approval: false, derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular library (AbortController and so on).
How to connect
Call createDeadline({ timeout_ms, clock?, parent? }) in src/deadline.js (a factory that does not throw). On the returned instance, remaining() returns remaining_ms and state, expired() returns expired, cancelled and over, child({ timeout_ms }) returns a child instance, cancel({ reason_code }) returns newly_cancelled and local_only: true, and status() returns state, deadline_at and cancelled. Every result has { ok, errors }, and when it stops, the code is from error_taxonomy. Side effect: NONE. There is no outside connection, no credential and no outside interface for the caller to supply; the state is in the process's memory.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
F-07FPrimitive
Fallback — Fallback part (Primitive)
The Fallback part (Primitive) is a standalone part that decides, one step at a time, whether it may move on to the next candidate after a failure, strictly following an "ordered allow-list" decided outside it (by policy, the Owner or the caller). It does not add candidates, reorder them, skip them or widen what is allowed. It does not call candidates; it only returns an id. An allow-list or report of the wrong shape is stopped with a fixed code (fail-closed). The state covers one run and is only in this process's memory.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When switching to the next candidate so that things "still work, partially", it prevents moving to a candidate that was not decided, skipping in the order, switching after a failure that is not allowed, trying again with a different code after being refused, and the allow-list being rewritten after creation.
When to use
- You want switches of an AI provider or model, a storage destination and so on to happen only in the order decided outside the part.
- You want to decide, per failure code, whether moving to the next candidate is allowed. next returns the next candidate's id only when the one that failed is the current candidate and the code is in the allowed_after of the immediately following candidate.
- You want an upper limit on the number of switching steps (max_steps, 0 to 31).
- You need a deterministic decision that uses no clock, no random numbers and no environment.
- You need a way of calling it that does not throw. Neither the factory nor the instance methods throw exceptions.
When not to use
- You want to share a run's state across multiple processes or machines. The state covers one run and is only in the instance in this process's memory, with no sharing between processes and no persistence.
- You want to keep the current candidate and step count across a restart. When the process ends, the state is lost.
- You want the part to decide which candidates to list or after which failures switching is allowed. That is decided outside it (by policy, the Owner or the caller).
- You want it to handle calling the candidates, waiting time, retries or circuit breaking as well. Deadlines are F-06, retries F-03 and circuit breaking F-02.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When error_code is not in the allowed_after of the immediately following candidate, it stops with FALLBACK_NOT_ALLOWED and ends that run. After being refused, it cannot be tried again with a different code.
- When failed_id is not the current candidate (an earlier candidate, a later candidate or an unknown id), it stops with OUT_OF_ORDER without changing the state.
- It stops with NO_MORE_CANDIDATES when the current candidate is the last, and with MAX_STEPS_REACHED when the step count has reached max_steps, and ends that run. next after the run has ended gives RUN_ENDED.
- It stops with ALREADY_STARTED when first is called twice, with NOT_STARTED when next is called before first, and with INVALID_REQUEST when the report to next is not an object with only failed_id and error_code.
- It stops with INVALID_CANDIDATES when candidates is not an array, is empty or has more than 32 entries, with INVALID_CANDIDATE_ID when an id has the wrong shape, and with DUPLICATE_CANDIDATE_ID when the same id appears twice.
- It stops with INVALID_ALLOWED_AFTER when allowed_after has the wrong shape (a code of the wrong shape, duplicate codes, more than 64, not empty for the first candidate, or empty for a candidate from the second on), with INVALID_MAX_STEPS when max_steps is not a safe integer from 0 to 31, and with INVALID_OPTIONS when the options have the wrong shape.
- Even if the caller rewrites the allow-list array after creation, the results do not change (it keeps a copy).
What it does not do / limits
- The state covers one run and is only in this process's memory; it is not shared between processes or machines and is lost when the process ends. It does not handle run state across processes or distributed run state.
- Of "degraded operation" in its name, it does not perform the degraded behaviour itself. It does not choose, switch or call a provider or model; it only returns the next id in the order decided outside it. Calling a candidate is done by the caller and each Tsugite.
- It does not judge whether the allow-list's contents are correct (whether switching to that candidate is acceptable, or whether the cost or the scope of the data handled is the same). It only keeps to what was decided outside it.
- It cannot check whether a failure code is genuine. It does not interpret what a code means either; it only looks for an exact string match.
- The next candidate's id only means "moving on is allowed on the allow-list decided outside"; it is not authority to call that candidate, approval of cost or correctness of the result (result_is_approval: false, derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- Compatibility with the fallback shape of any Tsugite or library has not been checked.
How to connect
Call createFallback({ candidates, max_steps }) in src/fallback.js (a factory that does not throw). candidates is an ordered list of { id, allowed_after } (1 to 32 entries). On the returned instance, first() returns the first candidate { id }, next({ failed_id, error_code }) returns the next candidate { id }, index, step and allowed_by, and status() returns state (READY, ACTIVE or ENDED), current, steps and end_code. When it stops, the code in errors is from error_taxonomy. Side effect: NONE. There is no clock, no outside connection, no credential and no outside interface for the caller to supply; calling the candidates is the caller's job. It is pure JS with no dependencies.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-01GTsugite
Payment authorization — Payment Rails Tsugite
The Payment Rails Tsugite (継手, tsugite) checks a payment authorization request (PaymentAuthRequest) against the pinned payment methods and the setting that allows operations that move money (allow_money_movement), carries it to the payment platform, and matches the returned receipt (PaymentAuthReceipt) against the contract. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a payment authorization is carried to an external payment platform, this Tsugite prevents sending an operation that moves money when that has not been allowed, using a payment method that is not pinned, and trusting a receipt that leaves it unknown whether money moved or a receipt that claims business approval. It is a check against mistaking a successful payment for business approval.
When to use
- You want to carry a payment authorization (AUTHORIZE) request to an external payment platform and treat the returned receipt as a result only after matching that its state fits op (AUTHORIZED).
- You want operations that move money to be sent only when it has been explicitly allowed by the setting allow_money_movement: true (by default nothing is sent).
- You want to limit the payment methods that may be used to a pinned list (method_refs, references that are not card numbers).
- You do not want the same authorization to run twice. An idempotency key is required, the same request_id is not sent twice, and a resend after a failure is resolved with a lookup (lookup).
When not to use
- You want to treat a successful payment as business approval. A successful payment is not business approval (business_approval is not derived from carrying).
- You want to confirm that amounts, currencies, taxes or fees are correct. This Tsugite does not check them (that is the role of external decisions and of people and organizations).
- You want to keep the records of authorizations after a restart. The records exist only inside this Tsugite and are lost on restart (persistence will be settled at a separate gate when connecting).
- You want to connect to an actual payment platform right away. It has never been connected to an actual payment platform, PSP or bank, and it does not include a port implementation.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the permission to move money. When allow_money_movement is not true, or is not a boolean, it does not send operations that move money (REFUSED).
- It checks the pinned lists. It does not send a payment method that is not pinned, or a reference that is empty or contains a path or .. (path traversal). An empty list allows nothing.
- It checks the idempotency key. It does not send an authorization without an idempotency key, or a request that reuses a key with different content.
- It checks the authorization records. It does not send a capture of a payment that this Tsugite has not matched as AUTHORIZED, a capture after a void, or a second capture.
- It matches the receipt. A receipt whose state does not match op, a receipt that claims an effect for a decline, a receipt that increases the external effect, or a receipt for another request (a spoofed request_digest) is not accepted, and the client stops from then on (HALTED).
- A receipt that leaves it unknown whether money moved (effect UNKNOWN) is not accepted, and the client stops from then on (HALTED). The case is passed to a person for review.
- A receipt that claims business approval is not accepted.
- It does not send the same request_id twice. A payment platform failure is treated as UNKNOWN, and resending is resolved with a lookup (lookup) so that nothing runs twice.
What it does not do / limits
- A successful payment is not business approval. Every result carries approval: false. Business approval is the role of the Kernel (approval-facts) and of people and organizations.
- It has never been connected to an actual payment platform, PSP or bank. The number of actual charges or payouts sent is 0. The port implementation, the pinned lists and the permission to move money will be settled at a separate gate when connecting.
- It does not check whether amounts, currencies, taxes or fees are correct (that is the role of external decisions and of people and organizations).
- It does not hold the secret used to authenticate to the payment platform. The secret is held on the port side (HELD_BY_PORT).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific payment service.
How to connect
Contract: erabazu.candidate/certified-tsugite/payment-rails/v1. Input: pass a PaymentAuthRequest to submit(request) on createPaymentRailsClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries op, auth_state and business_approval in result. Every result has approval: false. User provides: a port to the payment platform (submit, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE for authorization operations; NONE for declines, pending results, failures and similar.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-02GTsugite
Payment capture and void — Payment Rails Tsugite
The Payment Rails Tsugite (継手, tsugite) carries a payment capture (CAPTURE) and a void (VOID) as a payment authorization request (PaymentAuthRequest). For capture and void, it sends only payments that this Tsugite has matched as AUTHORIZED and that have not yet been captured or voided, and it matches the returned receipt (PaymentAuthReceipt) against the contract. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When an authorized payment is captured or voided, this Tsugite prevents capturing a payment that has not been authorized, capturing a payment that has been voided, capturing the same payment twice, and trusting a receipt that leaves it unknown whether money moved or a receipt that claims business approval. It is a check against mistaking a successful capture for business approval.
When to use
- You want to carry a payment authorized through this Tsugite (already matched as AUTHORIZED) to an external payment platform as a capture (CAPTURE to CAPTURED) or a void (VOID to VOIDED), and treat the returned receipt as a result only after matching that its state fits op.
- You want a capture to be sent only when it has been explicitly allowed by the setting allow_money_movement: true (by default nothing is sent).
- After a capture or void, you want to prevent the same payment from being captured a second time.
- When resending after a payment platform failure, you want to resolve it with a lookup (lookup) so that nothing runs twice.
When not to use
- You want to capture or void a payment that was authorized through another route. Capture and void apply only to payments that this Tsugite has matched as AUTHORIZED.
- You want to keep the records of authorizations after a restart. The records exist only inside this Tsugite and are lost on restart (persistence will be settled at a separate gate when connecting).
- You want to treat a successful capture as business approval. A successful payment is not business approval (business_approval is not derived from carrying).
- You want to confirm that amounts, currencies, taxes or fees are correct. This Tsugite does not check them (that is the role of external decisions and of people and organizations).
- You want to connect to an actual payment platform right away. It has never been connected to an actual payment platform, PSP or bank, and it does not include a port implementation.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the authorization records. It does not send a capture or void of a payment that this Tsugite has not matched as AUTHORIZED, a capture after a void, or a second capture (REFUSED).
- It checks the permission to move money. When allow_money_movement is not true, or is not a boolean, it does not send operations that move money, including a capture (REFUSED).
- It checks references. It does not send a reference that is empty or contains a path or .. (path traversal). An empty list allows nothing.
- It matches the receipt. A receipt whose state does not match op (anything other than CAPTURED for CAPTURE, or VOIDED for VOID), a receipt that claims an effect for a decline, a receipt that increases the external effect, or a receipt for another request (a spoofed request_digest) is not accepted, and the client stops from then on (HALTED).
- A receipt that leaves it unknown whether money moved (effect UNKNOWN) is not accepted, and the client stops from then on (HALTED). The case is passed to a person for review.
- A receipt that claims business approval is not accepted.
- It does not send the same request_id twice. A payment platform failure is treated as UNKNOWN, and resending is resolved with a lookup (lookup) so that nothing runs twice.
What it does not do / limits
- A successful capture is not business approval. Every result carries approval: false. Business approval is the role of the Kernel (approval-facts) and of people and organizations.
- It has never been connected to an actual payment platform, PSP or bank. The number of actual charges or payouts sent is 0. The port implementation, the pinned lists and the permission to move money will be settled at a separate gate when connecting.
- It keeps the records of authorized payments only inside this Tsugite (they are lost on restart). It does not persist them (persistence will be settled at a separate gate when connecting).
- It does not check whether amounts, currencies, taxes or fees are correct (that is the role of external decisions and of people and organizations).
- It does not hold the secret used to authenticate to the payment platform. The secret is held on the port side (HELD_BY_PORT).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific payment service.
How to connect
Contract: erabazu.candidate/certified-tsugite/payment-rails/v1. Input: pass a PaymentAuthRequest whose op is CAPTURE or VOID to submit(request) on createPaymentRailsClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries op, auth_state and business_approval in result. Every result has approval: false. User provides: a port to the payment platform (submit, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE for capture and void; NONE for declines, pending results, failures and similar.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-03GTsugite
Idempotent charging — Payment Rails Tsugite
The Payment Rails Tsugite (継手, tsugite) checks an idempotent charge request (IdempotentChargeRequest) against the idempotency key, the pinned payment methods and the setting that allows operations that move money (allow_money_movement), carries it to the payment platform, and matches the returned receipt (IdempotentChargeReceipt) against the contract. The same idempotency key is treated as a duplicate, and no new effect is accepted (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a charge request is carried to an external payment platform, this Tsugite prevents charging twice through a resend or a duplicate, sending different content under the same key, and trusting a receipt that claims an effect for a duplicate. It is also a check against mistaking a successful charge for business approval.
When to use
- To prevent double charging, you want an idempotency key to be required on charge requests and a request with the same key to be treated as a duplicate (DEDUP_HIT, no new effect).
- You want charge requests to be sent only when it has been explicitly allowed by the setting allow_money_movement: true (by default nothing is sent).
- You want to match that the returned receipt is in a charge state that fits the request before treating it as a result (charge_state, charged).
- When resending after a payment platform failure, you want to resolve it with a lookup (lookup) so that nothing runs twice.
When not to use
- You want to treat a successful charge as business approval. A successful charge is not business approval (business_approval is not derived from carrying).
- You want to reconcile charges against deposits. Reconciliation is the role of external decisions and of people and organizations; this Tsugite does not reconcile (the paired Kernel is reconcile-facts).
- You want to confirm that amounts, currencies, taxes or fees are correct. This Tsugite does not check them.
- You want to connect to an actual payment platform right away. It has never been connected to an actual payment platform, PSP or bank, and it does not include a port implementation.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the permission to move money. When allow_money_movement is not true, or is not a boolean, it does not send operations that move money (REFUSED).
- It checks the idempotency key. It does not send a charge without an idempotency key, or a request that reuses a key with different content.
- A receipt that claims an effect for a duplicate is not accepted. The effect of a duplicate (DEDUP_HIT) is NONE.
- It checks the pinned lists. It does not send a payment method that is not pinned, or a reference that is empty or contains a path or .. (path traversal). An empty list allows nothing.
- It matches the receipt. A receipt whose state does not match op, a receipt that claims an effect for a decline, a receipt that increases the external effect, or a receipt for another request (a spoofed request_digest) is not accepted, and the client stops from then on (HALTED).
- A receipt that leaves it unknown whether money moved (effect UNKNOWN) is not accepted, and the client stops from then on (HALTED). The case is passed to a person for review.
- A receipt that claims business approval is not accepted.
- It does not send the same request_id twice. A payment platform failure is treated as UNKNOWN, and resending is resolved with a lookup (lookup) so that nothing runs twice.
What it does not do / limits
- A successful charge is not business approval. Every result carries approval: false. Business approval is the role of the Kernel (approval-facts) and of people and organizations.
- It does not check whether amounts, currencies, taxes or fees are correct, and it does not reconcile statements (that is the role of external decisions and of people and organizations).
- It has never been connected to an actual payment platform, PSP or bank. The number of actual charges or payouts sent is 0. The port implementation, the pinned lists and the permission to move money will be settled at a separate gate when connecting.
- It does not hold the secret used to authenticate to the payment platform. The secret is held on the port side (HELD_BY_PORT).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific payment service.
How to connect
Contract: erabazu.candidate/certified-tsugite/payment-rails/v1. Input: pass an IdempotentChargeRequest to submit(request) on createPaymentRailsClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries charge_state, charged and business_approval in result. Every result has approval: false. User provides: a port to the payment platform (submit, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE for a charge; NONE for duplicates, declines, failures and similar.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-04GComponent
Invoice generation — Invoice generation part (Component)
The Invoice generation part (Component) is a part that issues one invoice in stages: calculating the tax facts (the G-07 Kernel) → the document (PDF_RENDER of the J-01 Tsugite) → storage (the C-05 Tsugite, with the invoice number as the idempotency key) → the record (the audit ledger of the H-01 Tsugite). If G-07's facts show missing input or missing required entries, it does not make the document and returns only the facts. When a stage's result is unknown, it stops at that stage and continues from that stage with an issue call with the same number and the same content (fail-closed). The invoice number is decided by the caller and passed in.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind accounting, it prevents an invoice with missing required entries or missing input from going on to the document, storage and the ledger, issuing an invoice with different content under the same number, storing twice, and passing the invoice's content to the ledger.
When to use
- You want to issue one invoice in the order calculation → document → storage → record, going on to the next stage only after the previous one has finished (issue).
- You want the tax facts produced by G-07's computeInvoiceTax and, if there is need_input or there are missing required entries, to receive them as they are with need_input and missing attached, without the document being made. The inputs invoice, tax_table and taxpayer_profile are G-07's input as is.
- You want the PDF stored only once with the C-05 Tsugite, with the invoice number as the idempotency key (invoice/<number>). It is stored only when the result is APPLIED or DEDUP_HIT.
- You want the fact of issuance recorded with the H-01 Tsugite as an AuditEventEnvelope in an append-only ledger. Only the digest is passed to the ledger, not the invoice's content.
- You want to resend a stage whose result was unknown, through an issue call with the same number and the same content, as the same request and the same envelope.
- You want to check the stage and digest for each invoice number (status).
When not to use
- You want to use being issued (ISSUED) as approval of the billing, as sending to the recipient, as the establishment of a receivable, or as legal sufficiency of the required entries.
- You want to use being issued as a sign that the tax is correct for tax purposes. Being issued is not proof that it is correct for tax purposes. The tax facts remain those of G-07, and the source of truth for the facts is the tax rate table and the business's choices.
- You want it to assign invoice numbers or take care of their uniqueness or sequence. That is the caller's role.
- You also want it to handle sending to and receiving by the recipient, or the management of incoming payments and receivables. A Tsugite for sending and receiving external electronic invoices is not included in this part.
- You want to check the PDF layout, or that the required entries actually appear in the PDF. G-07's check of required entries is against what the input declares.
- You want to continue an unfinished stage across a restart. The state is only inside this process, and a restart loses the unfinished stage and the held request (storage does not happen twice thanks to the idempotency key, but the ledger envelope is made again).
- You need a part checked against real document generation, storage and ledger backends. It has never connected to a real backend.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed, a reference is invalid or a port function is missing, it stops with INVALID_OPTIONS, when the input is malformed (missing or extra fields, the form of the invoice number and so on), with INVALID_INPUT, and when a getter throws an exception, with INSPECTION_FAILED.
- When G-07 does not return ok: true, it stops with TAX_INPUT_INVALID (with tax_errors attached), when G-07's function throws, with PORT_FAILED, and when the facts are malformed or inference or advice is not null, with TAX_FACTS_INVALID, and it does not make the document.
- When G-07's facts contain need_input, it stops with NEEDS_INPUT (it does not fill them in by guessing), and when required entries are missing, with REQUIRED_ITEMS_MISSING (it does not say how to fix them); it stops before the document is made and neither stores nor records.
- When the same invoice number comes with different content (any of the invoice, the tax rate table or the business's choices), it stops with INVOICE_NUMBER_REUSED. Another issue call for a number already issued returns already_issued: true.
- When the document Tsugite's result is unknown, it stops with RENDER_UNKNOWN, when the Tsugite did not send, with RENDER_REFUSED, and when it could not convert, converted only partly, halted or produced an empty PDF, with RENDER_FAILED, and it does not go on to storage.
- When the storage Tsugite's result is unknown, it stops with STORE_UNKNOWN (keeping the same request), when the Tsugite did not send, with STORE_REFUSED (including different content under the same idempotency key), and when it halted or the result is not APPLIED or DEDUP_HIT, with STORE_FAILED, and it does not go on to the record.
- When the ledger Tsugite's result is unknown (including when state() throws), it stops with LEDGER_UNKNOWN (keeping the same envelope), and when the ledger refuses, the head has advanced so the envelope no longer chains on, or the head is malformed, with LEDGER_REFUSED, and it does not mark the invoice as issued.
- When timestamp does not return a date-time string or throws, it stops with CLOCK_FAILED, and when the previous operation has not finished, with BUSY (only one operation at a time).
What it does not do / limits
- The Invoice generation part is not a Tsugite. It has no Request or Receipt of its own; the external effects of converting the document, storing it and appending to the ledger are caused by the bundled J-01, C-05 and H-01 Tsugite under their own contracts. This part itself does not touch the outside.
- The meaning of the bundled parts (facts only; being converted is not a check of the content; being stored is not acceptance; being recorded is not approval) stays on each part's side.
- Being issued is not approval of the billing, the establishment of a receivable or legal sufficiency of the required entries (approval: false, claim: NOT_DERIVED, legal_sufficiency: NOT_VERIFIED, derives_authority: false).
- Being issued is not proof that it is correct for tax purposes. It makes no tax judgements, returns only facts and does not return how to fix anything (tax consultation is a service reserved exclusively for licensed tax accountants).
- It does not assign invoice numbers or take care of their uniqueness or sequence, and it does not handle sending to or receiving by the recipient, approval of the billing, or the management of incoming payments and receivables.
- It has never connected to real document generation, storage or ledger backends. The unit tests and fuzzing use stand-ins, and the wiring with the code of the real four parts was checked against each Tsugite's test stand-in.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use particular invoicing software.
How to connect
Create it with createInvoiceIssuance(options) in src/invoice.js. Input: references, store_target, ledger, ports and an optional timestamp. Returns: { ok, errors, instance }; the results of issue and status carry approval: false, and an invoice that has gone on to a stage comes with stage and digest. User provides (injected through ports): taxFacts (G-07), render.transform (J-01), store.submit (C-05), and ledger.append and state (H-01). Side effect: caused by the bundled Tsugite (EXTERNAL_IRREVERSIBLE); credentials are held by the ports.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-05GTsugite
Subscription and recurring billing — Payment Rails Tsugite
The Payment Rails Tsugite (継手, tsugite) checks the billing (BILL_CYCLE), pause (PAUSE), resume (RESUME) and cancel (CANCEL) operations of a recurring billing request (SubscriptionBillingRequest) against the pinned lists and the setting that allows operations that move money (allow_money_movement), carries them to the payment platform, and matches the returned receipt (SubscriptionBillingReceipt). If a check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When recurring billing is carried to an external payment platform, this Tsugite prevents sending billing or resume operations that have not been allowed, sending the same billing twice, and trusting a receipt whose state does not match op or a receipt that leaves it unknown whether money moved.
When to use
- You want to carry recurring billing operations, billing (BILL_CYCLE to BILLED), pause (PAUSE to PAUSED), resume (RESUME to RESUMED) and cancel (CANCEL to CANCELLED), to an external payment platform and match that the receipt is in a state that fits op.
- You want operations that move money (billing and resume) to be sent only when it has been explicitly allowed by the setting allow_money_movement: true (by default nothing is sent).
- You want an idempotency key to be required on billing and a billing with the same key to be treated as a duplicate (DEDUP_HIT, no new effect).
- When resending after a payment platform failure, you want to resolve it with a lookup (lookup) so that nothing runs twice.
When not to use
- You want to treat a successful billing as business approval. Successful recurring billing is not business approval (business_approval is not derived from carrying).
- You want to confirm that amounts, currencies, taxes or fees are correct. This Tsugite does not check them (that is the role of external decisions and of people and organizations).
- You want to connect to an actual payment platform right away. It has never been connected to an actual payment platform, PSP or bank, and it does not include a port implementation.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the permission to move money. When allow_money_movement is not true, or is not a boolean, it does not send operations that move money (REFUSED).
- It checks the idempotency key. It does not send a billing (BILL_CYCLE) without an idempotency key, or a request that reuses a key with different content.
- A receipt that claims an effect for a duplicate is not accepted. The effect of a duplicate (DEDUP_HIT) is NONE.
- It checks the pinned lists. It does not send a payment method that is not pinned, or a reference that is empty or contains a path or .. (path traversal). An empty list allows nothing.
- It matches the receipt. A receipt whose state does not match op, a receipt that claims an effect for a decline, a receipt that increases the external effect, or a receipt for another request (a spoofed request_digest) is not accepted, and the client stops from then on (HALTED).
- A receipt that leaves it unknown whether money moved (effect UNKNOWN) is not accepted, and the client stops from then on (HALTED). The case is passed to a person for review.
- A receipt that claims business approval is not accepted.
- It does not send the same request_id twice. A payment platform failure is treated as UNKNOWN, and resending is resolved with a lookup (lookup) so that nothing runs twice.
What it does not do / limits
- Successful recurring billing is not business approval. Every result carries approval: false. Business approval is the role of the Kernel (approval-facts) and of people and organizations.
- It does not check whether amounts, currencies, taxes or fees are correct (that is the role of external decisions and of people and organizations).
- It has never been connected to an actual payment platform, PSP or bank. The number of actual charges or payouts sent is 0. The port implementation, the pinned lists and the permission to move money will be settled at a separate gate when connecting.
- It does not hold the secret used to authenticate to the payment platform. The secret is held on the port side (HELD_BY_PORT).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific payment service.
How to connect
Contract: erabazu.candidate/certified-tsugite/payment-rails/v1. Input: pass a SubscriptionBillingRequest to submit(request) on createPaymentRailsClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries billing_state and business_approval in result. Every result has approval: false. User provides: a port to the payment platform (submit, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE for recurring billing operations; NONE for duplicates, declines, failures and similar.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-06GTsugite
Payment and remittance (payout) — Payment Rails Tsugite
The Payment Rails Tsugite (継手, tsugite) checks a payout request (PayoutRequest) against a reference to evidence received from a person or organization, the pinned payout destinations and the setting that allows operations that move money (allow_money_movement), carries it to the payment platform, and matches the returned receipt (PayoutReceipt) against the contract. ERABAZU does not create payout authorization, and without evidence it does not send (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a payout to a business is carried to an external payment platform, this Tsugite prevents sending it without evidence from a person or organization, sending to a payout destination that is not pinned, and trusting a receipt that claims payout authorization. It is a check against mistaking the ability to pay for payout authorization.
When to use
- You want to carry a payout to a business only after receiving evidence issued by a person or organization with recordAuthorityEvidence(evidence_ref, origin) (origin is only HUMAN or ORGANIZATION; this operation also takes the evidence for accepting a chargeback).
- You want to limit payout destinations to a pinned list (payout_destinations).
- You want payouts to be sent only when it has been explicitly allowed by the setting allow_money_movement: true (by default nothing is sent).
- You do not want the same payout to run twice. An idempotency key is required, the same request_id is not sent twice, and a resend after a failure is resolved with a lookup (lookup).
When not to use
- You want this Tsugite to issue payout authorization. ERABAZU does not create payout authorization. Executing a payout is the role of a person (the same approval boundary as the Kernels).
- You want to confirm that the evidence is genuine, or that amounts, currencies, taxes or fees are correct. This Tsugite checks neither (that is the role of external decisions and of people and organizations).
- You want to connect to an actual payment platform right away. It has never been connected to an actual payment platform, PSP or bank, and it does not include a port implementation.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the evidence. It does not send a payout without an evidence reference, a payout that uses evidence not received through recordAuthorityEvidence, or evidence whose origin is ERABAZU.
- A receipt that claims payout authorization is not accepted.
- It checks the permission to move money. When allow_money_movement is not true, or is not a boolean, it does not send operations that move money (REFUSED).
- It checks the pinned lists. It does not send a payout destination that is not pinned, or a reference that is empty or contains a path or .. (path traversal). An empty list allows nothing.
- It checks the idempotency key. It does not send a payout without an idempotency key, or a request that reuses a key with different content.
- It matches the receipt. A receipt whose state does not match op, a receipt that claims an effect for a decline, a receipt that increases the external effect, or a receipt for another request (a spoofed request_digest) is not accepted, and the client stops from then on (HALTED).
- A receipt that leaves it unknown whether money moved (effect UNKNOWN) is not accepted, and the client stops from then on (HALTED). The case is passed to a person for review.
- It does not send the same request_id twice. A payment platform failure is treated as UNKNOWN, and resending is resolved with a lookup (lookup) so that nothing runs twice.
What it does not do / limits
- Being able to pay is not payout authorization. payout_authorization in the result is not derived from carrying either, and every result carries approval: false.
- It does not check the content of the payout authorization (whether the evidence is genuine) or whether amounts, currencies, taxes or fees are correct (that is the role of external decisions and of people and organizations).
- It has never been connected to an actual payment platform, PSP or bank. The number of actual charges or payouts sent is 0. The port implementation, the pinned lists and the permission to move money will be settled at a separate gate when connecting.
- It does not hold the secret used to authenticate to the payment platform. The secret is held on the port side (HELD_BY_PORT).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific payment service.
How to connect
Contract: erabazu.candidate/certified-tsugite/payment-rails/v1. Input: receive evidence with recordAuthorityEvidence(evidence_ref, origin) on createPaymentRailsClient({ cfg, port }), then pass a PayoutRequest to submit(request). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries payout_state and payout_authorization in result. Every result has approval: false. User provides: a port to the payment platform (submit, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-07GKernel
Tax calculation — Tax calculation part (Kernel)
The Tax calculation part (Kernel) is a standalone part that, for Japan's consumption tax (standard rate 10%, reduced rate 8%, former rate 8%), returns as facts, from the lines of one qualified invoice (or one simplified qualified invoice), the total consideration and the consumption tax for each tax rate, and the missing required entries. It holds no tax rates in code: it selects them from the tax rate table it is given (with periods and the national and local breakdown), and it selects the rounding method from the business's settings by the invoice date. What the table or the settings do not determine is not guessed but made need_input, and malformed input is stopped with a fixed code and location (fail-closed). The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind "tax included", it prevents rounding the tax line by line, floating-point errors entering the tax, calculating with tax rates held in code, and filling input that the table or the settings do not determine with guesses. The tax is rounded only once per tax rate for each invoice.
When to use
- You want to produce as facts, from the lines (1 to 1000) of one qualified invoice (QUALIFIED) or simplified qualified invoice (SIMPLIFIED), the total consideration and the consumption tax for each tax rate.
- You want the tax rounded only once per invoice for each tax rate (each set of the rate breakdown rate, national_rate and local_rate), by the method in the business's settings (FLOOR rounding down, CEIL rounding up, HALF_UP rounding half up). It does not round per line or per table row.
- You want the tax computed as an exact fraction without floating point, according to whether the invoice is tax-exclusive (TAX_EXCLUDED) or tax-inclusive (TAX_INCLUDED), one of the two per invoice. Monetary values and rates are passed as decimal strings.
- You want tax rates selected from a tax rate table the user passes (each row with its period, national and local breakdown, and source). bundledTaxTable() returns a copy of the bundled table, and another table can also be passed as tax_table.
- You want the missing required entries (name or business name, registration number, transaction date, description of the transaction, the indication that an item is subject to the reduced tax rate, and so on) listed in the same call as the calculation. Missing entries do not stop the calculation.
- You need the same invoice_digest (the sha256 of the RFC 8785 canonical JSON) and byte-for-byte identical output from the same invoice regardless of key order. It does not use the clock, random numbers or the environment's locale.
When not to use
- You need period aggregation (the accumulation-method calculation of output tax and input tax, or the choice between it and the division method), the deemed purchase rates of simplified taxation, the 20% special rule, or import consumption tax. This part only does calculations closed within one invoice.
- You need the determination of non-taxable, out-of-scope or exempt status, withholding tax, or another country's taxes. Of the catalog one-liner "consumption tax, withholding and so on", withholding tax is not included.
- You want it to determine whether an item is subject to the reduced tax rate or whether a transitional measure applies. The caller decides these and passes them through rate_class and by naming a rate_id.
- You want to check whether a registration number is in the register on the National Tax Agency's publication site. It checks only the form of T followed by 13 digits and does not query the register.
- You handle invoices containing negative values (discounts, returns, refunds of consideration and so on). v1 does not accept negative values and stops with NEGATIVE_AMOUNT.
- You handle values in another country's currency. It stops with UNSUPPORTED_CURRENCY.
- You need the tax divided between national and local. For the national and local breakdown it outputs only the rates.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the argument is not a plain object holding exactly the three keys { invoice, tax_table, taxpayer_profile }, it stops with INVALID_ARGUMENT, and when inspecting the values itself throws an exception (a Proxy trap and so on), with INSPECTION_FAILED. It does not accept getters, class instances, sparse arrays or a __proto__ key, and checks the values after copying them into plain data.
- When the invoice is malformed (an unknown key, a missing key, a value outside the vocabulary, a lone surrogate, 0 lines or more than 1000 lines, a reduced_marker other than true), it stops with INVALID_INVOICE, when a date is not a real calendar date, with INVALID_DATE, and when two lines have the same line_id, with DUPLICATE_LINE_ID.
- When a monetary value is not in the fixed form { value, scale, currency } (scale is not an integer from 0 to 6, or the number of decimal places differs from scale), it stops with MALFORMED_AMOUNT, when it is negative, with NEGATIVE_AMOUNT, and when the currency differs, with UNSUPPORTED_CURRENCY.
- When the tax rate table is malformed (including a source url that is not https), it stops with INVALID_TAX_TABLE, when a row has no source, with MISSING_SOURCE, when a rate is not in the fixed form or is 0, with MALFORMED_RATE, and when national_rate + local_rate does not equal rate, with RATE_SPLIT_MISMATCH.
- When a period ends before it begins, it stops with INVALID_PERIOD, when two rows have the same rate_id, with DUPLICATE_RATE_ID, when the periods of rows of the same class overlap, with OVERLAPPING_RATE_ROWS, and when the business's settings are malformed (a rounding method other than FLOOR, CEIL or HALF_UP and so on), with INVALID_PROFILE.
- When there is no transaction date, when the table has no row for the transaction date, when a named rate_id is not in the table, is of a different class or does not match the date, or when 0 or 2 or more rounding settings cover the invoice date, it does not guess: it returns need_input, with groups empty and invoice_totals null (it does not output a partial calculation).
- Of the required entries (Consumption Tax Act Article 57-4 paragraphs 1 and 2, Invoice Q&A questions 54 and 58), it checks the name or business name, the registration number (form only), the transaction date, the description of the transaction with the indication that an item is subject to the reduced tax rate, and the name of the receiving business (qualified invoices only) from the input fields, and whether the totals per tax rate, the applied rate and the tax are shown from the declaration in presentation, and lists what is absent in missing of required_items.
- Fuzzing that compares the results against an independent oracle rewritten from the rules without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only tax facts. The fields inference and advice are always null, and no string in the output contains wording on how to fix something or a judgement. The field approval is false and authority is NOT_DERIVED (derives_authority: false).
- The results are a presentation of facts: they are not a tax judgement, they do not show that a tax return is correct, and they are not acting on anyone's behalf in tax judgements or tax returns. Tax consultation is a service reserved exclusively for licensed tax accountants (zeirishi). Whether a transitional measure applies, the classification of items and whether a registration number is in the register are checked outside this part.
- It does not handle period aggregation (whether the choice between the accumulation method and the division method is appropriate), simplified taxation, the 20% special rule, import consumption tax, the determination of non-taxable, out-of-scope or exempt status, withholding tax, or another country's taxes.
- It does not check that a reduced_marker is attached to a standard-rate line (and does not report it as a missing entry either). The fields sales_tax_method and taxation_method are only checked for form and copied; they are not used in the calculation.
- When tax-inclusive values containing fractions below the smallest currency unit are passed and rounding up or rounding half up makes the tax exceed the total, the tax-exclusive total becomes negative. It outputs this as a fact as it is and does not correct it.
- The bundled table has only 3 rows, based on National Tax Agency materials as of 2026-10-01. Tax rates before 2014-04-01 and amendment proposals that have not been enacted are not included. A transaction date in a period with no row in the table becomes need_input.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call computeInvoiceTax({ invoice, tax_table, taxpayer_profile }) in src/tax-facts.js (it does not throw exceptions). Input: the invoice (lines, presentation and so on), the tax rate table (each row's rate breakdown, period and source) and the business's settings (rounding). Returns: { ok: true, errors: [], facts } (groups, invoice_totals, required_items and need_input, with inference: null and advice: null) or { ok: false, errors: [{ code, where }] } (where is a JSON Pointer). Side effect: NONE. There is no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-08GTsugite
Exchange rate retrieval — Payment Rails Tsugite
The Payment Rails Tsugite (継手, tsugite) checks an exchange rate request (FxRateRequest) against the pinned currency pairs (fx_pairs) and rate sources (rate_sources), carries it, and matches the returned receipt (FxRateReceipt) against the contract. An observed rate is not authoritative, and a receipt that claims authority is not accepted (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When exchange rates are fetched from outside, this Tsugite prevents using rates for currency pairs or from sources that are not pinned, and treating an observed rate as authoritative.
When to use
- You want to observe and carry exchange rates only for the pinned currency pairs and sources, and receive them as a result (rate_state, rate_ref).
- You want to use received rates on the premise that they are observed values, not authoritative ones (rate_authority is not derived from carrying).
When not to use
- You want to convert between currencies (currency conversion). This Tsugite is for exchange rate retrieval (carrying observed rates) and does not include any conversion function.
- You want to treat an observed rate as an authoritative rate. An observed rate is not authoritative.
- You want to connect to an actual payment platform right away. It has never been connected to an actual payment platform, PSP or bank, and it does not include a port implementation.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the pinned lists. It does not send a currency pair that is not pinned, a rate source that is not pinned, or a reference that is empty or contains a path or .. (path traversal). An empty list allows nothing.
- A rate receipt that claims authority is not accepted.
- A receipt that increases the external effect is not accepted. The effect of an exchange rate request is NONE.
- A receipt whose state does not match op, or a receipt for another request (a spoofed request_digest), is not accepted, and the client stops from then on (HALTED).
- A receipt whose effect is unknown (effect UNKNOWN) is not accepted, and the client stops from then on (HALTED).
- It does not send the same request_id twice. A payment platform failure is treated as UNKNOWN, and resending is resolved with a lookup (lookup) so that nothing runs twice.
What it does not do / limits
- It is a Tsugite that carries exchange rate requests; it does not include any conversion function (converting between currencies).
- An observed rate is not authoritative. Every result carries approval: false.
- It does not check whether amounts, currencies, taxes or fees are correct (that is the role of external decisions and of people and organizations).
- It has never been connected to an actual payment platform, PSP or bank. The number of actual charges or payouts sent is 0. The port implementation, the pinned lists and the permission to move money will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific payment service.
How to connect
Contract: erabazu.candidate/certified-tsugite/payment-rails/v1. Input: pass a FxRateRequest to submit(request) on createPaymentRailsClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries rate_state, rate_ref and rate_authority in result. Every result has approval: false. User provides: a port to the payment platform (submit, and optionally lookup). Side effect: NONE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-09GTsugite
Statement intake for reconciliation (processor) — Payment Rails Tsugite
The Payment Rails Tsugite (継手, tsugite) checks a statement intake request (StatementIntakeRequest) against the pinned statement issuers (processors), carries it, and matches the returned receipt (StatementIntakeReceipt) against the contract. Carrying statements is not reconciliation, and a receipt that claims reconciliation is not accepted (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When payment statements are taken in from outside, this Tsugite prevents taking in statements from an issuer that is not pinned, and mistaking the carrying of statements for completed reconciliation of statements against deposits.
When to use
- You want to take in statements only from the pinned statement issuers and receive them as a result (intake_state), leaving reconciliation to the Kernel (reconcile-facts) or to people and organizations.
- You want to handle intake and reconciliation separately. A receipt that claims reconciliation is not accepted, and reconciliation is not derived from carrying.
When not to use
- You want to reconcile statements against deposits. This Tsugite does not reconcile. Reconciling statements is the role of external decisions and of people and organizations.
- You want to confirm that the amounts, currencies, taxes or fees in statements are correct. This Tsugite does not check them.
- You want to connect to an actual payment platform right away. It has never been connected to an actual payment platform, PSP or bank, and it does not include a port implementation.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the pinned lists. It does not send a statement issuer that is not pinned, or a reference that is empty or contains a path or .. (path traversal). An empty list allows nothing.
- A receipt that claims reconciliation is not accepted.
- A receipt that increases the external effect is not accepted. The effect of statement intake is NONE or EXTERNAL_REVERSIBLE (NONE for FAILED).
- A receipt whose state does not match op, or a receipt for another request (a spoofed request_digest), is not accepted, and the client stops from then on (HALTED).
- A receipt whose effect is unknown (effect UNKNOWN) is not accepted, and the client stops from then on (HALTED).
- It does not send the same request_id twice. A payment platform failure is treated as UNKNOWN, and resending is resolved with a lookup (lookup) so that nothing runs twice.
What it does not do / limits
- It does not reconcile statements. Carrying statements is not reconciliation, and reconciliation in the result is not derived from carrying either.
- Successful intake is not approval. Every result carries approval: false.
- It does not check whether amounts, currencies, taxes or fees are correct (that is the role of external decisions and of people and organizations).
- It has never been connected to an actual payment platform, PSP or bank. The number of actual charges or payouts sent is 0. The port implementation, the pinned lists and the permission to move money will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific payment service.
How to connect
Contract: erabazu.candidate/certified-tsugite/payment-rails/v1. Input: pass a StatementIntakeRequest to submit(request) on createPaymentRailsClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries intake_state and reconciliation in result. Every result has approval: false. User provides: a port to the payment platform (submit, and optionally lookup). Side effect: NONE or EXTERNAL_REVERSIBLE (NONE for FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-10GTsugite
Chargeback disputes — Payment Rails Tsugite
The Payment Rails Tsugite (継手, tsugite) checks the filing (OPEN), evidence submission (SUBMIT_EVIDENCE), observation (OBSERVE) and acceptance (ACCEPT) operations of a chargeback request (DisputeRequest) before carrying them, and matches that the returned receipt (DisputeReceipt) is in the state that fits op. It sends an acceptance only after receiving evidence from a person or organization, and only when the setting allows it as an operation that moves money. Carrying is not resolution authority (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When chargebacks are carried to an external payment platform, this Tsugite prevents sending an evidence submission without an evidence reference, sending an acceptance (ACCEPT) without evidence from a person or organization, and trusting a receipt that claims resolution authority. Acceptance is a business decision that cannot be undone, so it is carried only when there is evidence.
When to use
- You want to carry chargeback filing (OPEN to OPENED), evidence submission (SUBMIT_EVIDENCE to EVIDENCE_SUBMITTED), observation (OBSERVE to OBSERVED) and acceptance (ACCEPT to ACCEPTED), and match that the receipt is in a state that fits op.
- You want to leave the resolution of chargebacks to the decisions of people and organizations, and not derive resolution authority (resolution_authority) from carrying.
- You want to carry a chargeback acceptance (ACCEPT) only after receiving evidence issued by a person or organization with recordAuthorityEvidence, and only when it has been allowed by the setting allow_money_movement: true.
When not to use
- You want to accept a chargeback (ACCEPT) without evidence from a person or organization, or while the setting allow_money_movement is not true. This Tsugite does not send it (REFUSED).
- You want to resolve a chargeback or issue a refund. This Tsugite carries chargeback filing, evidence submission, observation and acceptance; it has no resolution authority and does not include any refund function. Carrying an acceptance is not resolution.
- You want to confirm that the evidence for an acceptance is a genuine decision. This Tsugite only looks at whether it is a reference received from a person or organization.
- You want to connect to an actual payment platform right away. It has never been connected to an actual payment platform, PSP or bank, and it does not include a port implementation.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the evidence for an acceptance. It does not send an acceptance (ACCEPT) without an evidence reference, an acceptance that uses evidence not received with recordAuthorityEvidence, or an acceptance whose evidence has origin ERABAZU. It does not send a request that attaches the evidence fields to an op other than acceptance either.
- It checks the permission to move money. When allow_money_movement is not true, or is not a boolean, an acceptance (ACCEPT) is not sent (REFUSED).
- It does not send an evidence submission without an evidence reference. It does not send one whose reference is empty or contains a path or .. either.
- A receipt that claims resolution authority is not accepted.
- A receipt that increases the external effect is not accepted. The effect is NONE for observation, EXTERNAL_REVERSIBLE for filing and evidence submission, and EXTERNAL_IRREVERSIBLE for acceptance (NONE for FAILED).
- A receipt whose state does not match op, or a receipt for another request (a spoofed request_digest), is not accepted, and the client stops from then on (HALTED).
- A receipt whose effect is unknown (effect UNKNOWN) is not accepted, and the client stops from then on (HALTED).
- It does not send the same request_id twice. A payment platform failure is treated as UNKNOWN, and resending is resolved with a lookup (lookup) so that nothing runs twice.
What it does not do / limits
- It does not resolve chargebacks. Carrying an acceptance (ACCEPT) is not resolution authority, and every result carries approval: false. Resolution is the role of external decisions and of people and organizations.
- It does not confirm the content of the evidence for an acceptance (whether it is a genuine decision). It only looks at whether it is a reference received from a person or organization. ERABAZU does not create a decision to concede the dispute.
- It has never been connected to an actual payment platform, PSP or bank. The number of actual charges or payouts sent is 0. The port implementation, the pinned lists and the permission to move money will be settled at a separate gate when connecting.
- It does not hold the secret used to authenticate to the payment platform. The secret is held on the port side (HELD_BY_PORT).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a specific payment service.
How to connect
Contract: erabazu.candidate/certified-tsugite/payment-rails/v1. Input: after receiving the evidence with recordAuthorityEvidence on createPaymentRailsClient({ cfg, port }), pass a DisputeRequest to submit(request). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries dispute_state and resolution_authority in result. Every result has approval: false. User provides: a port. Side effect: NONE for observation, EXTERNAL_REVERSIBLE for filing and evidence submission, and EXTERNAL_IRREVERSIBLE for acceptance.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-11GPrimitive
Decimal representation of amounts — Decimal representation of amounts (string + scale) part (Primitive)
The Decimal representation of amounts (string + scale) part (Primitive) is a standalone part that holds an amount as { value, scale, currency } (a decimal integer string, the position of the decimal point, and a currency code), and reads amounts from decimal strings, normalizes them by removing trailing zeros, adds, compares and tests equality of amounts in the same currency, and writes them out as decimal strings. It computes only with BigInt, does not use floating point, and does not round.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents accidents in which an amount drifts by a fraction because of decimal error. Because it does not use floating point, no such error enters, and a result that does not fit in 38 digits is stopped rather than silently rounded. Values it cannot accept are stopped with a fixed code and location.
When to use
- You want to read a decimal string (-?[0-9]+(.[0-9]+)?, ASCII digits only), together with a currency code, as an amount (parseDecimal). scale stays the number of decimal digits as written; it is not normalized.
- You want to bring an amount to the smallest scale by removing trailing zeros (normalize; zero is { value: "0", scale: 0 }).
- You want to add two amounts in the same currency without rounding (add; the result is a normalized amount).
- You want to know the numeric order (-1, 0 or 1 from compare) or equality (equal) regardless of scale. equal is false when the currencies differ, which is not an error.
- You want to write an amount out as a decimal string with exactly scale decimal digits (toText). parseDecimal(toText(a)) returns to the same value and scale.
- You want to call it locally as deterministic processing that has no clock, no random numbers, no environment locale and no outside connection.
When not to use
- You need rounding, multiplication, division or currency conversion. This part does not handle them.
- You need a display string with symbols or digit grouping separators. Formatting for display is the role of the L-03 part.
- You want to confirm that a currency code is in the ISO 4217 table, or that an amount matches the number of decimal digits for its currency. This part checks only the form (3 uppercase letters).
- You handle amounts beyond 38 digits or scale 18. They cannot be represented, so it stops (TOO_MANY_DIGITS, INVALID_SCALE).
- You want leading zeros in the integer part to be refused. parseDecimal accepts leading zeros and drops them. If you need a narrower form, the caller checks it.
- You need an upper limit on the length of text. It has none; the size limit is decided by the I-06 part or by the caller.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with INVALID_ARGUMENT when the argument is not a plain object, has keys other than the fixed ones, has getters, non-enumerable properties or symbol keys, or when inspecting its values itself raises an exception (a Proxy trap and so on). It does not call getters.
- It stops with NOT_A_STRING when text or value is not a string (a value passed as a number, NaN, Infinity, a bigint, a missing key).
- It stops with INVALID_DECIMAL_TEXT when text is not of the form -?[0-9]+(.[0-9]+)? (exponents, +, white space, digit grouping separators, "1.", ".5", full-width digits, line breaks).
- It stops with INVALID_VALUE when value is not of the form -?[0-9]+ or has extra leading zeros, and with NEGATIVE_ZERO for a negative zero such as "-0".
- It stops with TOO_MANY_DIGITS when there are more than 38 digits (including the result of an addition), and with INVALID_SCALE when scale is not an integer from 0 to 18 (-0, a fraction, a string) or when there are more than 18 decimal digits.
- It stops with INVALID_CURRENCY when currency is not a string of 3 uppercase letters, and with CURRENCY_MISMATCH when the two amounts given to add or compare are in different currencies.
- When it stops, it returns only the first problem, in a fixed order, as a code (code) and a location (where; a JSON Pointer, which starts with /a or /b for operations on two amounts).
What it does not do / limits
- It does not do rounding, multiplication, division, currency conversion or formatting for display.
- Being able to compute does not mean the amount is correct for the business or that it may be accepted.
- It does not check against the number of decimal digits for each currency. It accepts a code that is not in the ISO 4217 table if the form matches.
- It does not accept full-width digits or digits from other writing systems. Digits are only ASCII 0 to 9.
- Computation results are not an approval (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular decimal library.
How to connect
Call parseDecimal({ text, currency }), normalize(amount), add(a, b), compare(a, b), equal(a, b) and toText(amount) in src/decimal-amount.js (none of them throw). An amount is { value, scale, currency } (value is an integer string of up to 38 digits excluding the sign, scale is an integer from 0 to 18, and currency is 3 uppercase letters). It returns { ok: true, errors: [], amount | result | equal | text } or { ok: false, errors: [{ code, where }] }. There are no side effects, no state, no outside connection and no credentials, and there is no port for the caller to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-12GKernel
Transaction event ID — Transaction event ID (txn_id) part (Kernel)
The Transaction event ID (txn_id) part (Kernel) is a standalone part that produces an event ID (txn_id) by fixed rules from the facts of a transaction or a piece of evidence (kind, source, occurred_on, amount, counterparty). It turns the form to be hashed into RFC 8785 canonical JSON, takes its SHA-256, and writes the first 16 bytes as 32 lower-case hex characters. When a required fact is absent it does not guess but returns need_input, and malformed input is stopped with a fixed code and location (fail-closed). The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
A matching key that prevents double counting needs the same value from the same facts every time. It prevents the ID of the same facts from changing because of trailing zeros in an amount and how scale is written, the order of the input keys, floating-point errors, or references to where a record came from such as raw_refs.
When to use
- You want to create, from the facts of a transaction or a piece of evidence, an event ID (txn_id) that takes the same value from the same facts, as a matching key that prevents double counting.
- You want amounts of the same value to get the same ID however scale is written. The amount has its trailing zeros removed with BigInt so that scale is minimal, and floating point is not used. A different currency, sign or value gives a different ID.
- You want to identify the counterparty by its ID in the source (id_in_source) or by name. When id_in_source is present and not empty, only it is used; otherwise the name after NFKC with leading and trailing whitespace removed is used.
- When required facts are absent, you want to receive missing, the list of the locations (JSON Pointer) of the absent facts, instead of having them filled by guesses. The state is need_input, and this is not an error.
- Along with the ID, you want to receive the form that was hashed (hashed) and its canonical JSON string (canonical), to check what the ID was made from.
- You need deterministic output that does not use the clock, random numbers or the environment's locale and does not depend on the order of the input keys. There is no external boundary and no state.
When not to use
- You want it to decide whether two records are the same transaction. Separate transactions with the same field values (two purchases on the same day for the same amount from the same counterparty, and so on) get the same txn_id. Telling them apart is the role of source_key, which the G-13 and G-14 parts handle.
- You want variations in notation brought together. Differences in upper and lower case, (株) versus 株式会社, zero-width characters and the number of spaces give different IDs. Proposing alias candidates is not part of this part.
- You want it to decide kind (evidence or transaction), source or id_in_source. The caller decides all of them and passes them. kind is used as declared and is not determined by this part.
- You need record-form checks, handling of supersedes, or grouping of duplicates. The record form and supersedes are within the scope of the G-13 part, and the identity criteria within the scope of the G-14 part.
- You want to rely on IDs never colliding. txn_id is a hash truncated to 128 bits, and the absence of collisions is not promised.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED). NFKC comes from String.prototype.normalize in Node.js (ICU), so results can change when the Unicode version changes.
What it checks
- When the argument is not a plain object holding only the six keys (kind, source, occurred_on, amount, counterparty, raw_refs), when it has getters, non-enumerable properties or symbol keys (except in raw_refs), or when inspecting the values itself throws an exception (a Proxy trap and so on), it stops with INVALID_ARGUMENT.
- When kind is not evidence or transaction, it stops with INVALID_KIND, when source is not a string matching ^[a-z0-9][a-z0-9_-]{0,62}$, with INVALID_SOURCE, and when occurred_on is not a real calendar date in YYYY-MM-DD form (years 0001 to 9999), with INVALID_DATE.
- When amount is not a plain object holding only { value, scale, currency }, when value is not of the form -?[0-9]+ (a leading zero, "-0", or more than 38 digits excluding the sign), when scale is not an integer from 0 to 18 (including -0), or when currency is not 3 upper-case letters, it stops with INVALID_AMOUNT.
- When counterparty is not a plain object holding only keys from { id_in_source, name }, when either is not a string or null, or when it contains a lone surrogate, it stops with INVALID_COUNTERPARTY. The form of the name is checked even when id_in_source is present.
- If even one field is malformed, the result is an error ({ ok: false }) even when other fields are absent, and only the first one found is returned. The order of checks is the form of the argument, then kind, source, occurred_on, amount and counterparty.
- It does not read raw_refs (it neither checks their form nor calls getters) and does not put them into the ID. The values checked and the values hashed are the same.
- Fuzzing that compares the results against an independent oracle rewritten from the rules without using the helper functions in src, src/canonical.js or crypto (its SHA-256 is also pure JS) confirmed that mismatches and fail-open cases were 0. Only for NFKC does the oracle use the same function.
- It is tested with 15 known answers (test/known-answers.json). The canonical strings were written by hand from the rules, and each txn_id was computed with a pure-JS SHA-256 that does not use src. The same answers are shared with the G-13 part.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null, and no string in the output contains wording of a judgement. The result is not an approval (result_is_approval: false) and creates no authority (derives_authority: false).
- The event ID is a value made from fixed fields by fixed rules; it is not a bookkeeping or reconciliation judgement. It does not decide whether two records are the same transaction.
- It does not normalize id_in_source (it uses it as is, including leading and trailing whitespace). For names it only applies NFKC and removes leading and trailing whitespace, and brings nothing further together.
- It checks only the form of the currency code (3 upper-case letters) and does not compare it with the ISO 4217 table.
- It has no length limit for id_in_source and name. NFKC, canonicalization and hashing take time in proportion to the length. The size limit is set by the I-06 part or by the user.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call computeTxnId(input) in src/txn-id.js (it does not throw exceptions). Input: { kind, source, occurred_on, amount: { value, scale, currency }, counterparty: { id_in_source?, name? }, raw_refs? }. Returns: { ok: true, errors: [], facts } (state, txn_id, hashed, canonical and missing, with inference: null and advice: null) or { ok: false, errors: [{ code, where }] } (where is a JSON Pointer). Side effect: NONE. There is no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-13GKernel
Transaction and match record checks — Common-form transaction and match record check part (Kernel)
The Common-form transaction and match record check part (Kernel) is a standalone part that checks every field of a common-form transaction (erabazu.common.txn/1) and confirms that its txn_id equals the value recomputed by the event ID rules of G-12. A match (erabazu.common.match/1) is checked as a record separate from the transaction. For a set of transactions it outputs the facts of the supersedes chains and the chains in order from oldest, but it does not decide which version is correct. Malformed input is stopped with a fixed code and location (fail-closed). The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
The form of the records that books are built from tends to be used without anyone looking at it. It prevents trusting the txn_id written in a record at its word, writing match information into the transaction record, and failing to notice breaks, cycles and forks in supersedes chains.
When to use
- You want to check one common-form transaction as a closed form (schema, txn_id, kind, occurred_on, amount, counterparty, lines, source, raw_refs, probe_version and extracted_at, plus the optional supersedes and source_key) (checkTxn).
- You want to confirm that the txn_id of a record equals the value computed by the rules of G-12 from kind, source, occurred_on, amount and counterparty. If they differ, it returns TXN_ID_MISMATCH and the recomputed value recomputed_txn_id.
- You want to check a match record (evidence_txn, transaction_txn, basis, matcher_version, confidence) as a record separate from the transaction (checkMatch). A match record that has transaction fields is not accepted.
- For a set of transactions (up to 10,000), you want the supersedes chains arranged in order from oldest, and to receive as problems the cases of a missing target, the same id, a different source, a cycle and a fork (checkSupersedesChain).
- You need deterministic output that does not use the clock, random numbers or the environment's locale. The result of checkSupersedesChain also does not depend on the order of the input. There is no external boundary and no state.
When not to use
- You want it to decide which version is correct or which one to use. It only lists the facts of the supersedes chains.
- You need valuation (currency conversion and so on) or grouping of duplicates. Grouping is within the scope of the G-14 part.
- You want to confirm that the evidence_txn and transaction_txn of a match are real transactions, or that the kind of each is right. It checks only the form of the ids and that the two differ.
- You want to confirm that the sum of the line amounts matches the transaction amount, or that the currency of the lines is the same as the currency of the transaction. It checks neither.
- You want to collect absent facts. When a record lacks fields, it stops with INVALID_RECORD rather than returning need_input. This part checks stored records, and its role differs from G-12, which outputs the list of absent facts.
- You want to handle, in the same set, a correction whose txn_id does not change even though the content was fixed. A set with two items with the same txn_id stops with DUPLICATE_TXN_ID.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED). NFKC depends on the runtime's ICU, so the result of name normalization can change when the Unicode version changes.
What it checks
- When a record is not plain data (getters, class instances, sparse arrays, symbol keys, a __proto__ key, nesting deeper than 8 levels or arrays of more than 10,000 elements), or has an unknown key or a missing key, it stops with INVALID_RECORD, and when inspecting the values itself throws an exception, with INSPECTION_FAILED.
- When schema differs, it stops with INVALID_SCHEMA, when txn_id, supersedes and the like are not 32 lower-case hex characters, with INVALID_TXN_ID, when kind is outside the vocabulary, with INVALID_KIND, when a date is not a real calendar date, with INVALID_DATE, and when an amount is malformed, with INVALID_AMOUNT.
- When the counterparty is empty (id_in_source is empty and name is also empty after NFKC and trim) or malformed, it stops with INVALID_COUNTERPARTY, when lines or quantities are malformed, with INVALID_LINES, when source is malformed, with INVALID_SOURCE, and when raw_refs is not an array of 1 to 1024 Refs without duplicates, with INVALID_RAW_REFS.
- When probe_version is malformed, it stops with INVALID_PROBE_VERSION, when extracted_at is not an RFC 3339 date-time (including a leap second or a lower-case t or z), with INVALID_TIME, and when source_key is malformed, with INVALID_SOURCE_KEY. The checks run in a fixed order, and only the first one hit is returned.
- It does not trust the txn_id at the record's word: it recomputes it by the rules of G-12 and compares. If they differ, it stops with TXN_ID_MISMATCH.
- When a match record is not plain data, or has an unknown key (including transaction fields) or a missing key, or when basis, matcher_version or confidence (exact, probable, manual) is malformed, it stops with INVALID_MATCH, and when the two ids are the same, with SAME_TXN_MATCHED.
- When the chain argument is not a plain object holding only { txns }, it stops with INVALID_ARGUMENT, and when two transactions have the same txn_id, with DUPLICATE_TXN_ID. Problem edges (a missing target, the same id, a different source, a fork, a cycle) are not linked into a chain and are output in problems as facts with codes beginning with SUPERSEDES_.
- Fuzzing that compares the results against an independent oracle rewritten from the rules without using the helper functions in src confirmed that mismatches and fail-open cases were 0. For NFKC and SHA-256, the oracle also uses the same Node.js functions.
What it does not do / limits
- It returns only facts about the form of the records and their agreement with the rules. The fields inference and advice are always null, approval is false, and it creates no authority (derives_authority: false). Name and description strings from the input are copied as they are.
- The results are not a judgement that the content of a transaction is correct or that a given version is correct. Nor are they accounting judgements or bookkeeping done on anyone's behalf. Whether a match is right, whether a kind is right and the valuation of amounts (currency and so on) are outside this part.
- It takes kind (evidence or transaction) as declared and does not determine it.
- It does not check whether the retrieved items that raw_refs point to exist, or whether probe_version is a real version (form only). It also checks only the form of the 3 upper-case letters of a currency and does not compare them with the ISO 4217 list.
- It does not load the G-12 part itself; it holds a copy of the rules inside. That the copy is the same was confirmed by checking the known answers of both in the tests and by a one-time comparison.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call checkTxn(record), checkMatch(record) and checkSupersedesChain({ txns }) in src/txn-record.js (they do not throw exceptions and do not modify the input). Returns: { ok: true, errors: [], facts } or { ok: false, errors: [{ code, where }] } (where is a JSON Pointer; TXN_ID_MISMATCH also carries recomputed_txn_id). The facts of checkTxn are txn_id, hashed, canonical, supersedes and source_key; for chains they are chains and problems. Side effect: NONE. There is no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
G-14GKernel
Transaction identity criteria — Transaction identity criteria part (Kernel)
The Transaction identity criteria part (Kernel) is a standalone part that divides transaction records into groups with the same key, following the identity criterion declared by the caller (source_key, or composite_key chosen from the date, the amount and the counterparty). Records without the declared key are not guessed but listed in need_input. When fields differ within a group it only sets differing: true; it neither merges nor deletes. It does no fuzzy matching. The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
As a foundation for judging whether records are the same transaction, it groups records that get the same key under the declared criterion. It prevents filling records that have no key with guesses, bringing similar names, nearby dates or nearby amounts together as the same thing, and discarding the differences between records when grouping them.
When to use
- Records have the platform's transaction id (source_key), and you want to group them with only that as the key (basis: source_key). identity_key is the string "sk:" joined with source_key.
- There is no id, and you want to choose 1 to 3 of the date (occurred_on), the amount (amount) and the counterparty (counterparty) as the key (basis: composite_key). The key is the SHA-256 (64 lower-case hex characters) of the RFC 8785 canonical JSON of the chosen fields only.
- You want differences in the scale of amounts or between full-width and half-width characters in counterparty names to give the same key, with the same normalization as the event ID of G-12. The amount has its trailing zeros removed so that scale is minimal; for the counterparty, id_in_source alone is used if it is not empty, and otherwise the name after NFKC with leading and trailing whitespace removed.
- You want records without the declared key returned as need_input, without a key being made by guessing. Each record goes exactly once into either groups or need_input.
- You want to know, as differing, that the presence or values of record fields differ within a group with the same key. Values are compared as written, so differences in representation such as scale or full-width versus half-width are also counted as differences.
- You need deterministic output that does not use the clock, random numbers or the environment's locale. groups are in identity_key order, and refs and need_input are in input order.
When not to use
- You want candidates with similar names, nearby dates or nearby amounts. It does no fuzzy matching. Counterparty names only go through NFKC and removal of leading and trailing whitespace, and nothing further is brought together.
- You want it to merge or delete records, or to decide which one to keep (supersedes). It only outputs the groups.
- You want to mix records from other sources and group them by source_key. Records have no source field, so even if source_key happens to be the same, they are not told apart. The caller passes the records per source.
- When the declaration is composite_key, you also want source_key used as part of the key. Following the declaration, source_key is not used for the key (it is counted for differing).
- You want the part to choose the criterion. Which criterion to use is declared by the caller.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED). NFKC depends on the runtime's ICU, so the result of name normalization can change when the Unicode version changes.
What it checks
- When the argument is not a plain object with only the two data properties { declaration, records }, it stops with INVALID_ARGUMENT, and when inspecting the values itself throws an exception (a Proxy trap and so on), with INSPECTION_FAILED. It does not accept getters, class instances, sparse arrays or a __proto__ key.
- When the basis of the declaration is not source_key or composite_key, when it has an unknown key (including source_key with fields attached), or when fields is not an array of 1 to 3 non-overlapping items from occurred_on, amount and counterparty, it stops with INVALID_DECLARATION.
- When records is not an array of plain data (up to 10,000 items, nesting up to 8 levels), or when a record has an unknown key or has no ref or a ref not in Ref form, it stops with INVALID_RECORDS, and when two records have the same ref, with DUPLICATE_REF.
- When a given field (source_key, occurred_on, amount, counterparty) is malformed, it stops with INVALID_FIELD, even for a field the declaration does not use. An empty source_key string is also stopped. A counterparty that is { } or has only empty strings is treated not as a form error but as an absent field (need_input).
- The checks run in a fixed order (the argument, then declaration, then the form of each record, then overlapping refs), and only the first one hit is returned.
- Fuzzing that compares the results against an independent oracle rewritten from the rules without using the helper functions in src confirmed that mismatches and fail-open cases were 0. For NFKC and SHA-256, the oracle also uses the same Node.js functions.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null, approval is false, and it creates no authority (derives_authority: false). The ref and source_key values from the input are copied as they are.
- The results are facts about whether records get the same key under the declared criterion; they are not a judgement that records are the same transaction or of which one to keep. Nor are they duplicate judgements or bookkeeping done on anyone's behalf.
- It does not include proposing fuzzy duplicate candidates (suggest-dup), bringing aliases together (suggest-alias), merging, deleting, or deciding supersedes.
- It checks only the form of the 3 upper-case letters of a currency and does not compare them with the ISO 4217 list.
- It does not load the G-12 part itself; it holds a copy of the normalization rules inside. The copy was confirmed with 4 known answers computed with sha256sum outside Node.js.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call groupByIdentity({ declaration, records }) in src/identity-rule.js (it does not throw exceptions). declaration is { basis: "source_key" | "composite_key", fields? }, and records is an array of { ref, source_key?, occurred_on?, amount?, counterparty? }. Returns: { ok: true, errors: [], facts } (groups, need_input and so on, with inference: null and advice: null) or { ok: false, errors: [{ code, where }] }. There is no side effect, no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-01HTsugite
Append-only audit log — Audit Ledger Tsugite
The Audit Ledger Tsugite (継手, tsugite) sends only checked audit event envelopes (AuditEventEnvelope) to an append-only backend, checks the receipt the backend returns (AuditAppendReceipt) against the contract's formulas, and only then advances the head of the ledger. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When audit records are sent to an external append-only store, it prevents sending malformed envelopes or envelopes that contain a body, and prevents treating a write as recorded without checking that the store's receipt is correct. It is a check so that an append that cannot be matched is never trusted.
When to use
- You want to record an audit log of who did what to an external append-only backend, in a form that cannot be erased later.
- You do not want to trust the receipt returned by the backend as is, and want to match it against the contract's formulas (chain hash, sequence and digest) before treating the event as recorded.
- You do not want the same event to be appended twice. When resending before a receipt is settled, the append is resolved by a lookup.
- You want appending to stop until a person checks it when a receipt is inconsistent.
When not to use
- You want to store the body of audit events itself. The envelope carries only the contract's fields, and a body mixed into it is rejected.
- You need a transparency log (Merkle inclusion proofs). That is outside the scope of this Tsugite.
- You want to treat a successful append as approval or a decision. A successful append is not approval.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a store you can use right away. No actual backend is included, and which append-only service to connect to has not been decided.
What it checks
- It checks the envelope's type. Envelopes with additional fields, values that differ from the fixed values (const), or violations of the genesis rules are not sent.
- It checks the pinned ledger. Appends to a ledger that is not pinned are not sent.
- It checks the chain (previous_hash) and the sequence. If the previous hash has been tampered with, or the sequence rolls back or skips, it does not send and does not accept.
- It checks the digest. Hashes are computed with the contract's formula (a restricted form of RFC 8785 canonicalization and sha256; for genesis, previous_hash_value is the empty string), and anything that differs from the formula is not accepted.
- It checks the receipt (AuditAppendReceipt). If event_digest is spoofed, the chain hash is wrong, or the ledger, sequence, event_id or target does not match, the receipt is not accepted.
- If a receipt is inconsistent, it stops (HALTED) and does not append until a person checks it.
- A backend failure (a 500 response or a partial write) is treated as UNKNOWN, and the head of the ledger is not advanced.
- It rejects envelopes that contain a body, oversized events, envelopes with a spoofed kind (content-type spoofing), and duplicate appends of the same event.
What it does not do / limits
- Deciding whether records are unalterable is the role of the Kernels (provenance-facts and integrity-facts). This Tsugite only checks before sending and matches the receipt.
- A successful append is not approval (approval: false).
- It does not include an actual backend. Which append-only service to connect to, the port implementation and what to pin (endpoint and receipt format) are undecided and will be settled at a separate gate when connecting.
- It does not handle transparency logs (Merkle inclusion proofs).
- It does not resolve the secret (SecretRef) used for backend authentication. That is the responsibility of the port.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use an audit backend.
How to connect
Contract: erabazu.candidate/certified-tsugite/audit-ledger/v1. Input: pass an AuditEventEnvelope to append(envelope) on createLedgerClient({ cfg, port }). Returns: one of APPENDED_VERIFIED, ALREADY_APPENDED, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the append-only backend (append, and optionally lookup). The pinned endpoint and receipt format, and resolving the SecretRef for authentication, are also on the port side. Side effect: EXTERNAL_IRREVERSIBLE (an external write that cannot be undone).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-02HTsugite
Access log — Audit Ledger Tsugite
The Audit Ledger Tsugite (継手, tsugite) is used to append records of access, that is, of who viewed what, to an append-only backend as audit events (AuditEventEnvelope). It sends only checked envelopes, and advances the head of the ledger only when the returned receipt (AuditAppendReceipt) is matched. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When access records are stacked on an external append-only store, it prevents sending malformed envelopes or envelopes that contain a body, duplicate appends of the same record, a sequence that rolls back or skips, and treating a write as recorded without checking that the store's receipt is correct.
When to use
- You want to append access records (who viewed what), one at a time, to an external append-only backend as audit events.
- You can provide the side that captures an access and turns it into an AuditEventEnvelope (the user's side) yourself, and want to leave only the append and matching of that envelope to this Tsugite.
- You want to match the receipt returned by the backend against the contract's formulas (chain hash, sequence and digest) before treating the record as recorded.
- You do not want the same access record to be appended twice. When resending before a receipt is settled, the append is resolved by a lookup.
- You want appending to stop until a person checks it when a receipt is inconsistent.
When not to use
- You want to detect or collect the accesses themselves. Capturing an access and turning it into an event is the role of the user's side; this Tsugite only carries the append and matches the receipt.
- You want to stack the content that was viewed (the body) in the ledger. The envelope carries only the contract's fields, and a body mixed into it is rejected.
- You want to rewrite or delete records. This Tsugite handles appends only.
- You need a transparency log (Merkle inclusion proofs). That is outside the scope of this Tsugite.
- You want a store you can use right away. No actual backend is included, and which append-only service to connect to has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the envelope's type. Envelopes with additional fields, values that differ from the fixed values (const), violations of the genesis rules, or a spoofed kind (content-type spoofing) are not sent.
- It checks the pinned ledger. Appends to a ledger that is not pinned are not sent.
- It checks the chain (previous_hash) and the sequence. If the previous hash has been tampered with, or the sequence rolls back or skips, it does not send and does not accept.
- If the same event_id with the same content is already appended, it does not send to the backend and returns ALREADY_APPENDED; if the same event_id comes with different content, it returns REFUSED.
- It checks the receipt (AuditAppendReceipt). If event_digest is spoofed, the chain hash is wrong, or the ledger, sequence, event_id or target does not match, the receipt is not accepted.
- If a receipt is inconsistent, it stops (HALTED) and does not append until a person checks it.
- A backend failure (a 500 response or a partial write) is treated as UNKNOWN, and the head of the ledger is not advanced.
- It rejects envelopes that contain a body and events over the envelope size limit (max_envelope_bytes).
What it does not do / limits
- It neither captures accesses nor creates access events. That is the role of the user's side; this Tsugite only carries the append and matches the receipt.
- A successful append is not approval (approval: false).
- It does not include an actual backend. Which append-only service to connect to, the port implementation and what to pin (endpoint and receipt format) are undecided and will be settled at a separate gate when connecting.
- It does not handle transparency logs (Merkle inclusion proofs). It also does not resolve the secret (SecretRef) used for backend authentication; that is the responsibility of the port.
- It is not a part dedicated to H-02. The same single part serves H-01 (append-only audit log), C-04 (append-only ledger) and H-08 (tamper-detection chain), and the tests and inspection are shared.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
How to connect
Contract: erabazu.candidate/certified-tsugite/audit-ledger/v1. Input: pass an AuditEventEnvelope that represents an access record to append(envelope) on createLedgerClient({ cfg, port }). Returns: one of APPENDED_VERIFIED, ALREADY_APPENDED, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: the processing that turns an access into an envelope, and a port to the append-only backend (append, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE (an external write that cannot be undone).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-03HTsugite
Consent record store — Audit & Privacy Tsugite
The Audit & Privacy Tsugite (継手, tsugite) checks requests to record consent grants and withdrawals and to query them (ConsentRecord) against the pinned purposes and scopes, carries them to a consent ledger backing service, and matches the returned receipt against the contract. If anything fails, it does not send and does not accept (fail-closed). The consent ledger itself is not included.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When consent grants and withdrawals are recorded in an external consent ledger, it prevents recording under purposes or scopes that are not pinned, sending the data subject's email address or phone number as is, and mistaking a recorded consent for consent authority. A consent record is a fact, not authority.
When to use
- You want to append the history of consent grants (RECORD_GRANT) and withdrawals (RECORD_WITHDRAWAL) to an external consent ledger backing service.
- You want to check the state of recorded consent with a query (QUERY). A query has the side effect NONE.
- You want to carry the data subject only as a pseudonymous reference, and not send email-address or phone-number forms to the backing service.
- You want to limit consent purposes and scopes to pinned lists (purpose_refs and scope_refs). An empty list permits nothing.
- You do not want to trust the receipt from the backing service as is, and want to match it by request_id, request_digest and record_digest before treating consent as recorded.
When not to use
- You want it to judge whether consent is valid. It makes no legal judgements; that is the role of people, organizations and Kernels.
- You want to use a recorded consent as consent authority. consent_authority is fixed to NOT_DERIVED or to a person or organization; this Tsugite does not derive it.
- You want to delete or rewrite recorded consent. Consent records are append-only, an irreversible external write (EXTERNAL_IRREVERSIBLE). A withdrawal is recorded as RECORD_WITHDRAWAL.
- You want a consent ledger you can use right away. No actual backing service (consent ledger) is included; the port implementation and the pinned lists will be settled at a separate gate when connecting.
- You want to identify personal information comprehensively. Direct-identifier detection covers only typical forms of email addresses and phone numbers.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a part for H-03 alone, or want it to handle other H items. This Tsugite covers H-03, H-04, H-05, H-07 and H-09 as one part under the contract, with shared tests and inspection. H-01 and H-08 are served by the Audit Ledger Tsugite, H-06 by the Storage Lifecycle Tsugite (C-08), and H-02 by the audit ledger or an observability sink.
What it checks
- It checks the data subject reference and the form of references. A subject reference shaped like an email address or phone number, an empty reference, a reference containing a path or .. (path traversal), and additional fields are not sent.
- It checks purposes and scopes. Requests with a purpose or scope that is not pinned are not sent.
- It checks that op and side effect correspond. QUERY is NONE; RECORD_GRANT and RECORD_WITHDRAWAL are EXTERNAL_IRREVERSIBLE. Requests and receipts that do not correspond are not sent or accepted.
- It checks the receipt's state and record_digest. A state that does not answer the op (for RECORD_GRANT, other than GRANTED or UNKNOWN; for RECORD_WITHDRAWAL, other than WITHDRAWN or UNKNOWN) or a record_digest for another consent (target differs from consent_ref) is not accepted.
- A receipt that claims to have derived consent authority is not accepted.
- Receipts that increase the external effect, receipts whose effect is UNKNOWN and receipts with a spoofed request_digest are not accepted. When an unacceptable receipt arrives, it stops (HALTED).
- The same request_id is never sent twice. A backing-service failure is treated as UNKNOWN, and a resend is resolved with a lookup.
What it does not do / limits
- It does not judge whether consent is valid or whether consent authority exists. A consent record is a fact, not authority.
- The facts of required consent, missing consent and approval paths are handled by the Kernel (consent-facts, which does not grant consent), the facts of retention by the Kernel (policy-facts), and legal rights by people and organizations. This Tsugite only checks before sending and after receiving.
- It does not include an actual backing service (consent ledger). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not identify all personal information. Direct-identifier detection is a net that looks only at typical forms of email addresses and phone numbers.
- A recorded consent is not approval (approval: false).
- It does not promise compliance with privacy laws. It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/audit-privacy/v1. Input: pass a ConsentRecord request to submit(request) on createAuditPrivacyClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the backing service (submit, and optionally lookup) and purpose_refs and scope_refs in cfg. Side effect: EXTERNAL_IRREVERSIBLE for records, NONE for QUERY.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-04HTsugite
Data subject access request (DSAR) handling (only carries; does not decide) — Audit & Privacy Tsugite
The Audit & Privacy Tsugite (継手, tsugite) checks a data subject's request (DSAR), carries it to a DSAR intake backing service, and matches the returned receipt against the contract. It only carries the DSAR: it does not decide whether to fulfil it, what to disclose or whether to delete, and carries none of that out. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a DSAR is carried to an external DSAR intake, it prevents sending the data subject's email address or phone number as is, making an irreversible or unknown effect the intent of the transport, and mistaking a carried request for a judgement of legal entitlement.
When to use
- You want to carry a DSAR to a DSAR intake backing service using only a pseudonymous reference to the data subject.
- You want to confirm the transport result only after matching request_id, request_digest and references. The DONE result carries transport_state, dsar_type and legal_entitlement.
- You want to keep the side effect of the transport within NONE to EXTERNAL_REVERSIBLE. An irreversible or unknown effect cannot be the intent.
- Even if the result becomes unknown because of a backing-service failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want it to judge whether there is a right to have the request fulfilled, or what may be disclosed. It makes no legal judgements; that is the role of people, organizations and Kernels.
- You want the intake that fulfils disclosure or deletion requests itself, or a mechanism that carries out disclosure or deletion. This Tsugite only carries the DSAR to the backing service; whether to delete is decided by people and organizations. No actual backing service (DSAR intake) is included either.
- You want to use a carried request as a judgement of legal entitlement. legal_entitlement is fixed to NOT_DERIVED or to a person or organization; this Tsugite does not derive it.
- You want an irreversible effect to be the intent of carrying a DSAR. Such requests are not sent.
- You want to identify personal information comprehensively. Direct-identifier detection covers only typical forms of email addresses and phone numbers.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a part for H-04 alone, or want it to handle other H items. This Tsugite covers H-03, H-04, H-05, H-07 and H-09 as one part under the contract, with shared tests and inspection. H-01 and H-08 are served by the Audit Ledger Tsugite, H-06 by the Storage Lifecycle Tsugite (C-08), and H-02 by the audit ledger or an observability sink.
What it checks
- It checks the data subject reference and the form of references. A subject reference shaped like an email address or phone number, an empty reference, a reference containing a path or .. (path traversal), and additional fields are not sent.
- It checks the DSAR intent. Requests whose intent is an irreversible or unknown effect are not sent.
- A receipt that claims to have judged legal entitlement is not accepted.
- If the receipt's request_id, request_digest or references do not match the request, it is not accepted.
- Receipts that increase the external effect, receipts whose effect is UNKNOWN and receipts with a spoofed request_digest are not accepted. When an unacceptable receipt arrives, it stops (HALTED).
- The same request_id is never sent twice. A backing-service failure is treated as UNKNOWN, and a resend is resolved with a lookup.
What it does not do / limits
- It makes no legal judgements. Whether there is a right to have the request fulfilled, and what may be disclosed, are the role of people, organizations and Kernels. The Kernel (consent-facts) handles the facts of required and missing consent; it does not grant consent.
- It does not decide on disclosure or deletion, and does not carry them out. It only carries the DSAR; whether to delete is decided by people and organizations. Carrying a DSAR is not a judgement of legal entitlement.
- It does not include an actual backing service (DSAR intake). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not identify all personal information. Direct-identifier detection is a net that looks only at typical forms of email addresses and phone numbers.
- A carried request is not approval (approval: false).
- It does not promise compliance with privacy laws. It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/audit-privacy/v1. Input: pass a DSAR request to submit(request) on createAuditPrivacyClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries transport_state, dsar_type and legal_entitlement in its result. Every result has approval: false. User provides: a port to the DSAR intake backing service (submit, and optionally lookup). Side effect: NONE to EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-05HTsugite
PII detection and redaction pipeline — Audit & Privacy Tsugite
The Audit & Privacy Tsugite (継手, tsugite) checks redaction requests (RedactionPipeline) against the pinned redaction definitions, carries them to a redaction backing service, and matches the returned receipt against the contract. It does not do the redaction itself, decide what to redact, or confirm that the redaction is complete. If anything fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When redaction is delegated to an external backing service, it prevents processing with unpinned definitions, accepting a result that overwrote the input or a redaction with an external effect, treating a partially redacted result (PARTIAL) as fully redacted, and mistaking redaction for disclosure authorization.
When to use
- You want to carry redaction processing to the backing service, limited to pinned redaction definitions (redaction_manifests).
- You want to receive the redaction result under a reference separate from the input (output_ref), and not let the input be overwritten.
- You want to distinguish a partially redacted result (PARTIAL) from a fully redacted one. For PARTIAL, fully_redacted is false.
- You want to keep the side effect of redaction to NONE or a locally reversible effect (LOCAL_REVERSIBLE). The DONE result carries redaction_state, fully_redacted, output_ref and disclosure_authorization.
When not to use
- You want it to decide what to redact. That decision is the role of the Kernel (redact-facts).
- You want to check whether the redaction is correct (nothing missed). This Tsugite looks only at the receipt's state and the output reference.
- You want comprehensive detection of sensitive information. Direct-identifier detection is a net that looks only at typical forms of email addresses and phone numbers placed in data subject references; it does not identify all personal information.
- You want to use redaction as disclosure authorization. disclosure_authorization is fixed to NOT_DERIVED or to a person or organization; this Tsugite does not derive it.
- You want a redaction backing service you can use right away. No actual backing service is included; the port implementation and the pinned lists will be settled at a separate gate when connecting.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a part for H-05 alone, or want it to handle other H items. This Tsugite covers H-03, H-04, H-05, H-07 and H-09 as one part under the contract, with shared tests and inspection. H-01 and H-08 are served by the Audit Ledger Tsugite, H-06 by the Storage Lifecycle Tsugite (C-08), and H-02 by the audit ledger or an observability sink.
What it checks
- It checks the redaction definition. Requests with a definition (redaction manifest) that is not pinned are not sent.
- A receipt that overwrote the input (the output has the same reference as the input) is not accepted.
- A redaction receipt with an external effect is not accepted. The side effect of redaction is NONE or LOCAL_REVERSIBLE, and FAILED is NONE.
- A receipt that claims to have derived disclosure authorization is not accepted.
- It does not treat PARTIAL as fully redacted (fully_redacted: false).
- It checks the data subject reference and the form of references. A subject reference shaped like an email address or phone number, an empty reference, a reference containing a path or .. (path traversal), and additional fields are not sent.
- Receipts that increase the external effect, receipts whose effect is UNKNOWN and receipts with a spoofed request_digest are not accepted. When an unacceptable receipt arrives, it stops (HALTED).
- The same request_id is never sent twice. A backing-service failure is treated as UNKNOWN, and a resend is resolved with a lookup.
What it does not do / limits
- It does not do the redaction itself. Redaction processing is the backing service's role; this Tsugite only checks and carries requests and matches receipts. No actual backing service (redaction processing) is included; the port implementation and the pinned lists will be settled at a separate gate when connecting.
- It does not decide what to redact. That is the role of the Kernel (redact-facts).
- It does not check whether the redaction is correct (nothing missed). It looks only at the receipt's state and the output reference.
- It does not identify all personal information. Direct-identifier detection is a net that looks only at typical forms of email addresses and phone numbers.
- Redaction is not disclosure authorization. A completed redaction is not approval either (approval: false).
- It does not promise compliance with privacy laws. It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/audit-privacy/v1. Input: pass a RedactionPipeline request to submit(request) on createAuditPrivacyClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the backing service (submit, and optionally lookup) and redaction_manifests in cfg. Side effect: NONE or LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-06HTsugite
Retention enforcement — Storage Lifecycle Tsugite
The Storage Lifecycle Tsugite (継手, tsugite) checks retention requests (RetentionRequest: EVALUATE, RETAIN or EXPIRE) against the pinned retention policies and logical targets before sending them to the storage backend, and checks the receipt against the contract. Irreversible deletion (EXPIRE) is permitted by the allow_expire setting (default false) and is sent only for targets without a legal hold (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When requests for deletion or preservation at expiry are sent to the backend, it prevents sending deletions that are not permitted or deletions of targets under legal hold, treating an expiry evaluation alone as a deletion, and treating data as deleted without checking the backend's receipt.
When to use
- You want to check retention requests (RetentionRequest) that follow a retention policy and then carry them to the storage backend.
- You want to send deletion (EXPIRE) requests only when the allow_expire setting permits it.
- You want to receive the fact of a legal hold (a Kernel or human decision) and stop sending EXPIRE for that target from then on.
- You want to ask the backend only for an evaluation of whether data has expired (EVALUATE), without deleting.
- You want to treat data as deleted (deleted: true) only when the EXPIRE receipt is matched as EXPIRED.
When not to use
- You want the mechanism itself that deletes or preserves data automatically when it expires. Executing deletion and preservation at expiry is the role of the underlying platform; this Tsugite only carries the retention requests it is given and matches the receipts.
- You want this Tsugite to decide retention periods or legal holds. Those decisions belong to Kernels and people; this Tsugite only receives the facts and keeps to them.
- You want to move data to another tier on expiry. Moving between tiers is an Archive request, the C-09 facet.
- You want a store you can use right away. No actual backend is included, and which storage service to connect to has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- When the allow_expire setting is not true, it does not send EXPIRE.
- It does not send EXPIRE for a target under legal hold.
- An EVALUATE receipt that involves deletion is not accepted. EXPIRED for EVALUATE is only an expiry evaluation, not a deletion.
- A receipt that returns EXPIRED for RETAIN is treated as an anomaly and not accepted.
- Requests for a retention policy that is not in the pinned list are not sent.
- It checks the request's type. Requests with additional fields, values that differ from the fixed values (const), file-system targets, an unpinned backend or a target that is not allowed are not sent.
- It checks the receipt. A spoofed request_digest, a receipt for another target, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- FAILED, REFUSED and SIMULATED receipts are not accepted unless they have no effect (NONE). A backend failure is treated as UNKNOWN, and the same request_id is never sent twice.
What it does not do / limits
- It does not execute deletion or preservation at expiry. Execution is the role of the underlying platform; this Tsugite only carries retention requests and matches the receipts.
- It does not decide retention periods or legal holds. That is the role of the Kernels (redact-facts and policy-facts) and people; this Tsugite only receives those facts and keeps to them.
- A completed deletion is not approval (approval: false).
- It does not include an actual backend. Which storage service to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- It is not a part dedicated to H-06. The same single part serves C-05 to C-09 and F-04, and H-06 is the retention (RetentionRequest) facet of C-08. The tests and inspection are shared.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
How to connect
Contract: erabazu.candidate/certified-tsugite/storage-persistence/v1. Input: pass a RetentionRequest to submit(request) on createStorageLifecycleClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries decision, deleted and effect. Every result has approval: false. User provides: a storage backend port (submit, and optionally lookup) and settings for the retention policies and allow_expire. Side effect: EXTERNAL_IRREVERSIBLE for EXPIRE, NONE otherwise.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-07HTsugite
Legal hold (only carries; neither decides nor freezes) — Audit & Privacy Tsugite
The Audit & Privacy Tsugite (継手, tsugite) checks legal hold (LegalHold) requests to place (PLACE) and release (RELEASE) a hold against the pinned scopes, only when there is a reference to evidence received from a person or organization, then carries them to a legal hold backing service and matches the receipt. It only carries requests: it neither freezes deletion itself nor judges whether a hold is needed. If anything fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a legal hold is carried to an external mechanism, it prevents carrying it without evidence from a person or organization, carrying it on evidence whose origin is not a person or organization or is ERABAZU itself, and mistaking a carried request for legal hold authority.
When to use
- You want to carry place and release requests to the backing service with a reference to legal hold evidence issued by a person or organization. Evidence is received with recordAuthorityEvidence(evidence_ref, origin) (origin is HUMAN or ORGANIZATION only).
- You want to limit the scope of a legal hold to a pinned list (scope_refs).
- You want to treat a request as carried only after matching that the receipt's state answers the op (PLACE→PLACED, RELEASE→RELEASED) or is FAILED. The DONE result carries hold_state and legal_hold_authority.
- Even if the result becomes unknown because of a backing-service failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want it to judge whether a legal hold is needed. It makes no legal judgements; that is the role of people, organizations and Kernels.
- You want to check whether the evidence is a genuine decision. It does not check the content of the evidence, only whether it is a reference received from a person or organization.
- You want to use a carried request as legal hold authority. legal_hold_authority is fixed to NOT_DERIVED or to a person or organization; this Tsugite does not derive it.
- You want the mechanism that freezes deletion itself. This Tsugite only carries requests; no actual backing service (legal hold mechanism) is included, and the port implementation and the pinned lists will be settled at a separate gate when connecting.
- There is no evidence from a person or organization yet. Requests without evidence are not sent (REFUSED).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a part for H-07 alone, or want it to handle other H items. This Tsugite covers H-03, H-04, H-05, H-07 and H-09 as one part under the contract, with shared tests and inspection. H-01 and H-08 are served by the Audit Ledger Tsugite, H-06 by the Storage Lifecycle Tsugite (C-08), and H-02 by the audit ledger or an observability sink.
What it checks
- It checks that evidence exists. Requests without evidence from a person or organization are not sent (REFUSED).
- It checks the origin of the evidence. It does not carry requests on evidence from anything other than a person or organization (HUMAN, ORGANIZATION), or on evidence whose origin is ERABAZU.
- It checks the scope. Requests for a scope that is not pinned are not sent.
- A receipt that claims legal hold authority is not accepted.
- It checks the receipt's state. A state that does not answer the op (none of PLACE→PLACED, RELEASE→RELEASED or FAILED) is not accepted.
- It checks the data subject reference and the form of references. A subject reference shaped like an email address or phone number, an empty reference, a reference containing a path or .. (path traversal), and additional fields are not sent.
- Receipts that increase the external effect, receipts whose effect is UNKNOWN and receipts with a spoofed request_digest are not accepted. When an unacceptable receipt arrives, it stops (HALTED).
- The same request_id is never sent twice. A backing-service failure is treated as UNKNOWN, and a resend is resolved with a lookup.
What it does not do / limits
- It does not judge whether a legal hold is needed. Legal judgements are the role of people, organizations and Kernels.
- It does not create legal hold evidence (ERABAZU does not create evidence). It does not check the content of the evidence (whether it is a genuine decision) either, only whether it is a reference received from a person or organization.
- It does not freeze deletion (preservation) itself. It only carries requests to the backing service and matches the receipt. No actual backing service is included.
- Handling the facts of retention and legal hold policies is the role of the Kernel (policy-facts), and authority is the role of people and organizations. This Tsugite only checks before sending and after receiving.
- A carried request is not legal hold authority and is not approval (approval: false).
- It does not promise compliance with privacy laws. It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/audit-privacy/v1. Input: after receiving the evidence with recordAuthorityEvidence(evidence_ref, origin) on createAuditPrivacyClient({ cfg, port }), pass a LegalHold request to submit(request). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the backing service (submit, and optionally lookup) and scope_refs in cfg. Side effect: EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-08HTsugite
Tamper-detection chain — Audit Ledger Tsugite
The Audit Ledger Tsugite (継手, tsugite) handles a tamper-evident chain that links audit event envelopes (AuditEventEnvelope) by the previous hash (previous_hash). It checks the chain and the digest against the contract's formulas before sending to an append-only backend, and then matches the chain hash in the receipt the backend returns (AuditAppendReceipt). If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
A hash-linked audit chain loses its power to reveal tampering if a previous hash is rewritten somewhere or the digest is computed in a different way. This Tsugite checks both the envelope before sending and the store's receipt with the same contract formulas, and does not send or accept a chain that does not fit them, so a chain that cannot be matched is never trusted.
When to use
- You want to keep audit events on an external append-only backend as a tamper-evident chain linked by the previous hash.
- You want chain hashes computed and matched with one fixed formula (a restricted form of RFC 8785 canonicalization and sha256).
- You do not want to trust the event_digest or chain hash in the store's receipt as is, and want to recompute them from the envelope that was sent and match them before treating the event as recorded.
- You want appending to stop until a person checks it when a chain mismatch is found.
- You want to try only the envelope check and the receipt match without calling a backend. The pure functions checkEnvelope and checkReceipt return { ok, errors }.
When not to use
- You need a transparency log (Merkle inclusion proofs). This Tsugite handles a chain linked by the previous hash; inclusion proofs are out of scope.
- You want to leave the final judgement on whether tampering occurred to this Tsugite. Deciding whether records are unalterable is the role of the Kernels (provenance-facts and integrity-facts).
- You want to use a hash formula other than restricted RFC 8785 canonicalization with sha256. Hashes computed with a different formula are not accepted.
- You want to put the body of audit events itself on the chain. The envelope carries only the contract's fields, and a body mixed into it is rejected.
- You want a store you can use right away. No actual backend is included, and which append-only service to connect to has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the previous hash (previous_hash) against the head of the ledger. An envelope whose previous_hash has been rewritten is not sent and not accepted.
- It recomputes chain hashes with the contract's formula: restricted RFC 8785 canonicalization and sha256. A hash computed over a non-canonicalized form is not accepted.
- It checks the rules for the start of the chain (genesis). For genesis, previous_hash_value is computed as the empty string, and envelopes that violate the genesis rules are not sent.
- It checks the digest. A digest computed over the wrong target (domain) is not accepted.
- It checks the receipt (AuditAppendReceipt). If event_digest is spoofed, the chain hash is wrong, or the ledger, sequence, event_id or target does not match, the receipt is not accepted (RECEIPT_REJECTED).
- If a receipt is inconsistent, it stops (HALTED) and does not append until a person checks it.
- If the sequence rolls back or skips, it does not send and does not accept. Appends that break the order of the chain do not pass.
What it does not do / limits
- Deciding whether records are unalterable, and whether tampering occurred, is the role of the Kernels (provenance-facts and integrity-facts). This Tsugite only checks before sending and matches the receipt.
- It does not handle transparency logs (Merkle inclusion proofs).
- It does not include an actual backend. Which append-only service to connect to, the port implementation and what to pin (endpoint and receipt format) are undecided and will be settled at a separate gate when connecting.
- It does not resolve the secret (SecretRef) used for backend authentication. That is the responsibility of the port.
- A successful append is not approval (approval: false).
- It does not change the contract or the hash formula. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- It is not a part dedicated to H-08. The same single part serves H-01 (append-only audit log), C-04 (append-only ledger) and H-02 (access log), and the tests and inspection are shared.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/audit-ledger/v1. Input: pass an AuditEventEnvelope to append(envelope) on createLedgerClient({ cfg, port }). Returns: one of APPENDED_VERIFIED, ALREADY_APPENDED, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. For checks only, checkEnvelope and checkReceipt return { ok, errors }. User provides: a port to the append-only backend (append, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE (a write that cannot be undone).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-09HTsugite
Regulatory report output (only carries; not regulator acceptance) — Audit & Privacy Tsugite
The Audit & Privacy Tsugite (継手, tsugite) checks regulatory report (RegulatoryReport) requests and the report content (bytes) against the pinned report formats and destinations, the digest and the size limit, then carries them to a regulator submission backing service and matches the receipt. It only carries reports: the user passes the report content, and delivering a report is not acceptance by the regulator. If anything fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a regulatory report is carried to an external submission point, it prevents sending to a format or destination that is not pinned, sending altered or oversized content, making an irreversible submission that is not permitted, and mistaking delivery for acceptance by the regulator.
When to use
- You want to limit report formats and destinations to pinned lists (report_profiles and report_destinations).
- You want to check the digest of the report content (bytes) and the size limit (max_report_bytes) before carrying it. max_report_bytes is required when reports are used.
- You want irreversible submissions only when the allow_irreversible setting is true (default false).
- You want to tell generated (GENERATED) from delivered (DELIVERED) by checking against the side effect. GENERATED is NONE or LOCAL_REVERSIBLE; DELIVERED is EXTERNAL_*. The DONE result carries delivery_state, delivered and regulatory_acceptance.
When not to use
- You want to know whether the regulator accepted the report. A delivered report is not acceptance by the regulator; regulatory_acceptance is fixed to NOT_DERIVED or to a person or organization, and this Tsugite does not derive it.
- You want the receipt to confirm that the report reached the pinned destination. Regulatory report receipts have no destination field; only the pin before sending can be checked.
- You want it to create the report content. The user passes the content as bytes in submit(request, bytes); this Tsugite checks only the digest and the size limit.
- You want it to make legal judgements. It makes no legal judgements; that is the role of people, organizations and Kernels.
- You want a submission point you can use right away. No actual backing service (regulator submission point) is included; the port implementation and the pinned lists will be settled at a separate gate when connecting.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a part for H-09 alone, or want it to handle other H items. This Tsugite covers H-03, H-04, H-05, H-07 and H-09 as one part under the contract, with shared tests and inspection. H-01 and H-08 are served by the Audit Ledger Tsugite, H-06 by the Storage Lifecycle Tsugite (C-08), and H-02 by the audit ledger or an observability sink.
What it checks
- It checks the report format and destination. Requests with a format (report profile) or destination (report destination) that is not pinned are not sent.
- It checks the report content. Content whose bytes do not match the digest, or that exceeds the limit, is not sent.
- It checks irreversible submissions. They are not sent unless allow_irreversible is true.
- It checks that state and side effect correspond. A GENERATED receipt that claims an external effect, or a DELIVERED receipt without an effect, is not accepted.
- A receipt that claims to have derived acceptance by the regulator is not accepted.
- It checks the data subject reference and the form of references. A subject reference shaped like an email address or phone number, an empty reference, a reference containing a path or .. (path traversal), and additional fields are not sent.
- Receipts that increase the external effect, receipts whose effect is UNKNOWN and receipts with a spoofed request_digest are not accepted. When an unacceptable receipt arrives, it stops (HALTED).
- The same request_id is never sent twice. A backing-service failure is treated as UNKNOWN, and a resend is resolved with a lookup.
What it does not do / limits
- It does not derive acceptance by the regulator. Delivery is not acceptance, and it is not approval either (approval: false).
- It does not use the receipt to confirm where the report arrived. Regulatory report receipts have no destination field; it checks only by the pin before sending.
- For the report content, this Tsugite checks only the digest and the size limit. It makes no legal judgements (the role of people, organizations and Kernels).
- Legal rights and disclosure authorization are the role of people and organizations. This Tsugite only checks before sending and after receiving.
- It does not include an actual backing service (regulator submission point). The port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not promise compliance with privacy laws. It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/audit-privacy/v1. Input: pass a RegulatoryReport request and the content bytes to submit(request, bytes) on createAuditPrivacyClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the backing service (submit, and optionally lookup) and cfg (the pinned formats and destinations, max_report_bytes and allow_irreversible). Side effect: NONE to EXTERNAL_*.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-10HPrimitive
Lineage record — Lineage record part (Primitive)
The Lineage record part (Primitive) is a standalone part that creates a lineage record (erabazu.lineage/1) from the set of inputs used in an aggregation (a list of Refs), the reference and version of the aggregator, and the output bytes, and later recomputes from the inputs and output to check against the record. The order of the inputs does not change the result, and duplicates are refused rather than silently removed. Differences found when checking are not errors; they are returned as a list of the fields that differed. It does not run the aggregator.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It provides backing when the origin of a number is questioned later. It keeps which inputs were used, with which version of the aggregator, and which output was produced, as the hash of the input set, the aggregator version and the hash of the output. Because it does not silently remove duplicate inputs, it does not hide a miscount of which inputs were used.
When to use
- You want to create a lineage record from the input set of an aggregation, the aggregator's reference and version, the output bytes and the recording time (recordLineage).
- You want a digest of the input set that does not depend on the order of the inputs. It is the sha256 of the RFC 8785 canonical JSON of the array of inputs sorted in UTF-16 code unit order.
- You want to recompute later from the inputs and output and confirm that they are the same as the record (checkLineage; a difference is matches: false with the mismatched list, and is not an error).
- You want to know which fields differed (input_set.count, input_set.digest, output.digest, output.size).
- You want to call it locally as deterministic processing that has no clock, no random numbers and no outside connection. recorded_at is passed by the caller.
When not to use
- You need proof that the aggregator is correct or that the output is correct. This part does not run the aggregator; the lineage is a record of what was used.
- You want to confirm that it really ran with that version or at that time. The tool's ref and version and recorded_at are recorded exactly as passed, and checking only checks their form and does not compare their values.
- You want to confirm that the Refs in inputs point to inputs that exist, or what they contain. It checks only the form of a Ref; even if the content of an input changes, the digest is the same as long as the Ref is the same.
- You want to stop tampering with the record itself. That is the role of signing (B-12) or the seal (H-11).
- You want to record with a time that has an offset (+09:00 and so on) or a leap second. recorded_at is UTC Z only; anything else stops with INVALID_TIME.
- You handle more than 1,048,576 inputs. It stops with INVALID_INPUTS.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the same string appears in inputs two or more times, it stops with DUPLICATE_INPUT rather than silently removing it (it compares by exact string match; uppercase and lowercase are different inputs).
- It stops with INVALID_INPUTS when inputs is not a plain array, has holes, extra properties or getter elements, has elements that are not Refs, or has more than 1,048,576 items.
- It stops with INVALID_TOOL when tool_ref is not a Ref, or tool_version is not 1 to 200 characters or contains control characters or unpaired surrogates, and with NOT_BYTES when output_bytes is not a Uint8Array.
- It stops with INVALID_TIME when recorded_at is not in RFC 3339 UTC (Z) form, or is a date and time that does not exist (February 30, hour 24, second 60 and so on).
- When the record given to checkLineage has the wrong form (missing or extra fields, a different schema, a negative count, a digest that is not 64 lowercase hex digits and so on), it stops with INVALID_RECORD and the name of the first field found (field).
- It stops with INVALID_ARGUMENT when the argument is not a plain object or has fields other than the fixed ones, and with INSPECTION_FAILED when reading the argument itself raises an exception. It reads each field only once.
What it does not do / limits
- A lineage is a record of "what was used and what was produced"; it is not proof that the aggregator is correct, that the output is correct, or that the record has not been tampered with. matches: true shows only that the inputs and output passed were the same as the record.
- It does not store records. It has no state, no clock and no outside connection.
- It creates a record even with 0 inputs (count 0; the digest is the sha256 of "[]"). It does not refuse 0 inputs.
- The SHA-256 computation itself is left to the Node.js crypto module, and the fuzz oracle uses the same crypto.
- Canonicalization uses a copy of the I-08 part's code (src/canonical.js). The branches inside the copy are not themselves exercised by this part's tests (the copy's tests are on the I-08 side).
- Records and check results are not an approval (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not an implementation of a particular provenance specification.
How to connect
Call recordLineage({ inputs, tool_ref, tool_version, output_bytes, recorded_at }) and checkLineage({ record, inputs, output_bytes }) in src/lineage.js (they do not throw). A record is schema (erabazu.lineage/1), input_set (count, digest), tool (ref, version), output (digest, size) and recorded_at. It returns record, or matches and mismatched; when it stops, it returns { ok: false, errors: [{ code }] }. There are no side effects, no state, no outside connection and no credentials, and there is no port for the caller to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-11HComponent
Seal (append-only signing and daily roots) — Seal (append-only signing and daily roots) part (Component)
The Seal part (Component) is a part that bundles the Merkle tree (the B-11 part (Primitive)), signing with a local key (the B-12 part (Primitive)), the idempotent write Tsugite (C-05) and an optional anchor port (in the shape of H-01). appendSeal checks the chain of earlier seal records passed in by the caller and writes a record that signs the new root including the added files, as a separate object with the next seq. finalizeDay checks the chain of each partition, builds the day's root, writes seal/roots/<date>.json and a signed day record, and anchors it if there is an anchor port. No result is approval or a time authority.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It is a part for backing up that raw data has not been rewritten afterwards. It prevents writing on after a rewrite that was not re-signed or after a record from an untrusted key, trusting a proof or the tree size stated by the caller, overwriting a record, and claiming to have anchored when anchoring did not succeed.
When to use
- For each set of files added to a partition (folder_ref) (name and sha256, 1 to 10000 files), you want to write a signed seal record that follows the chain of earlier records, once, at <folder_ref>/seal/<seq>.json (appendSeal).
- For each day, you want to build the day's root from the partition roots in folder_ref order, and write a file in the shape canonical({ seal_date, root }) and a signed day record (finalizeDay). The day record contains no clock value, so the same chain gives the same bytes.
- You want to anchor the day's root externally through the injected anchor port (the same shape as append of H-01) and keep its receipt at seal/roots/<date>.anchor.json. The anchor port is called only after the root file and the day record have been written.
- You want to check a file's inclusion proof against one signed record (verifyFile), or to build that proof from the chain (proveFile).
- You want to leave signing to a sign port that encloses the private key, without passing the private key to this part. Only the first key in keys signs; the other keys are used only to check earlier records.
- You want to resend a write or anchoring whose result is unknown with the same request, the same bytes and the same envelope (resolvePending).
When not to use
- You want to use it, without an anchor port, as evidence that something "existed at this date and time". Without an anchor port, anchored is false and the record only says "this is what was signed". sealed_at is the value of the injected clock and is not a time authority (trusted time is B-06).
- You want to use the signature as proof that the file content or the fetch is correct, as a business decision, or as approval. The signature only means "the holder of a key in keys signed these bytes".
- You want it to also handle where keys are kept, their storage, rotation and revocation. Which key to trust and until when is not decided in this part; that is outside it.
- You want verifyFile alone to also check the chain. verifyFile checks only the signature and inclusion of one record, and cannot detect a record signed with a trusted key over a wrong tree_size. The chain is checked in appendSeal, finalizeDay and proveFile.
- You want this part to read from storage. This part does not read storage; the caller reads the chain from storage and passes it in. If it is passed a version of a record that is not in storage, it writes a record following it, and from then on that partition stops with SEAL_CHAIN_BROKEN (a person fixes it).
- You want to receive a retry of finalizeDay with an anchor port as a success. The anchor record contains anchor_status, so its content changes, and storage refuses it, ending with WRITE_REFUSED.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed, keys does not have 1 to 16 entries, a key_id or fingerprint is duplicated, or a port function is missing, it stops with INVALID_OPTIONS; when the input to an operation is malformed, with INVALID_INPUT; and when a getter throws an exception, with INSPECTION_FAILED.
- When the chain passed in does not pass the check (canonicalization, fields, schema, folder_ref, seq, duplicate names, tree_size, prev_entry_sha256, the signature by a trusted key, consistency with the previous root, the root recomputed from the files), it writes nothing (SEAL_CHAIN_BROKEN). errors[0] carries folder_ref, at and reason.
- It takes tree_size and the root again from the signed records and the number of files. It does not use consistency or inclusion proofs or the size stated by the caller (RFC 9162 proofs do not bind the size of the tree).
- When the names of the files to add are duplicated or already in the chain, it stops with DUPLICATE_NAME; when the same folder_ref appears twice in finalizeDay, with DUPLICATE_FOLDER; and when a partition exceeds 10000 records or 100000 files, with SEAL_LIMIT_EXCEEDED.
- When the sign port throws or is not ok, it reports SIGN_FAILED; when the result is not Ed25519 with the first key's key_id and fingerprint, or the round-trip check with the first key's public key fails, SIGNER_MISMATCH; and on a fault or malformed result from the B-11 or B-12 port, MERKLE_PORT_FAILED or VERIFY_PORT_FAILED; and it does not write.
- When the result of a write is unknown, it reports WRITE_UNKNOWN; when the Tsugite did not send, WRITE_REFUSED; and when the Tsugite halted or the result is not APPLIED or DEDUP_HIT, WRITE_FAILED. When the result of the anchor port is unknown, it reports ANCHOR_UNKNOWN; when it refused, ANCHOR_REFUSED; and on a halt, a failed receipt match or a malformed result, ANCHOR_FAILED; and it does not set anchored to true. While something is pending, appendSeal and finalizeDay return SEAL_PENDING.
- In verifyFile, when the record cannot be canonicalized, is malformed, or is not signed by a trusted key, it reports ENTRY_UNVERIFIED, and when the proof's tree_size differs from the record or leaf_index is too large, PROOF_NOT_FOR_ENTRY; in proveFile, when the name is not in the chain, NAME_NOT_SEALED.
- When timestamp does not return an existing date-time in RFC 3339 UTC (Z), it reports CLOCK_FAILED; when the previous operation has not finished, BUSY; and calling resolvePending when nothing is pending returns NOTHING_PENDING.
What it does not do / limits
- The Seal part is not a Tsugite. It has no Request or Receipt of its own; writing is caused by the bundled C-05 Tsugite under its own contract, and anchoring by the injected port under that port's contract (appending to a ledger and a Git push are effects that cannot be undone). This part itself does not touch the outside.
- The meaning of the bundled parts (the root is correct ≠ the content is correct, signature ≠ approval, anchored ≠ stamped with an authoritative time) stays on each part's side.
- No result is approval, authority or a time authority (approval: false, authority: NOT_DERIVED, timestamp_authority: NOT_DERIVED, derives_authority: false).
- The strength of the external anchoring goes only as far as the strength of the injected anchor port and what lies beyond it. This part only records that the port said it anchored, and the receipt; it does not check the anchor target itself.
- It does not send overwrite or delete requests. It does not see the private key (it stays inside the sign port).
- The unit tests and fuzzing use stand-ins (the signing stand-in in fuzzing is not Ed25519 but a SHA-512 stand-in, for speed). The wiring with the real B-11, B-12, C-05 and H-01 was checked by a separate wiring check. For the Git anchor, only the success path was checked in the wiring; the refusal, conflict and non-fast-forward paths have not been tried.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for a particular transparency log or signing product.
How to connect
Create it with createSeal(options) in src/seal.js; it returns { ok, errors, instance }. options are target, request_prefix, keys, ports and an optional timestamp. The operations are appendSeal, finalizeDay, verifyFile, proveFile, resolvePending and status, and the results carry approval: false. Ports: merkle (the 5 functions of B-11), sign (a closure that encloses the private key), verify and publicKeyFingerprint (B-12), write.submit (the client of the C-05 Tsugite) and an optional anchor.append. Credentials are held by the ports.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
H-12HTsugite
Anchoring seal roots in Git — Seal Root Git Anchor Tsugite
The Seal Root Git Anchor Tsugite (継手, tsugite) checks a request (SealAnchorRequest) to commit the daily seal root file (seal/roots/YYYY-MM-DD.json) to a fixed Git branch as a child of the previous version (parent_commit) and push it as a fast-forward, carries it to the port, and matches the returned receipt (SealAnchorReceipt). It only appends; when a check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It makes it possible to check from the outside "as of when the root was fixed". In doing so, it prevents overwriting a root, anchoring a different root for the same date, committing twice while the result of a push is not visible, and mistaking the commit date for a time authority.
When to use
- You want to anchor the daily root file written by the seal part in a Git repository, by appending only.
- Before sending, you want to rebuild the file content canonical({seal_date, root}) from the request's root_digest and confirm that its sha256 is the same as file_digest.
- You want to keep one root per date. For a date in conflict (CONFLICT), nothing is sent until a person decides.
- When the result of a push is not visible, you want to resolve it by reading the other side (the head of the branch and the file at the path) before sending again (port.inspect with SealInspectRequest / SealInspectReceipt).
When not to use
- You want to create or verify the root itself (the Merkle computation and the signature). That is the role of the seal part; this Tsugite only rebuilds the file digest from the root digest and compares it.
- You want to stamp with a trusted time. Both the commit date and the host's push time are observations. A trusted time is stamped separately with the B-06 (RFC 3161) Tsugite.
- You want to rewrite history (overwrite, force-push, amend or delete). Neither the request nor the settings have such fields.
- You want a template for using the API of a specific Git hosting service (GitHub and the like) or git commands. This Tsugite is not such a template.
- You want an anchoring target you can use right away. No commit or push to an actual Git repository or remote has been made (0 times); the port implementation, the credentials and the branch protection settings will be decided at a separate gate when connecting.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the settings and the request. repository, branch and committer must be in the setting lists (repositories, branches and committer_refs). Only a branch_ref that conforms to git check-ref-format is allowed, and a seal_date that does not exist in the calendar is not sent. An empty list allows nothing.
- It compares the file digest. It does not send a request whose rebuilt sha256 differs from file_digest. It does not accept a receipt whose committed_file_digest differs, a receipt whose ANCHORED parent_commit differs from the request, or a receipt whose changed_paths is not exactly [path].
- It keeps the anchoring append-only. It does not accept a receipt whose history_rewrite is not NOT_PERFORMED. If the settings contain an unknown key (such as force_push), it sends nothing.
- It matches the state of each date. It does not send a different root for a date it knows. It does not accept a second ANCHORED for a date it knows, or an ALREADY_ANCHORED with a root different from the one it knows, and it stops (HALTED).
- It matches the length of the commit ID. It accepts a commit_id only when it has the same number of hex digits as the request's parent_commit (40 or 64, depending on the repository format).
- It does not accept a receipt that escalates the effect, a receipt other than FAILED whose effect is unknown, a receipt outside the state table, or a receipt for a different request, and it stops. The effect of ANCHORED is EXTERNAL_IRREVERSIBLE; for the others it is NONE (for FAILED, NONE or UNKNOWN).
- A push whose result is not visible (a port failure, or a FAILED or UNKNOWN receipt) is treated as UNKNOWN (unresolved), and is treated neither as a success nor as a confirmed failure. When it cannot read the other side, it does not send again, and it sends no other request until the push is resolved.
- Push credentials are only references (committer_ref). A configuration in which committer_refs contains a reference to a seal signing key (seal_signing_key_refs) is rejected as an error.
What it does not do / limits
- It does not check whether the commit content really has that parent and that change, whether the push was a fast-forward, or whether an answer it read reflects the real Git state (these are what the port reports; this Tsugite does not read Git objects).
- The state of each date exists only inside this Tsugite and is lost on restart. It cannot detect an overwrite of a date this Tsugite does not know. The ban on overwrites and force-pushes is enforced separately by branch protection on the repository.
- It does not check whether the commit date or host_time is correct, nor their order. Neither is a time authority (commit_date_authority and timestamp_authority are NOT_DERIVED).
- It does not check whether the root itself is correct. Resolving a CONFLICT is a decision for a person.
- Having anchored a root is not approval, not a stamp with an authoritative time, and does not mean the root is correct (every result carries approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/seal-anchor-git/v1 (SealAnchorRequest / SealAnchorReceipt, with SealInspectRequest / SealInspectReceipt for reading). Input: pass a SealAnchorRequest to submit(request) on createSealAnchorClient({ cfg, port, clock }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; every result has approval: false. User provides: a Git port (submit, and optionally lookup and inspect) and the lists in cfg. Side effect: EXTERNAL_IRREVERSIBLE for ANCHORED only.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-01IPrimitive
Input schema validation — Input schema validation part (Primitive)
The Input schema validation part (Primitive) is a standalone part that checks a JSON value not against the whole of JSON Schema 2020-12 but against a fixed subset (profile json-schema-subset/v1). Only 28 fixed keywords can be used, and any other keyword (including format) causes the whole schema to be refused (it is not silently skipped). The result is { ok, errors }, and when it stops it returns the code and position (JSON Pointer) of the first error only (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When checking that input has a valid shape, it prevents unknown keywords, external $refs and broken values from being silently skipped and treated as "passed". It fixes the keywords that can be used and refuses the whole schema for anything outside the subset.
When to use
- You want to check the shape of a JSON value (type, required, properties, additionalProperties, lengths, ranges, pattern, enum, const and so on) with a fixed subset of keywords (validate).
- You want to confirm first, without an instance, that the schema itself fits within the subset (checkSchema).
- You want to receive the reason it stopped as the first error, with its code, instance_path and schema_path (JSON Pointers), in a fixed order.
- You want the check to be done on a copy read once from the schema and the instance, without calling getters (the content does not change midway).
- You want to call it locally, as processing that does not depend on the clock, random numbers or the environment's locale and that has no outside boundary and no state. validate and checkSchema do not throw an exception.
When not to use
- You need the whole of JSON Schema 2020-12. Only the 28 fixed keywords can be used, and format is refused without being checked.
- You want to check the current actual contract schemas (the 17 from 00a) as they are. All 17 use things outside the subset (format, runtime_validator_contract, uniqueItems, hash_computation, external $refs), so each whole schema is refused.
- You need a $ref that uses an external document, a base URI from $id, $anchor or $dynamicRef. $ref is only "#" and "#/$defs/<name>".
- You use a pattern from an untrusted schema as it is. It does not stop regular-expression blowup (ReDoS), and the limit on the number of evaluations does not count the time spent inside a regular expression.
- You need a limit on the length of strings and arrays or on the size of const and enum comparisons. It has no limit; a limit is decided by the I-06 part or by the caller.
- You want integers of 2^53 or more to be treated as integer, or strings that look the same to be treated as the same. integer means safe integers only, and it does no Unicode normalization.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED; the Unicode version used by the RegExp u flag depends on the engine).
What it checks
- When a keyword outside the subset (including format) appears anywhere (even inside an unused $defs), or when $schema, $id or $defs appears somewhere other than the root, it stops the whole schema with UNSUPPORTED_KEYWORD.
- When $schema is not 2020-12 it stops with UNSUPPORTED_DIALECT, when a $ref has the wrong form (an external document, an anchor, or % or ~ notation) with UNSUPPORTED_REF, and when the name is not in $defs with UNRESOLVED_REF.
- When there is a $ref that loops without advancing through the instance, it stops with REF_CYCLE whether or not the loop is reached.
- When a keyword's value has the wrong shape (an empty enum, duplicate entries in required or type, a negative number, items in array form, then without if) it stops with INVALID_SCHEMA, and when a pattern cannot be compiled with INVALID_PATTERN.
- When the schema is not a JSON value (including getters) it stops with SCHEMA_NOT_JSON, and when the instance is not a JSON value (undefined, a function, NaN, a bigint, a Date, an array with holes, a symbol key, a getter and so on) with INSTANCE_NOT_JSON.
- When nesting exceeds 256 levels it stops with SCHEMA_TOO_DEEP or INSTANCE_TOO_DEEP, when the evaluation depth reaches 512 with EVALUATION_TOO_DEEP, and when the number of evaluations exceeds 1,000,000 with EVALUATION_LIMIT. Even when a limit is reached inside not, anyOf, oneOf or if, it does not turn into "does not match".
- When the instance does not match the schema, it stops with a fixed code for each keyword, such as TYPE_MISMATCH, REQUIRED_MISSING, ADDITIONAL_PROPERTY, PATTERN_MISMATCH or ONE_OF_MANY_MATCHED. Integers of 2^53 or more do not match integer.
- When inspecting a value itself raises an exception (a Proxy trap and so on), it returns INSPECTION_FAILED and does not throw. Even if a Proxy changes its answers, the result does not change midway, because the check is done on a copy read once.
What it does not do / limits
- It handles only a subset of JSON Schema, not the whole (the 28 fixed keywords). format is refused without being checked, and $refs to external documents, $anchor and $dynamicRef are refused too.
- The actual contract schemas (the 17 from 00a) are all refused as outside the subset. The subset has not been silently widened; whether to widen the subset or to bring the contracts into line with it is a separate decision.
- It has not been run against the official JSON-Schema-Test-Suite. The known answers are the ones written in this part's own tests.
- It does not stop regular-expression blowup (ReDoS) in pattern. Patterns from untrusted schemas are to be restricted by the caller.
- Matching the schema is a shape check. It does not mean that the value's content is correct, that it may be accepted, or that it is permitted (judging meaning is for the Kernel, and approval is for people; result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular JSON Schema library.
How to connect
Call validate(schema, instance) in src/schema-validate.js (it does not throw). checkSchema(schema) checks only the schema (it does not throw), and assertValid throws SchemaValidateError when there is no match. Input: a schema that uses only the fixed keywords, and an instance that is a JSON value. Returns: { ok: true, errors: [] } or { ok: false, errors: [{ code, instance_path, schema_path }] } (the first error only; code is from error_taxonomy). It does not fetch external $refs. Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing. The only dependencies are Node.js built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-02IPrimitive
Output encoding — Output encoding part (Primitive)
The Output encoding part (Primitive) is a standalone part that turns a string into a form that is read as characters within one specified output destination (context). There are only 4 contexts: html_text, html_attribute_quoted, url_component and json_in_html_script. A context that is not defined, a value that is not a string, a lone surrogate or input that is too long is stopped with a fixed code (fail-closed). It is not HTML sanitization.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a string is written out into HTML, a URL or a <script>, it prevents encoding by guesswork without deciding the context, and silently replacing a broken string (a lone surrogate). The context must be stated explicitly by name, and there is no default context.
When to use
- You want to put a string into the content (text) of an HTML element (html_text; it turns & < > " ' into entity references).
- You want to put a string into an HTML attribute value enclosed in double quotes (html_attribute_quoted; in addition to the 5 characters of html_text, it also encodes `).
- You want to put a string into one component of a URL (one path segment, or a query name or value) (url_component; everything other than RFC 3986 unreserved characters becomes %XX).
- You want to place a string as a JSON string literal inside an HTML <script> element (json_in_html_script; < > & U+2028 U+2029 are written in \u form, so </script> or <!-- cannot close the element).
- You want to call it locally, as processing that does not depend on the clock, random numbers or the environment's locale and that has no outside boundary and no state. encode does not throw an exception, and reversing the encoding gives back the original text.
When not to use
- You want to sanitize HTML input while keeping tags and attributes. This part is not HTML sanitization; it writes everything as characters.
- You want to neutralize the meaning of values in unquoted attributes, event attributes (onclick and so on), style attributes or attributes that take URLs (javascript: in href or src and so on). html_attribute_quoted does not prevent that.
- You need a check of a whole URL (allowing schemes, checking hosts). url_component is for one component of a URL.
- You write into an HTML attribute, into a JavaScript string or template literal, or into a raw-text element such as style. json_in_html_script is for the inside of a <script> element.
- You write into contexts such as CSS, JavaScript identifiers, XML, SQL, shell or headers. These are not handled.
- You handle strings longer than 1,048,576 UTF-16 code units. It stops them with INPUT_TOO_LARGE.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When context is not one of the 4 defined ones (including differences in case or whitespace, Object.prototype names and values that are not strings), it stops with UNKNOWN_CONTEXT.
- When text is not a primitive string (a number, null, undefined, a String object, an array, a Symbol, a bigint, a Proxy), it stops with NOT_A_STRING.
- When text is longer than 1,048,576 UTF-16 code units, it stops with INPUT_TOO_LARGE.
- When text contains a lone surrogate (an unpaired U+D800 to U+DFFF, including a reversed pair or a lone one at the end), it stops with INVALID_UNICODE in every context. It does not replace it with U+FFFD.
- The order of stopping is context → whether it is a string → length → lone surrogate, and only the first one hit is returned.
What it does not do / limits
- The item's name refers to "injection prevention", but this part is only encoding that writes characters as characters within one specified context; it does not include HTML sanitization (processing that keeps allowed tags).
- It does not decide which value goes into which context (assembling templates). That stays with the caller or with another part.
- Encoding for one context does not make the string safe in another context.
- How browsers actually parse the output has not been checked on real browsers. The rules were derived from the HTML, URL and JSON specifications and checked with Node.js v24.21.0 built-in functions and an independent oracle.
- It does no Unicode normalization. Even if two strings look the same, different code units give different output.
- Being able to encode a string does not mean that it may be put into another context, that it has been made harmless as HTML, or that its content may be accepted (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular template engine or escaping library.
How to connect
Call encode(text, context) in src/encode.js (it does not throw). Input: a primitive string text (up to 1,048,576 UTF-16 code units) and a context that is one of html_text, html_attribute_quoted, url_component or json_in_html_script. Returns: { ok: true, errors: [], text } (the encoded string) or { ok: false, errors: [{ code }] } (code is from error_taxonomy; there is no text). Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing. The only dependencies are Node.js built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-03ITsugite
Malware scanning — Malware Scan Receipt Tsugite
The Malware Scan Receipt Tsugite (継手, tsugite) submits uploaded attachments and similar files to a pinned scanner, and checks the returned scan receipt (ScanReceipt) against the target digest, the pinned engine and version, the scan time window, the vocabulary and the contract's const. The scan itself is done by an external engine, and this Tsugite does not include a scan engine. Whatever the result, it keeps quarantine and never says the file is safe (NOT_DETECTED ≠ SAFE; fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
In upload quarantine, it prevents spoofed, stale or swapped scan results from passing, and prevents mistaking "not detected" for "safe" and lifting quarantine. Even with no detection it derives no safety (safety: NOT_DERIVED_FROM_SCAN), and lifting quarantine is left to a Policy or human decision.
When to use
- You want an external scan engine to scan uploaded attachments and similar files, and want to check the result before handling them.
- You want to match scan receipts against the target digest, the pinned scanner, engine and version, and the time window since submission, so that spoofed, stale or swapped results do not pass.
- You want to keep quarantine regardless of the scan result and leave lifting it to a Policy or human decision.
- You do not want scan results with no verdict or an unknown verdict to be treated as clean.
When not to use
- You want the malware-scanning engine itself. This Tsugite does not include a scan engine. Scanning is done by an external engine, and which engine to connect to has not been decided.
- You want to use "not detected" as "safe". Even for NOT_DETECTED it does not call the file safe (safety: NOT_DERIVED_FROM_SCAN) and keeps quarantine.
- You want this Tsugite to lift quarantine. Lifting it is a Policy or human decision, and this Tsugite does not handle it.
- You need moderation (I-04). That is a separate part.
- You want to treat scan results as grounds for approval, trust or authorization. No result is approval, and no trust or authorization is derived (NOT_DERIVED).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the submission. Content types that are not allowed, files over the size limit, and tampered content (bytes that do not match the digest) are not submitted (REFUSED).
- It checks the scanner and engine. A scanner that is not pinned is not accepted, and a receipt from an engine or version not in the pinned list is not accepted. If a receipt comes from an engine that is not pinned, it stops from then on (HALTED).
- It matches the receipt's target. A receipt with a spoofed request_digest, or one with a swapped target digest, is not accepted, and it stops from then on (HALTED).
- It checks the time window. A receipt dated before the submission, or one arriving after max_scan_seconds from submission, is treated as stale and not accepted.
- It checks vocabulary and evidence. A receipt with an unknown verdict, DETECTED without evidence, or a claim of safe, trusted or authorized is not accepted.
- INCONCLUSIVE and UNKNOWN verdicts become SCAN_INCONCLUSIVE and are not treated as clean.
- A scanner failure is treated as UNKNOWN. The same submission is not sent twice, and a resend is resolved by a lookup.
What it does not do / limits
- It does not scan for malware. It includes no scan engine; the scan itself is done by an external engine. The actual scan engine is undecided, and the port implementation and what to pin (engine, version, receipt format) will be settled at a separate gate when connecting.
- It does not organize the case after a detection. That is the role of the Kernel case-support. Lifting quarantine is a Policy or human decision.
- It does not derive safety. Every result carries safety: NOT_DERIVED_FROM_SCAN, quarantine: KEEP_UNTIL_POLICY_DECISION and approval: false.
- It does not lift quarantine. Lifting it is a Policy or human decision.
- It does not handle moderation (I-04). That is a separate part.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use antivirus software.
How to connect
Contract: erabazu.candidate/certified-tsugite/request-receipt-group/v1. Input: pass a ScanSubmitRequest and the bytes to scan(ScanSubmitRequest, bytes) on createScanClient({ cfg, port }). Returns: one of DETECTED_QUARANTINE_KEPT, NOT_DETECTED_QUARANTINE_KEPT, SCAN_INCONCLUSIVE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the external scan engine (submit, and optionally lookup). Side effect: EXTERNAL_REVERSIBLE (an external submission).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-04ITsugite
Content moderation — Content Moderation Tsugite
The Content Moderation Tsugite (継手, tsugite) checks a request (ModerationRequest) asking an external moderation provider or AI to observe one piece of content (a post, an image and so on), carries it, and matches the returned receipt (ModerationReceipt) against the contract. The labels it returns are observations only; it does not derive removal, stopping, publication, a human judgement or approval. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Handing content to an external provider is an irreversible disclosure (EXTERNAL_IRREVERSIBLE). It prevents handing over bytes that have not been checked, handing content to a provider or content type that the disclosure basis does not cover, and mistaking a returned label for removal, a refusal to publish or a human judgement.
When to use
- You want an external moderation provider or AI to look at content such as a post or an image, and to receive its observation (labels) as material for a decision.
- You want to pin the provider, the policy version and the model version in the configuration, and not have content observed with versions outside what is pinned. Receipts with a mismatched version are not accepted either.
- You want content to be handed out only when the disclosure basis (disclosure_basis_ref) covers that provider and content_type.
- You want to confirm that the bytes of the content being sent are the same as the content_digest stated in the request before handing them over.
When not to use
- You want removal, unpublishing or other enforcement actions to be taken automatically from the observation result. The result of this Tsugite is an observation of facts, not an enforcement action (enforcement_action is NOT_DERIVED).
- You want to run a model locally to observe content. This Tsugite asks an external provider or AI; local observation is within the scope of a separate Primitive.
- The content must not be handed outside. Sending it is an irreversible disclosure (EXTERNAL_IRREVERSIBLE), and even for a request whose result is unknown (UNKNOWN), the disclosure may already have happened.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a connection to a provider that is ready to use. It has never connected to an actual provider or AI; the port implementation, credentials and configuration values will be settled at a separate gate when connecting.
What it checks
- It checks what the configuration pins. If there is no configuration, a list is empty or a table is missing, or the provider, policy, policy version, model or model version is outside what is pinned, or the model belongs to another provider, it does not send.
- It checks the disclosure basis. With a basis that is not in the configuration, a basis that does not cover that provider or content_type, or a basis that is not in Ref form, it does not send.
- It checks the content bytes. If there are no bytes, their sha256 differs from content_digest, or they were changed after being checked, it does not send. A request that carries the content itself is also refused.
- It checks the form of references and of the request. References that are empty, are absolute drive paths, contain .., start with /, or contain control characters or U+2028/2029, a request_id that is not a Ref, a content_type that is not a media type, and additional fields are not sent. Malformed references are not let through even if the configuration pins them.
- It matches the receipt. A spoofed request_digest, a receipt for a different request or a different provider, a copied content_digest that differs, a receipt that is not CONTENT_MISMATCH even though the digest of the received content differs, and a mismatched policy or model version are not accepted.
- It checks the form of labels. A verdict_state or level outside the vocabulary, OBSERVED without labels, a receipt that has labels but is not OBSERVED, overlapping categories, and a score_bp that is not an integer from 0 to 10000 are not accepted.
- Receipts that derive an enforcement action, a human judgement or a publication decision (where enforcement_action, human_judgement or publication_decision is not NOT_DERIVED), and receipts with an effect the state does not allow or an UNKNOWN effect, are not accepted, and it stops (HALTED).
- A request whose result is unknown is resolved by resending the same request (lookup), and no other requests are sent until it is resolved. A different request with the same request_id, swapping in a different request for an unresolved one, and a request changed while being sent are not sent.
What it does not do / limits
- It takes no moderation action. This Tsugite carries requests for observation; it does not include removal, unpublishing or judging violations. The result is an observation of facts, and every result carries NOT_DERIVED for enforcement_action, human_judgement and publication_decision, and approval: false.
- It does not check whether labels are correct, whether they fit the policy, or what score_bp means. The content of the observation is the provider's answer. Judging something a violation is the role of a separate human judgement and of Kernels.
- It does not create the disclosure basis (records of consent or approval), and does not check whether it is valid, within its term or not withdrawn. It only looks at whether it is in the configuration. The basis is the role of people and organizations outside the part (APPROVAL_OUTSIDE_PART).
- The bytes the provider actually received can be confirmed only when the provider returns received_content_digest. If it does not, confirmation extends only to handing the checked bytes to the port.
- It checks only the form of the time (observed_timestamp), not whether a receipt is old or reused. The matching records and unresolved requests exist only inside this Tsugite and are lost on restart.
- It holds no credentials for authenticating with the provider; the port holds them (HELD_BY_PORT). It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for using any specific moderation API.
How to connect
Contract: erabazu.candidate/certified-tsugite/content-moderation/v1. Input: pass a ModerationRequest and the content bytes to submit(request, bytes) on createModerationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries verdict_state, labels and other fields in its result. Every result has approval: false. cfg needs four pinned lists (allowed_providers and others). User provides: a port to the provider (submit, and optionally lookup) and credentials. Side effect: EXTERNAL_IRREVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-05IPrimitive
File type validation — File type validation part (Primitive)
The File type validation part (Primitive) is a standalone part that identifies the type and MIME from the magic bytes at the start of bytes by comparing them with a versioned table (7 types: pdf, png, jpeg, gif, zip, gzip, webp), and compares the result with the declared MIME and the list of allowed MIMEs. It accepts bytes only. An unidentifiable type, a mismatch with the declaration or a type that is not allowed is stopped with a fixed code (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents accepting a file whose real content is different because a disguised extension or a declared Content-Type was trusted. It compares the type identified from the bytes with the declaration, makes the allowed list mandatory, and also stops the configuration mistake of writing a MIME that is not in the table into that list.
When to use
- You want to identify the type and MIME of a received file from the magic bytes at the start of its bytes (detect).
- You want to accept a file only when the declared MIME (declared_mime) exactly matches the real content of the bytes and is in the list of allowed MIMEs (allowed_mimes) (check).
- You want the check to take the same fixed effort even for large files. It reads at most the first 12 bytes.
- You need a way of calling it that does not throw. check returns { ok, errors, detected }, does not throw an exception, and does not call getters on the options.
- You want to call it locally, as processing that does not depend on the clock, random numbers or the environment's locale and that has no outside boundary and no state.
When not to use
- You want to check whether the content is harmful (malware, JavaScript in a malicious PDF, a zip bomb and so on). That is the scope of I-03.
- You want to confirm that the rest of the file has the correct form (that it is not broken, that it can be opened). It identifies only the magic bytes at the start.
- You want to identify the inner format of formats built on zip, such as docx or xlsx. It identifies them only as application/zip, and declaring the MIME of the inner format results in TYPE_MISMATCH.
- You want to handle types other than the 7 in the table (text, json, svg, mp4, webm, tiff, bmp, heic, 7z, rar and so on). It stops them with UNKNOWN_TYPE.
- You want this part to normalize uppercase letters, parameters (; charset= and so on) or aliases (image/jpg and so on) in the declared MIME. It does no such normalization, so the caller removes them and lowercases the MIME before passing it.
- You need a size limit on the bytes. It has no limit; a size limit is decided by the I-06 part or by the caller.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the input is not bytes (Uint8Array), such as a string, an array of numbers, an ArrayBuffer or another typed array, check stops with NOT_BYTES (detect throws FileTypeError).
- When the bytes match the magic bytes of no type in the table (a truncated header, a 1-byte difference, GIF88a, lowercase %pdf-, an empty zip archive or a split-archive marker, another gzip method, RIFF WAVE, a shifted position), it stops with UNKNOWN_TYPE.
- When the identified MIME does not exactly match declared_mime (an alias, a parameter, uppercase, an Office MIME declared for a zip), it stops with TYPE_MISMATCH.
- When the identified MIME is not in allowed_mimes, it stops with NOT_ALLOWED.
- When allowed_mimes is missing, empty, has holes, is an array-like object, or contains a MIME that is not in the table, it stops with INVALID_OPTIONS.
- When declared_mime is not a string, when opts has an unknown key (including a symbol) or a getter, or when opts is a Proxy trap or a class instance, it stops with INVALID_OPTIONS and does not throw.
- When it stops, there is always exactly one error, and the order of checks is bytes, options, whether the type can be identified, the declaration, and the allowed list.
What it does not do / limits
- Identifying the type is not a check of whether the content is safe. Malware scanning (I-03), size limits (I-06) and checks of the inner structure (such as decoding images) are separate parts, and combining them is up to the caller.
- Matching magic bytes is not proof that the rest of the file has the correct form. It does not read the PDF xref, PNG chunks, the zip central directory and so on.
- The magic-bytes copies inside the Tsugite (2 content.js files) have not yet been replaced with this part. The pdf, png and jpeg checks were compared once in the workspace and showed no difference, but the copies differ in that they also handle text/plain and application/json and accept only a Buffer, and any replacement is separate work.
- It does not identify the inner format of formats built on zip, or empty or split zips.
- Identifying the type does not mean that the file may be accepted (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a copy of, or a template for how to use, a particular file type detection library (file-type, libmagic and so on).
How to connect
Call check(bytes, opts) in src/filetype.js (it does not throw). Input: bytes (a Uint8Array, including a Buffer) and a plain object opts with only allowed_mimes (required) and declared_mime (optional). Returns: { ok: true, errors: [], detected: { type, mime } } or { ok: false, errors: [{ code }], detected } (code is from error_taxonomy). detect(bytes) returns { type, mime } or null (it throws if given something that is not bytes). Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing. The only dependencies are Node.js built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-06IPrimitive
Size and quantity limits — Size and quantity limit part (Primitive)
The Size and quantity limit part (Primitive) is a standalone part that counts the size of a value in one of 6 fixed units (bytes, utf8_bytes, utf16_units, code_points, items, keys) and compares it with an upper and a lower limit. Each unit accepts a fixed type of value, and a value that does not fit is stopped. The upper limit is required; exceeding it stops with TOO_LARGE, and falling short stops with TOO_SMALL (fail-closed). The caller decides the unit and passes it in.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
In the upper-limit check that refuses oversized input, it prevents counting a value that cannot be counted as 0, counting a string as bytes or an array-like object as an array, silently counting the UTF-8 length of a string that contains a lone surrogate, and accepting a range with no upper limit or an empty range.
When to use
- You want to apply a unit and an upper limit decided by the caller to the size of a body, a name, an array or an object (for example, 1 MiB in utf8_bytes for a body and 64 in code_points for a name).
- You want to handle, by stating the unit explicitly, the fact that the same string counts differently depending on the unit (one emoji is 4 in utf8_bytes, 2 in utf16_units and 1 in code_points).
- You want to decide where the boundaries of the upper and lower limits fall. By default the upper limit is inclusive (max_inclusive), and the lower limit defaults to 0 and is always inclusive.
- You want to apply a size limit before passing a value to the I-08 canonicalization, B-07 hashing or I-05 file type parts. Those parts have no size limit.
- You need a way of calling it that does not throw. check does not throw an exception, does not modify the value or the options, and does not call getters.
- You want to call it locally, as processing that does not depend on the clock, random numbers or the environment's locale and that has no state.
When not to use
- You want to limit by the number of visible characters (grapheme clusters, such as 👨👩👧 or 🇯🇵). It does not count grapheme clusters.
- You want strings that look the same to have the same length. It does no Unicode normalization, so é and e plus a combining character count differently.
- You want to limit by size including nested contents (arrays inside arrays, keys inside objects). items and keys count only the top level.
- You want to stop oversized input before the value is read into memory. This part counts values that are already in memory.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the size exceeds the upper limit (or equals it, if max_inclusive is false) it stops with TOO_LARGE, and when it is below the lower limit with TOO_SMALL, and it returns the counted size (size).
- When the type of the value does not fit the unit (for bytes, a string, an ArrayBuffer or another typed array; for string units, bytes or a String object; for items, an array-like object or a Set; for keys, an array, a Map, a Date, a class instance and so on), it stops with VALUE_TYPE_MISMATCH.
- For utf8_bytes, when the string contains a lone surrogate (an unpaired U+D800 to U+DFFF), it stops with INVALID_UNICODE.
- When unit is none of the 6 units (including uppercase and aliases), it stops with UNKNOWN_UNIT.
- When max is missing, when max or min is not a safe integer of 0 or more, when min > max, or when the range admits no size at all, it stops with INVALID_LIMITS.
- When opts is not a plain object, has an unknown key or a getter, or max_inclusive is not a boolean, it stops with INVALID_OPTIONS, and when inspecting the value itself raises an exception (a Proxy trap and so on) with INSPECTION_FAILED.
What it does not do / limits
- Of the "blocking oversized input" in its name, it cannot stop the reading of an oversized value itself. The work of loading a value into memory before passing it is outside this part.
- It does not decide which unit to use or what the limit is. The caller decides (the policy of each Tsugite, contract or Kernel).
- It does not handle grapheme counts, Unicode normalization or the size of nested contents.
- The size-limit checks inside the Tsugite and the I-06 records in the workshop ledger have not yet been replaced with this part. Any replacement is separate work. For a string with a lone surrogate, utf8_bytes differs from Buffer.byteLength and stops.
- Proxy traps were checked with only a few unit tests (they are not included in the fuzzing).
- Being within the range does not mean that the value's content is correct or that it may be accepted (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular validation library.
How to connect
Call check(value, opts) in src/size.js (it does not throw). Input: a value and a plain object opts with only { unit, max, min?, max_inclusive? }, where unit is one of the 6 units and max is a required safe integer of 0 or more. Returns: { ok: true, errors: [], size } or { ok: false, errors: [{ code }], size } (code is from error_taxonomy; size is a number only for TOO_LARGE and TOO_SMALL, and null otherwise). measure(value, unit) returns an integer of 0 or more and throws SizeError on a value it cannot accept. Side effect: NONE. There is no state, no outside connection, no credential and nothing for the caller to provide, and the only dependencies are Node.js built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-07IPrimitive
Deduplication — Deduplication part (Primitive)
The Deduplication part (Primitive) is a standalone part that identifies duplicates of the same key within a given list (an array of { key }) and a given index (known_keys). It puts the positions that appear for the first time into kept, and the second and later occurrences and those found in the index into duplicates ({ index, of }). Keys are compared as UTF-16 code units, unchanged. Input it cannot accept is stopped with a fixed code and position (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
In identifying duplicates, the counterpart of reconcile, it prevents silently skipping entries of the wrong shape, mistaking keys with names such as __proto__ or constructor, and accepting input with no limit on the number of entries or the length of keys. The same input always gives the same answer.
When to use
- You want to identify duplicates of the same key within the list passed in a single call. Each position goes into exactly one of kept and duplicates, once.
- You want to pass keys seen before as known_keys and also identify duplicates against them, as of: "known".
- You want to compare keys made with the I-08 canonicalization or B-07 hashing parts by exact code-unit match. Which values the key is made from is decided by the caller.
- You want input that cannot be accepted to be stopped with a code and position (JSON Pointer) instead of being silently skipped.
- You want to call it locally, as processing that does not throw an exception, does not modify its input, and has no state.
When not to use
- You want to find duplicates against a stored index or across other calls. It compares only within its arguments, and it neither reads nor writes a persistent or global index (that is on the storage Tsugite's side).
- You want keys that look the same (é and e plus a combining character, A and a, differences in leading or trailing whitespace) to be treated as the same. It does no normalization, case folding or whitespace trimming. If you want them treated as the same, the caller makes the key by fixed rules before passing it.
- You want to handle a list of more than 100,000 entries in one call. The caller splits it. Duplicates across split calls are not identified unless the earlier keys are passed as known_keys.
- You want duplicates to be judged by the content of an entry (properties other than key). Duplicates are judged by key only.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When entries is not a plain array (including subclasses of Array) it stops with INVALID_ENTRIES, and when it has more than 100,000 entries with TOO_MANY_ENTRIES.
- When an element is not a plain object (including holes, getters, class instances and a null prototype), or does not have an own data property key (a getter or an inherited one), it stops with MALFORMED_ENTRY.
- When key is not a string or is empty it stops with INVALID_KEY, and when it exceeds 4096 UTF-16 code units with KEY_TOO_LONG.
- When known_keys is not a plain array it stops with INVALID_KNOWN_KEYS, when it has more than 100,000 entries with TOO_MANY_KNOWN_KEYS, and when the same key appears twice with DUPLICATE_KNOWN_KEY (at the position of the second one).
- When options is not a plain object, has a name other than known_keys, or known_keys is a getter, it stops with INVALID_OPTIONS, and when inspecting a value itself raises an exception (a Proxy trap and so on) with INSPECTION_FAILED.
- When it stops, it returns the code and position (path, a JSON Pointer) of the first problem found, and returns neither kept nor duplicates.
What it does not do / limits
- Of the "deduplication" in its name, it does not delete duplicates from stored data. It only sorts the positions of the list in its arguments into kept and duplicates. Looking up and writing a stored index is on the storage Tsugite's side.
- Of the "detection of identical items" in its name, it does not look at whether the contents are the same. It uses only exact code-unit matching of keys, with no normalization or case folding.
- It cannot tell duplicates against an index that was not passed in (other calls, stored data).
- Accepting exactly the limit of 100,000 entries was checked only by unit tests (the fuzzing covers only inputs over the limit).
- Not being a duplicate does not mean that the content is correct or that it may be accepted (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular deduplication library.
How to connect
Call dedupe(entries, options) in src/dedupe.js (it does not throw). Input: a plain array of plain objects { key } (up to 100,000 entries) and options that are omitted or contain only { known_keys }. Returns: { ok: true, errors: [], kept, duplicates } (kept in ascending order of position; duplicates as { index, of }, where of is a position or "known") or { ok: false, errors: [{ code, path }] } (code is from error_taxonomy, path is a JSON Pointer). Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing. The only dependencies are Node.js built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-08IPrimitive
Canonicalization — Canonicalization part (Primitive)
The Canonicalization part (Primitive) is a standalone part that turns a JSON value into a canonical string (UTF-8 bytes) under the RFC8785-restricted rules. It orders keys by UTF-16 code units, inserts no whitespace, and accepts only safe integers as numbers. A value it cannot accept is stopped with a fixed code and position (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
In the preprocessing for signing and reconciliation, it prevents the bytes that a digest is computed from from changing because of variation in key order, whitespace or the way numbers are written, or because a value that cannot be accepted is silently skipped. The same value always produces the same string.
When to use
- You want to produce, from a JSON value, the canonical bytes that a digest is computed from, as preprocessing for signing or reconciliation (canonicalBytes).
- You want a value that cannot be accepted (a fraction, undefined, a function, an object that is not plain, an array with holes, and so on) to be stopped with a code and a position (JSON Pointer) instead of being silently skipped.
- You need a way of calling it that does not throw. tryCanonicalize returns { ok, errors, text } and does not throw an exception.
- You need deterministic output that does not depend on the clock, random numbers or the environment's locale.
- You want to call it locally, as processing with no outside boundary and no state. It has no dependency packages.
When not to use
- You want to canonicalize a value that contains fractional or exponent numbers. Numbers are safe integers only (-(2^53-1) to 2^53-1); this is not the whole of RFC 8785.
- You want strings that look the same to be treated as the same. It does no Unicode normalization, so é and e plus a combining character are different strings.
- You are looking for broad unification like the catalog's one-liner "unifying variations in representation (RFC8785 and similar)". What this part does is limited to the profile of RFC 8785 (JCS) that restricts numbers to safe integers.
- You handle values nested more than 1000 levels deep. It stops them with TOO_DEEP.
- You need a size limit on values. It has no limit on the length of strings, arrays or keys; a size limit is decided by the I-06 part or by the caller.
- You use it on a version or engine other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- For a fraction, an integer outside the safe range, NaN or Infinity, it stops with NOT_A_SAFE_INTEGER.
- For undefined (as the value itself, an array element or an object member) it stops with UNDEFINED_VALUE, and for a function, a symbol or a bigint with UNSUPPORTED_TYPE.
- For a subclass of Array, an array with holes, or an array with properties other than its elements, it stops with NOT_A_PLAIN_ARRAY, SPARSE_ARRAY or EXTRA_ARRAY_PROPERTY.
- For an object that is not plain (Date, Map, Buffer, a null prototype, a class instance and so on), a symbol key, or an object with a non-enumerable property, it stops with NOT_A_PLAIN_OBJECT, SYMBOL_KEY or NON_ENUMERABLE_PROPERTY.
- When there is a getter or setter property, it stops with ACCESSOR_PROPERTY without calling it. The content does not change while the value is being inspected.
- When nesting exceeds 1000 levels, it stops with TOO_DEEP without overflowing the stack.
- When inspecting the value itself raises an exception (a Proxy trap and so on), tryCanonicalize returns INSPECTION_FAILED and does not throw.
- When it stops, it returns the code (code) and the position where it stopped (path, a JSON Pointer per RFC 6901; the root is the empty string).
What it does not do / limits
- It does not handle the whole of RFC 8785 (the way fractional and exponent numbers are written). Numbers are safe integers only.
- It does no Unicode normalization. Even if two strings look the same, they are different strings if their code units differ.
- It does not decide which fields go into a digest or which values are excluded. That stays with the caller (each Tsugite or contract).
- The canonicalization copies kept inside the Tsugite (20 of them) have not yet been replaced with this part; they still have their own copies. For values the copies handle correctly, the output is the same byte for byte (or both stop the same way), but for values where a copy produces broken JSON or incomplete output (an array with holes, a symbol key and so on), this part stops. The copies' handling is unchanged, and any replacement is separate work, done after the compatibility table has been checked.
- Producing a canonical string does not mean that the value's content is correct, nor that it may be accepted (result_is_approval: false). It creates no authority either (derives_authority: false).
- It has not checked how JSON.stringify writes strings on engines other than Node.js v24.21.0.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular JSON library.
How to connect
Call tryCanonicalize(value) in src/canonical.js (it does not throw). canonicalize and canonicalBytes return the canonical string and a UTF-8 Buffer, and throw CanonicalizationError on a value they cannot accept. Input: one JavaScript value (a nesting of null, booleans, strings, safe integers, dense arrays and plain objects). Returns: { ok: true, errors: [], text } or { ok: false, errors: [{ code, path }] } (code is from error_taxonomy; path is a JSON Pointer). Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing. The only dependencies are Node.js built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-09IPrimitive
Readability check — Readability check (gzip, JSON, TSV, shallow PDF/image check) part (Primitive)
The Readability check part (Primitive) is a standalone part that checks whether fetched bytes can be opened (gzip decompression, UTF-8, JSON parsing, the number of TSV columns, the markers of PDF, PNG and JPEG) and returns the result as facts. Being unreadable is not an error; it is returned as the facts readable: false and reason. It does not make the decision to stop what cannot be opened (it is not a gatekeeper).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
You do not notice that a file is unreadable until you open it. This part returns a broken gzip, invalid UTF-8 or broken JSON as facts without throwing, and stops the decompressed size at 1 GiB so that a gzip bomb cannot use memory without limit. Because it does not infer format from the content, it does not reinterpret content that differs from its declaration as another type.
When to use
- You want to record as a fact whether fetched bytes can be opened as their declared type (probeReadability).
- You want gzip-compressed bytes to be decompressed and then checked (compression: "gzip"; if it cannot decompress, GUNZIP_FAILED).
- You want to know, as facts, the top-level type of JSON (top_type) or the number of rows and columns of TSV (rows, columns).
- You want a shallow check of whether PDF, PNG or JPEG has its markers (for PNG, also the IHDR width, height, bit_depth and color_type).
- You want unreadability not treated as an error, and want to receive why it could not be read (reason, and the line number and so on for TSV_RAGGED).
- You want to call it locally as deterministic processing that has no clock, no random numbers and no outside connection. It does not open files; the caller passes the bytes.
When not to use
- You want to confirm that a PDF or image can be rendered, that the page or image data is not corrupted, or that it is not encrypted. readable: true only means the markers are present (markers_only: true).
- You want to read character encodings other than UTF-8 (Shift_JIS and so on). If charset is not utf-8, the result is CHARSET_NOT_SUPPORTED, with no conversion and no guessing. Shift_JIS with no declared charset becomes NOT_UTF8.
- You want the type guessed from the first bytes of the content. format is decided only from the type/subtype of the declared media type.
- You want TSV quoting (\t or line breaks inside "…") to be handled. \t is always counted as a delimiter.
- You want files with bytes before the PDF header, more than 1024 bytes after %%EOF, or bytes after the JPEG EOI, to be treated as openable. They result in the fact that the markers are missing.
- You want to check input larger than 256 MiB, or input larger than 1 GiB after decompression. It stops with INPUT_TOO_LARGE (with limit). This is not the fact that it is unreadable; it means it was not checked.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with NOT_BYTES when the input is not an object, when bytes is not a Uint8Array (a string, an array of numbers, an ArrayBuffer, another typed array), or when reading a property itself raises an exception.
- It stops with INVALID_MEDIA_TYPE when declared_media_type is not a string of the form in the contract raw-fetch/v1 or is longer than 255 characters, and with INVALID_COMPRESSION when compression is neither "gzip" nor "none" (including when it is omitted).
- Before reading the bytes, it returns MEDIA_TYPE_PARAMETERS_UNREADABLE when the parameters of the declaration cannot be read as RFC 9110 parameters or charset appears two or more times, and CHARSET_NOT_SUPPORTED when charset is not utf-8 for json, tsv or text.
- It decodes UTF-8 in fatal mode; overlong sequences, surrogates, values above U+10FFFF and truncated sequences are NOT_UTF8. It does not strip a leading BOM; JSON with a BOM is JSON_PARSE_FAILED.
- A TSV whose non-empty lines do not all have the same number of columns is TSV_RAGGED, and it returns in facts the line number and column count of the first line that did not match.
- For PDF it checks the leading %PDF- and %%EOF within the last 1024 bytes; for PNG, the 8-byte signature, an IHDR of length 13 and its CRC-32; for JPEG, the leading FFD8 (SOI) and trailing FFD9 (EOI). If they are missing, the result is PDF_MARKERS_MISSING, PNG_SIGNATURE_OR_IHDR_BAD or JPEG_MARKERS_MISSING.
- When the input is larger than 256 MiB, the decompressed output is larger than 1 GiB, or the JSON text is larger than 256 MiB, it does not check and returns INPUT_TOO_LARGE with limit (input, gunzip_output, json_text).
- When the declared media type is not in the supported table, the result is FORMAT_UNKNOWN. It reads only the range of the view of the bytes it is given.
What it does not do / limits
- readable is only the fact of whether it can be opened; it does not mean that the content is correct, complete, safe, or may be accepted. For pdf, png and jpeg it means only that the markers are present.
- It does not make the decision to stop what cannot be opened (it is not a gatekeeper). Whether to stop is decided by the caller.
- It does not open, fetch or store files.
- gzip decompression is left to zlib, and the fuzz oracle also uses zlib. The correctness of zlib itself is outside the scope of this part's tests.
- Its behavior in an environment with insufficient memory has not been checked. If JSON.parse fails for a reason other than syntax, the result is also JSON_PARSE_FAILED.
- Readability results are not an approval (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular file format library.
How to connect
Call probeReadability({ bytes, declared_media_type, compression }) in src/decode-probe.js (it does not throw). bytes is a Uint8Array (up to 256 MiB), declared_media_type is the declaration of the type of the content after decompression (the form in the contract raw-fetch/v1, up to 255 characters), and compression is "gzip" or "none". It returns { ok: true, errors: [], readable, format, facts, reason } or { ok: false, errors: [{ code }] }. There are no side effects, no state, no outside connection and no credentials, and there is no port for the caller to provide. Its only dependency is the Node.js built-in zlib.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-10IComponent
Fetched data probe (versioned) — Fetched data probe (3 stages, 4 states, versioned) part (Component)
The Fetched data probe part (Component) is a part that, right after a fetch, checks the raw data in 3 stages (integrity, readability, usability) and keeps one of 4 states (done, need_input, failed, need_consent) in a separate record that carries the probe's version. It bundles the record check (the N-09 part (Primitive)), checked reading (the C-01 Tsugite), the readability check (the I-09 part (Primitive)), per-source extractors and idempotent writing (the C-05 Tsugite). It only reads the raw data; it does not delete or block it. done means the 3 items could be extracted, not that they are correct.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Whether an import succeeded cannot be looked back on later. This part keeps the result of the probe in a separate record that carries the probe's version. It also prevents the probe from blocking the raw data, writing a false done when a port has failed, reading a fetch made by a Tsugite that is not in the list of admitted Tsugite, and moving the extracted values (which may include PII) into the result.
When to use
- Right after a fetch, you want to pass the fetch.json record (fetch_record) and the folder (folder_ref = raw/<source>/<YYYYMMDDTHHMMSSZ>) and write the probe result once to probe/<source>/<folder_stamp>/<probe_version>.json (probeFetch).
- You want to record, without reading the raw data, failed when the fetch failed, there is no record, or N-09 does not accept the record, and need_consent when tsugite_id does not exactly match an entry in the list of admitted Tsugite (certified_tsugite).
- You want to check integrity with the expected sha256 and size, readability with I-09, and usability by whether the per-source extractor could extract the 3 items (transaction date occurred_on, amount, counterparty), and learn which stage fell short as need_input with a reason.
- You want to apply a new version of the probe to past fetches again and create new records while keeping the old ones. The result key includes probe_version.
- From the pile of need_input results, you want to find which Tsugite and probe to build next (a source without an extractor gives need_input / NO_EXTRACTOR).
- You want to resend a write whose result is unknown as the same request (resolvePending).
When not to use
- You want to use done as proof that the extracted values are correct or well-formed (how dates and amounts are written). done only means the 3 items could be extracted.
- You want to use the probe as a gatekeeper that blocks or deletes raw data that does not fit. This part does not write, delete or block raw data. The result is a separate record.
- You want it to also check that a file renders or is not broken. Readability is the shallow check of I-09 (for pdf, png and jpeg, whether the signature is present), and files beyond the I-09 limits are not checked (READABILITY_NOT_CHECKED).
- You want it to also take care of whether the extractor reads correctly or whether the list of admitted Tsugite is correct. Both are outside this part; it only looks for an exact match of the tsugite_id string.
- You want the content type inside application/gzip to be identified reliably. The content type is looked up from a table of file name extensions, so when the extension is not in the table or the name is misleading, it checks with a different type and the result may be need_input.
- You want to run an untrusted extractor in isolation. Extractors run in the same process and have no time or memory limit.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed, it stops with INVALID_OPTIONS; when the input to probeFetch is malformed (the shape of folder_ref, fetch_failed not a boolean, and so on), with INVALID_INPUT; and when a getter throws an exception, with INSPECTION_FAILED.
- When the N-09 port throws or its answer is inconsistent (not an answer about this record, or the shape of schema, partition or files differs), it reports CHECK_PORT_FAILED, and when the record's source and folder_stamp differ from folder_ref, FOLDER_MISMATCH; and it does not write a result.
- In the integrity stage, it reports INTEGRITY_MISSING when the stored object does not exist, INTEGRITY_DIGEST_MISMATCH on GET_REJECTED or when the sha256 recomputed by the part itself differs, and INTEGRITY_SIZE_MISMATCH when the size differs from byte_size; it lists all files and sets need_input.
- When the result of a read in the integrity stage is unknown, it stops with READ_UNKNOWN; on a refusal, a halt, a malformed result or a value that is not bytes, with READ_FAILED; and when the I-09 port throws or its answer is malformed, with READABILITY_PORT_FAILED; and it does not write a result (errors[0].name gives the file).
- In the readability stage, if any file cannot be read, the result is need_input / UNREADABLE, and if any file was not checked, need_input / READABILITY_NOT_CHECKED. In the usability stage, when the extractor throws or answers with something other than an array, the reason is EXTRACTOR_FAILED; when there are 0 records, NO_RECORDS; and when any of the 3 items is missing, MISSING_FACTS (with the count and the names of the missing items).
- When this instance has previously sent different content for the result key of the same version, it does not send (PROBE_RESULT_EXISTS). When the result of a write is unknown, it reports PROBE_WRITE_UNKNOWN (PROBE_PENDING while that is pending); when the Tsugite did not send, WRITE_REFUSED; and when the Tsugite halted or the result is not APPLIED or DEDUP_HIT, WRITE_FAILED.
- When timestamp does not return an existing date-time in RFC 3339 UTC (Z), it reads and writes nothing (CLOCK_FAILED); when the previous operation has not finished, it reports BUSY; and calling resolvePending when nothing is pending returns NOTHING_PENDING.
What it does not do / limits
- The Fetched data probe part is not a Tsugite. It has no Request or Receipt of its own; the external effect of writing the result is caused by the bundled C-05 Tsugite under its own contract (reading the raw data is NONE). This part itself does not touch the outside.
- The meaning of the bundled parts (the record was accepted ≠ the fetch is correct, read ≠ the content is correct, opened ≠ not broken, written ≠ accepted) stays on each part's side. The extractors themselves and the correctness of the extracted values are outside this part.
- The result is not approval and does not show correctness (approval: false, authority: NOT_DERIVED, derives_authority: false).
- It does not write the extracted values into the result record. It writes only the counts, the names of the missing items, and file names and indexes.
- Whether a raw key points to a stored object in that partition is the job of the backend configuration. A result of the same version written by another process is refused by the Tsugite and the backend, not by this part (neither overwrites).
- In this part's tests it is not connected to the real N-09, I-09, C-01 or C-05. The unit tests and fuzzing use stand-ins. The state (the record of sent result keys, the pending write) is only inside the process and is lost on restart.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for a particular data quality tool.
How to connect
Create it with createProbe(options) in src/probe.js; it returns { ok, errors, instance }. options are probe_version, certified_tsugite, result_target, request_prefix, ports and an optional timestamp. probeFetch returns state, reason, result, result_key and other fields, and the results carry approval: false. Ports: checkFetchRecord (N-09), probeReadability (I-09), read.<class>.get for each partition (the client of the C-01 Tsugite), write.submit (the client of the C-05 Tsugite) and optional extractors. Credentials are held by the ports.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
I-11IPrimitive
Selected input assembly and UTF-8 capacity — Selected input assembly and UTF-8 capacity part (Primitive)
The Selected input assembly and UTF-8 capacity part (Primitive) is a standalone part that joins only explicitly selected items, in the order selected, with a fixed separator (prefix + pieces.join(separator) + suffix), counts the UTF-8 bytes, and compares them with a limit passed by the caller. A selection names the revision, and a range names its unit (UTF16 or CODE_POINT). If the limit is exceeded, it returns OVER_LIMIT and the amount over, and cuts nothing. Input it cannot accept is stopped with a fixed code and position (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
What a sent text was assembled from, and whether it was within the limit, cannot be seen afterwards. This part prevents adding items that were not selected, using a selection of an old revision, and silently cutting, shrinking or splitting a text that exceeds the limit, and it returns a list of what was selected, what was excluded, the revisions and the digests.
When to use
- You want to assemble a request by joining only explicitly selected items (naming id and revision), in the order selected, with separator, and adding prefix and suffix before and after (assemble). Items that were not selected are not included.
- You want to compare the UTF-8 bytes of the assembled request with a limit, limit_bytes (1 to 1,073,741,824), passed by the caller. Exactly at the limit is WITHIN_LIMIT. The part has no default limit; the caller passes it.
- You want to select only part of an item with a range that names its unit, { unit, start, end }. unit is UTF16 or CODE_POINT; since the same numbers can point to different characters, the unit is not guessed.
- You want to trace afterwards what was assembled. It returns request_digest (the sha256 of the request in UTF-8), selection_digest, which changes if any of the revision, content, range or separator changes, included in the order selected, and excluded for the items not selected.
- You want to fix the string to be sent after per-role projection (P-12). Unlike I-06, which measures the size of a single value, it assembles only what was selected and fixes the revisions and digests.
- You want to call it locally, as processing that does not throw an exception, does not modify its input, and has no state. The same input always gives the same answer.
When not to use
- You want a text over the limit cut, shrunk or split to make it fit. With OVER_LIMIT, request_text is null, and no piece is cut or removed. Selecting again is up to the caller.
- You want the part to check what is selected (the authority to select, consent, purpose). It only assembles what was selected; it does not look at whether the authority to select, consent or purpose is correct.
- You want to measure the size including an outer wrapper or frame such as JSON. It counts only prefix + pieces.join(separator) + suffix; the caller checks the outer wrapper separately.
- You want to send or store the assembled text. This part neither sends nor stores. Building the outer wrapper and sending remain with the caller.
- You want to handle input where items or selection has more than 10,000 entries, or text or a separator exceeds 4,194,304 UTF-16 code units. Beyond these limits it stops with a fixed code.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options is not a plain object, has an unknown name or a getter, or lacks limit_bytes or separator, it stops with INVALID_OPTIONS; when limit_bytes is not an integer from 1 to 1,073,741,824, with INVALID_LIMIT; and when separator, prefix or suffix is not a string (including boxed strings) or is too long, with INVALID_FRAMING.
- When items is not a plain array it stops with INVALID_ITEMS; when it has more than 10,000 entries, with TOO_MANY_ITEMS; when an element has the wrong shape, with MALFORMED_ITEM; when id, revision or text has the wrong form, with INVALID_ID, INVALID_REVISION or INVALID_TEXT; when text is too long, with TEXT_TOO_LONG; and when the same id appears twice, with DUPLICATE_ITEM.
- When selection has the wrong shape it stops with INVALID_SELECTION, TOO_MANY_SELECTED or MALFORMED_SELECTION; when a selected id is not in items, with UNKNOWN_ITEM; when the selected revision differs from the revision in items, with REVISION_MISMATCH (an old selection is not used); and when the same id is selected twice, with DUPLICATE_SELECTION.
- When range is not { unit, start, end }, unit is not UTF16 or CODE_POINT (including lower case), or start and end are not safe integers with 0 ≤ start < end, it stops with INVALID_RANGE; when end exceeds the length of text, with RANGE_OUT_OF_BOUNDS; and when an end of a UTF16 range falls inside a surrogate pair, with RANGE_SPLITS_SURROGATE.
- When a selected piece, or separator, prefix or suffix, contains a lone surrogate (it cannot be encoded as UTF-8) it stops with INVALID_UNICODE; when the assembled request is empty or only whitespace (including line breaks and full-width spaces), with BLANK_REQUEST; and when inspecting a value itself raises an exception (a Proxy trap and so on), with INSPECTION_FAILED.
- When it stops, it returns the code and position (path, a JSON Pointer) of the first problem found in a fixed order, and returns no assembled result.
- Fuzzing (1,000,000 runs) that compares the results against an independent oracle written in a different way (TextEncoder for bytes, Array.from for code points, isWellFormed for lone surrogates, and a front-to-back search of items without a Map) confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- WITHIN_LIMIT only means "within the limit passed in"; it does not mean that the text may be sent or that there is consent. The size of an outer wrapper (JSON, a frame) is not counted.
- The content digest (content_digest) is the digest of the text passed in; it does not look at whether that text matches the original material.
- Accepting exactly the limits of 10,000 entries and 4,194,304 code units was checked only by unit tests (the fuzzing covers only small inputs).
- It has no external boundary, no state, no clock and no random numbers, and neither sends nor stores. Side effect: NONE, and it uses no credentials.
- The result of assembling and counting is not an approval (result_is_approval: false) and creates no authority (derives_authority: false).
- The input and output forms were defined in v1; there is no existing implementation (new).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular prompt-assembly library.
How to connect
Call assemble(items, selection, options) in src/selected-input.js (it does not throw). items is an array of { id, revision, text }, selection is an array of { id, revision, range? }, and options is { limit_bytes, separator, prefix?, suffix? }. Returns: { ok: true, errors: [], status, request_text, request_digest, selection_digest, included, excluded and so on } or { ok: false, errors: [{ code, path }] }. There is no side effect, no state, no external connection, no credential, no dependency package and no port.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
J-01JTsugite
PDF generation — Document and Media Transform Tsugite
The Document and Media Transform Tsugite (継手, tsugite) works behind PDF generation. Before it passes a PDF generation request to an external conversion backend, it checks the input, the operation and the output profile, and it then matches the returned receipt and output bytes. The backend makes the PDF; this Tsugite does not make PDFs. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When an external conversion backend is used to output business forms and supporting documents as PDF, it prevents sending spoofed input or an unpinned output profile, and prevents passing on a returned PDF without checking that it is the one requested (swapping). It computes the digest of the output bytes itself and matches it against the receipt.
When to use
- You leave outputting business forms and supporting documents as PDF to an external conversion backend and want checks before and after it.
- You want to pin the PDF output profile and params so that PDFs are made only in the form you decided.
- You want to accept the PDF bytes returned by the backend only when they match the digest in the receipt.
- You do not want the same request to be sent twice. A resend after a failure is resolved by a lookup.
When not to use
- You want the PDF-making mechanism itself. This Tsugite does not make PDFs and does not include an actual backend.
- You want to check whether the resulting PDF looks the same as the original (fidelity) or whether it is intact as a PDF. Neither is checked.
- You want to store the resulting PDF. Storage is the role of the object store Tsugite (C-01).
- You need e-signatures. E-signature (J-06) has not been built, and this part does not cover J-06. Requests for an e-signature operation are not sent.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the input. Input whose bytes do not match the digest in the request, input over the limit (max_input_bytes), input with a content-type that is not allowed, and input whose leading magic bytes do not match its content-type (spoofed input) are not sent to the conversion backend.
- It checks the operation, output profile and params. Requests for an operation that is not allowed, for an e-signature (J-06) operation, or for an output profile or params that are not pinned are not sent.
- It computes the digest of the output bytes itself. If it differs from the receipt, the output is not accepted (OUTPUT_REJECTED) and it stops from then on (swapped output is not taken).
- It checks the receipt (MediaTransformReceipt). A spoofed request_digest, a receipt for another request, a swapped operation, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- It does not accept a receipt that claims to be verified, disclosure-authorized or trusted (any value other than content_verification NOT_VERIFIED, fidelity_assertion NOT_ASSERTED, disclosure_authorization NOT_DERIVED or trust_assertion NOT_DERIVED).
- It checks the shape of the result. It does not accept SUCCEEDED without an output digest, SUCCEEDED for OCR_EXTRACT or QR_DECODE without extracted_ref, or FAILED that comes with an output or an effect. PARTIAL is not treated as success.
- It never sends the same request_id twice. A backend failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not generate PDFs itself. An external conversion backend makes the PDF; this Tsugite only checks before sending and matches after receiving.
- It does not check the visual fidelity of a conversion. A successful conversion does not mean the meaning is the same.
- It does not include an actual backend. Which conversion engine or OCR service to connect to, the port implementation and the profiles to pin are undecided and will be settled at a separate gate when connecting.
- Magic bytes are checked only in the first few bytes. It does not inspect the structure of the whole file (for example, a broken PDF).
- No result is approval (approval: false). Every result carries content_verification: NOT_VERIFIED and trust: NOT_DERIVED.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a conversion engine.
How to connect
Contract: erabazu.candidate/certified-tsugite/document-media/v1. Input: call transform(request, bytes) on createMediaTransformClient({ cfg, port }) with the request and the input bytes. Returns: one of TRANSFORMED, PARTIAL, FAILED, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED, OUTPUT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the conversion backend (transform, and optionally lookup) and cfg (allowlists, pins and limits). Side effect: NONE or LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
J-02JTsugite
OCR and text extraction — Document and Media Transform Tsugite
The Document and Media Transform Tsugite (継手, tsugite) works behind OCR and text extraction. Before it passes an OCR request to an external conversion backend, it checks the input, the operation and the output profile, and it then matches the returned receipt and output bytes. The backend reads the text; this Tsugite does not perform OCR. Text that was read is treated neither as a verified fact nor as trusted. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When text is extracted from images or paper for search and matching preprocessing, it prevents sending spoofed input, treating a result as success when there is no extraction result (extracted_ref), and treating text that was read as if it were a checked fact.
When to use
- You leave reading text from images or PDFs to an external OCR service and want checks before and after it.
- You want every successful OCR_EXTRACT to carry extracted_ref. A success without it is not accepted.
- You want to receive text that was read as material for search and matching preprocessing, and leave the decision on whether to treat it as fact to the Kernels (evidence family).
- You do not want the same request to be sent twice. A resend after a failure is resolved by a lookup.
When not to use
- You want to confirm that the text read is correct. The correctness of content read by OCR is not checked, and it is treated neither as a verified fact nor as trusted.
- You want an OCR engine itself. This Tsugite does not perform OCR and does not include an actual backend. Which OCR service to connect to has not been decided.
- You want to send input in a format that is not on the allowlist. The only types that can be confirmed by magic bytes are application/pdf, image/png, image/jpeg, text/plain and application/json; types not on this list are not sent.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the shape of the result. It does not accept SUCCEEDED without an output digest, SUCCEEDED for OCR_EXTRACT or QR_DECODE without extracted_ref, or FAILED that comes with an output or an effect. PARTIAL is not treated as success.
- It does not accept a receipt that claims to be verified, disclosure-authorized or trusted (any value other than content_verification NOT_VERIFIED, fidelity_assertion NOT_ASSERTED, disclosure_authorization NOT_DERIVED or trust_assertion NOT_DERIVED).
- It checks the input. Input whose bytes do not match the digest in the request, input over the limit (max_input_bytes), input with a content-type that is not allowed, and input whose leading magic bytes do not match its content-type (spoofed input) are not sent to the conversion backend.
- It checks the operation, output profile and params. Requests for an operation that is not allowed, for an e-signature (J-06) operation, or for an output profile or params that are not pinned are not sent.
- It checks the receipt (MediaTransformReceipt). A spoofed request_digest, a receipt for another request, a swapped operation, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- It computes the digest of the output bytes itself. If it differs from the receipt, the output is not accepted (OUTPUT_REJECTED) and it stops from then on (swapped output is not taken).
- It never sends the same request_id twice. A backend failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not perform OCR itself. An external conversion backend reads the text; this Tsugite only checks before sending and matches after receiving.
- It does not check the correctness of content read by OCR (as the contract's const specifies, it makes no such claim). Whether to treat text that was read as fact is decided by the evidence-family Kernels.
- It does not include an actual backend. Which conversion engine or OCR service to connect to, the port implementation and the profiles to pin are undecided and will be settled at a separate gate when connecting.
- Magic bytes are checked only in the first few bytes. It does not inspect the structure of the whole file (for example, a broken PDF).
- No result is approval (approval: false). Every result carries content_verification: NOT_VERIFIED and trust: NOT_DERIVED.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a conversion engine.
How to connect
Contract: erabazu.candidate/certified-tsugite/document-media/v1. Input: call transform(request, bytes) on createMediaTransformClient({ cfg, port }) with the request and the input bytes. Returns: one of TRANSFORMED (with extracted_ref for OCR), PARTIAL, FAILED, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED, OUTPUT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the conversion backend (transform, and optionally lookup) and cfg (allowlists, pins and limits). Side effect: NONE or LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
J-03JTsugite
Format conversion — Document and Media Transform Tsugite
The Document and Media Transform Tsugite (継手, tsugite) works behind format conversion. Before it passes a format conversion request to an external conversion backend, it checks the input, the operation and the output profile, and it then matches the returned receipt and output bytes. The backend changes the format; this Tsugite does not convert. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When an external conversion backend changes a document's format for compatibility, it prevents sending spoofed input or an unpinned target (output profile), and prevents passing on the returned bytes without checking that they are the ones requested (swapping).
When to use
- You leave document format conversion to an external conversion backend and want checks before and after it.
- The input is application/pdf, image/png, image/jpeg, text/plain or application/json, and you want its type confirmed by its leading magic bytes before sending.
- You want to pin the target output profile and params.
- You want to accept the converted bytes only when they match the digest in the receipt.
When not to use
- You want to send a format not on the list, such as docx, as input. The only types that can be confirmed by magic bytes are application/pdf, image/png, image/jpeg, text/plain and application/json; types not on this list are not sent (the docx in the catalog one-liner "docx→pdf" is not on this list).
- You want to confirm that meaning or appearance is the same before and after conversion. A successful conversion does not mean the meaning is the same, and visual fidelity is not checked.
- You want a conversion engine itself. This Tsugite does not convert and does not include an actual backend.
- You want to store the converted result. Storage is the role of the object store Tsugite (C-01).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the input. Input whose bytes do not match the digest in the request, input over the limit (max_input_bytes), input with a content-type that is not allowed, and input whose leading magic bytes do not match its content-type (spoofed input) are not sent to the conversion backend.
- It checks the operation, output profile and params. Requests for an operation that is not allowed, for an e-signature (J-06) operation, or for an output profile or params that are not pinned are not sent.
- It computes the digest of the output bytes itself. If it differs from the receipt, the output is not accepted (OUTPUT_REJECTED) and it stops from then on (swapped output is not taken).
- It checks the receipt (MediaTransformReceipt). A spoofed request_digest, a receipt for another request, a swapped operation, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- It does not accept a receipt that claims to be verified, disclosure-authorized or trusted (any value other than content_verification NOT_VERIFIED, fidelity_assertion NOT_ASSERTED, disclosure_authorization NOT_DERIVED or trust_assertion NOT_DERIVED).
- It checks the shape of the result. It does not accept SUCCEEDED without an output digest, SUCCEEDED for OCR_EXTRACT or QR_DECODE without extracted_ref, or FAILED that comes with an output or an effect. PARTIAL is not treated as success.
- It never sends the same request_id twice. A backend failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not perform format conversion itself. An external conversion backend changes the format; this Tsugite only checks before sending and matches after receiving.
- It does not check the visual fidelity of a conversion. A successful conversion does not mean the meaning is the same.
- It does not include an actual backend. Which conversion engine or OCR service to connect to, the port implementation and the profiles to pin are undecided and will be settled at a separate gate when connecting.
- Magic bytes are checked only in the first few bytes. It does not inspect the structure of the whole file (for example, a broken PDF).
- No result is approval (approval: false). Every result carries content_verification: NOT_VERIFIED and trust: NOT_DERIVED.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a conversion engine.
How to connect
Contract: erabazu.candidate/certified-tsugite/document-media/v1. Input: call transform(request, bytes) on createMediaTransformClient({ cfg, port }) with the request and the input bytes. Returns: one of TRANSFORMED, PARTIAL, FAILED, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED, OUTPUT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the conversion backend (transform, and optionally lookup) and cfg (allowlists, pins and limits). Side effect: NONE or LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
J-04JTsugite
Thumbnail/preview generation — Document and Media Transform Tsugite
The Document and Media Transform Tsugite (継手, tsugite) works behind thumbnail and preview generation. Before it passes a thumbnail request to an external conversion backend, it checks the input, the operation and the output profile, and it then matches the returned receipt and output bytes. The backend makes the thumbnails; this Tsugite does not make them. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When an external conversion backend makes the thumbnails or previews used for how lists look, it prevents sending spoofed or oversized input, and prevents passing a returned image on to display without checking that it is the one requested (swapping).
When to use
- You leave making the thumbnails or previews shown in lists to an external conversion backend and want checks before and after it.
- You want to pin the output profile and params so that thumbnails are made only in the form you decided.
- You want to set an upper limit on input size (max_input_bytes) and not send anything over it.
- You want to accept the image bytes returned by the backend only when they match the digest in the receipt.
When not to use
- You want the thumbnail-making mechanism itself. This Tsugite does not make thumbnails and does not include an actual backend.
- You want to check whether a thumbnail looks like the original (fidelity). This is not checked.
- You want to store the thumbnails made. Storage is the role of the object store Tsugite (C-01).
- You want to send input in a format that is not on the allowlist. The only types that can be confirmed by magic bytes are application/pdf, image/png, image/jpeg, text/plain and application/json; types not on this list are not sent.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the input. Input whose bytes do not match the digest in the request, input over the limit (max_input_bytes), input with a content-type that is not allowed, and input whose leading magic bytes do not match its content-type (spoofed input) are not sent to the conversion backend.
- It checks the operation, output profile and params. Requests for an operation that is not allowed, for an e-signature (J-06) operation, or for an output profile or params that are not pinned are not sent.
- It computes the digest of the output bytes itself. If it differs from the receipt, the output is not accepted (OUTPUT_REJECTED) and it stops from then on (swapped output is not taken).
- It checks the receipt (MediaTransformReceipt). A spoofed request_digest, a receipt for another request, a swapped operation, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- It does not accept a receipt that claims to be verified, disclosure-authorized or trusted (any value other than content_verification NOT_VERIFIED, fidelity_assertion NOT_ASSERTED, disclosure_authorization NOT_DERIVED or trust_assertion NOT_DERIVED).
- It checks the shape of the result. It does not accept SUCCEEDED without an output digest, SUCCEEDED for OCR_EXTRACT or QR_DECODE without extracted_ref, or FAILED that comes with an output or an effect. PARTIAL is not treated as success.
- It never sends the same request_id twice. A backend failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not make thumbnails or previews itself. An external conversion backend makes them; this Tsugite only checks before sending and matches after receiving.
- It does not check the visual fidelity of a conversion. A successful conversion does not mean the meaning is the same.
- It does not include an actual backend. Which conversion engine or OCR service to connect to, the port implementation and the profiles to pin are undecided and will be settled at a separate gate when connecting.
- Magic bytes are checked only in the first few bytes. It does not inspect the structure of the whole file (for example, a broken PDF).
- No result is approval (approval: false). Every result carries content_verification: NOT_VERIFIED and trust: NOT_DERIVED.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a conversion engine.
How to connect
Contract: erabazu.candidate/certified-tsugite/document-media/v1. Input: call transform(request, bytes) on createMediaTransformClient({ cfg, port }) with the request and the input bytes. Returns: one of TRANSFORMED, PARTIAL, FAILED, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED, OUTPUT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the conversion backend (transform, and optionally lookup) and cfg (allowlists, pins and limits). Side effect: NONE or LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
J-05JTsugite
Image optimization — Document and Media Transform Tsugite
The Document and Media Transform Tsugite (継手, tsugite) works behind image optimization. Before it passes an image optimization request to an external conversion backend, it checks the input, the operation and the output profile, and it then matches the returned receipt and output bytes. The backend downscales and compresses images; this Tsugite does not change them. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When an external conversion backend downscales and compresses images for display speed, it prevents sending spoofed input or unpinned settings, and prevents using a returned image without checking that it is the one requested (swapping).
When to use
- You leave downscaling and compressing images to an external conversion backend and want checks before and after it.
- The input is an allowed type such as image/png or image/jpeg, and you want its type confirmed by its leading magic bytes before sending.
- You want to pin the optimization output profile and params.
- You want to accept the image bytes returned by the backend only when they match the digest in the receipt.
When not to use
- You want the mechanism that downscales and compresses images itself. This Tsugite does not change images and does not include an actual backend.
- You want to check whether an optimized image looks like the original (fidelity). This is not checked.
- You want to store optimized images. Storage is the role of the object store Tsugite (C-01).
- You want to send input in a format that is not on the allowlist. The only types that can be confirmed by magic bytes are application/pdf, image/png, image/jpeg, text/plain and application/json; types not on this list are not sent.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the input. Input whose bytes do not match the digest in the request, input over the limit (max_input_bytes), input with a content-type that is not allowed, and input whose leading magic bytes do not match its content-type (spoofed input) are not sent to the conversion backend.
- It checks the operation, output profile and params. Requests for an operation that is not allowed, for an e-signature (J-06) operation, or for an output profile or params that are not pinned are not sent.
- It computes the digest of the output bytes itself. If it differs from the receipt, the output is not accepted (OUTPUT_REJECTED) and it stops from then on (swapped output is not taken).
- It checks the receipt (MediaTransformReceipt). A spoofed request_digest, a receipt for another request, a swapped operation, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- It does not accept a receipt that claims to be verified, disclosure-authorized or trusted (any value other than content_verification NOT_VERIFIED, fidelity_assertion NOT_ASSERTED, disclosure_authorization NOT_DERIVED or trust_assertion NOT_DERIVED).
- It checks the shape of the result. It does not accept SUCCEEDED without an output digest, SUCCEEDED for OCR_EXTRACT or QR_DECODE without extracted_ref, or FAILED that comes with an output or an effect. PARTIAL is not treated as success.
- It never sends the same request_id twice. A backend failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not perform image optimization itself. An external conversion backend downscales and compresses the image; this Tsugite only checks before sending and matches after receiving.
- It does not check the visual fidelity of a conversion. A successful conversion does not mean the meaning is the same.
- It does not include an actual backend. Which conversion engine or OCR service to connect to, the port implementation and the profiles to pin are undecided and will be settled at a separate gate when connecting.
- Magic bytes are checked only in the first few bytes. It does not inspect the structure of the whole file (for example, a broken PDF).
- No result is approval (approval: false). Every result carries content_verification: NOT_VERIFIED and trust: NOT_DERIVED.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a conversion engine.
How to connect
Contract: erabazu.candidate/certified-tsugite/document-media/v1. Input: call transform(request, bytes) on createMediaTransformClient({ cfg, port }) with the request and the input bytes. Returns: one of TRANSFORMED, PARTIAL, FAILED, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED, OUTPUT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the conversion backend (transform, and optionally lookup) and cfg (allowlists, pins and limits). Side effect: NONE or LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
J-06JComponent
Electronic contract (envelope) — Electronic contract (envelope) part (Component)
The Electronic contract (envelope) part (Component) is a part that bundles a signing flow. It fixes the document's digest (B-07), checks each designated signer's signature with a pinned key (B-02), and advances the state only when the state transition (open, sign, complete, void, timestamp) has been recorded in the audit ledger Tsugite (H-01). Checking a time token attached to the completion digest (B-06) is bundled optionally. When the ledger's result is unknown, it does not advance the state and holds the record as pending (fail-closed). Signatures being complete is not legal acceptance, authority or approval.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
As a layer separate from B signing, in a signing flow it prevents claiming a state that has no record in the ledger, reusing a signature made for another envelope, document or signer, advancing the state while the ledger's result is unknown, and passing the document's body to the ledger.
When to use
- You want to collect the signatures of the designated signers (1 to 20) on one document, and set COMPLETED only when every signer's signature has passed the check and been recorded.
- You want each signer's signature checked by the B-02 part against public keys fixed by the caller (pinned_keys). Each signer signs the bytes returned by signingBytes(signer_ref) (which include envelope_ref, the document's digest and signer_ref).
- You want to append state transitions to the H-01 audit ledger Tsugite as AuditEventEnvelope, and make only transitions that were recorded (APPENDED_VERIFIED or ALREADY_APPENDED) into the state. Only the digest of the transition's payload is passed to the ledger.
- After completion, you want to check a time token attached to the completion digest once, with a function that bundles B-06's verifyToken, and record it (optional).
- You want to resend a record whose ledger result was unknown, with the same envelope (resolvePending).
When not to use
- You want to use signatures being complete or a time being attached as legal acceptance, authority, delegation or approval. "Signed" shows only that the holder of a pinned key signed the bytes for this envelope, this document and this signer.
- You need identity verification of the signers. This part does not handle identity verification, authority or delegation, or the legal effect of signatures.
- You want it to also send signing requests (notifications). Notifications are not bundled. If they are to be sent, the caller sends them with the D-01 Tsugite.
- You want it to also obtain the time token (a request to a TSA). Obtaining it is not included, and B-06 only checks.
- You want to connect to an external electronic signature service. That would need the contract of a separate Tsugite, which is not being built now.
- You want to continue a flow across a restart. The state is only inside this process, and v1 has no function to rebuild the state from the ledger's records.
- You want to create two flows with the same envelope_ref. The event_id values would collide and the ledger refuses them (v1 has one flow per envelope_ref).
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed (an invalid reference, a duplicate signer or key, a key not in the pinned set, 21 or more signers, an extra key in the ledger settings, a getter), it stops with INVALID_OPTIONS.
- When an operation is not possible in the current state (signing before open or after void, voiding after completion, a timestamp before completion or a second time), it stops with WRONG_STATE, and when the previous operation has not finished, with BUSY.
- When the signer is not a designated signer it stops with UNKNOWN_SIGNER, when that signer has already signed with ALREADY_SIGNED, and when the signature does not pass the B-02 check (bytes for another envelope, document or signer, or another key) with SIGNATURE_REJECTED.
- When the ledger's result is unknown (UNKNOWN, an exception, no reply), it does not advance the state and holds the record as pending with LEDGER_UNKNOWN; while it is pending, anything other than resolvePending is stopped with RECORD_PENDING. Calling resolvePending when nothing is pending returns NOTHING_PENDING.
- When the ledger refuses (REFUSED, RECEIPT_REJECTED, HALTED and so on) or the shape of the ledger's head is invalid, it stops with LEDGER_REFUSED without advancing the state and without keeping a pending record (if the head is invalid, it does not send).
- When the timestamp request is not for the completion digest, hash_algo is not "SHA256", or it does not pass the B-06 check, it stops with TIMESTAMP_REJECTED, and when no verifyTimestamp function has been injected, with NOT_CONFIGURED.
- When the ledger's state() or an injected check function throws an exception it stops with PORT_FAILED, when a getter on an injected function's result throws with INSPECTION_FAILED, when the result of contentAddress is invalid with DIGEST_FAILED, and when timestamp is invalid with CLOCK_FAILED.
What it does not do / limits
- The Electronic contract (envelope) part is not a Tsugite. It has no Request or Receipt of its own; the external effect of recording in the ledger is caused by the bundled H-01 Tsugite under its own contract (audit-ledger/v1). This part itself does not touch the outside.
- The meaning of the bundled parts (digest identity, checking with a pinned key, matching the ledger's receipt, checking the time token; none of them is approval) stays on each part's side.
- Signatures being complete or a time being attached is not legal acceptance, authority, delegation or approval (approval: false, authority: NOT_DERIVED, derives_authority: false).
- It does not handle identity verification of the signers, authority or delegation, or the legal effect of signatures.
- It does not include notifications (sending signing requests) or obtaining the time token (a request to a TSA).
- It has never connected to a real ledger or TSA. The unit tests and fuzzing use stand-ins, and signature verification is also a stand-in (cryptographic correctness is the role of B-02). The wiring with the code of the real four parts was checked against a local stand-in backend.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular electronic signature service.
How to connect
Create it with createEnvelopeFlow(options) in src/envelope.js. Input: envelope_ref, signers, pinned_keys, ledger, ports and so on. Returns: { ok, errors, instance }; each operation returns { ok, errors, approval: false, phase, signed, … }. User provides (injected through ports): contentAddress (B-07), verifySignature (B-02), ledger.append and state (the client of the H-01 Tsugite), and an optional verifyTimestamp (B-06). Side effect: caused by the bundled Tsugite (EXTERNAL_IRREVERSIBLE); credentials are held by the injected functions.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
J-07JTsugite
Barcode/QR generation and reading — Document and Media Transform Tsugite
Of the barcode and QR generation and reading item, the Document and Media Transform Tsugite (継手, tsugite) handles only QR reading (QR_DECODE) requests. Before it passes an image to an external conversion backend, it checks the input and the operation, and it then matches the returned receipt and output. The backend reads the QR code; this Tsugite does not. QR generation is not in the contract, and content that was read is treated neither as a verified fact nor as trusted (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When QR codes are read for on-site input and output, it prevents sending spoofed images, treating a result as success when there is no extraction result (extracted_ref), and treating content that was read as if it were a checked fact.
When to use
- You leave QR reading to an external conversion backend and want checks before and after it.
- You want every successful QR_DECODE to carry extracted_ref. A success without it is not accepted.
- You want to receive content that was read and leave the decision on whether to treat it as fact to the Kernels (evidence family).
- You do not want the same request to be sent twice. A resend after a failure is resolved by a lookup.
When not to use
- You want to generate QR codes. QR generation is not in the contract; this Tsugite handles only reading (QR_DECODE).
- You want to handle barcodes. The operations of this part list QR reading; barcodes are not listed.
- You want to confirm that the content read is correct. The correctness of content read from QR codes is not checked, and it is treated neither as a verified fact nor as trusted.
- You want the QR-reading mechanism itself. This Tsugite does not read QR codes and does not include an actual backend.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the shape of the result. It does not accept SUCCEEDED without an output digest, SUCCEEDED for OCR_EXTRACT or QR_DECODE without extracted_ref, or FAILED that comes with an output or an effect. PARTIAL is not treated as success.
- It does not accept a receipt that claims to be verified, disclosure-authorized or trusted (any value other than content_verification NOT_VERIFIED, fidelity_assertion NOT_ASSERTED, disclosure_authorization NOT_DERIVED or trust_assertion NOT_DERIVED).
- It checks the input. Input whose bytes do not match the digest in the request, input over the limit (max_input_bytes), input with a content-type that is not allowed, and input whose leading magic bytes do not match its content-type (spoofed input) are not sent to the conversion backend.
- It checks the operation, output profile and params. Requests for an operation that is not allowed, for an e-signature (J-06) operation, or for an output profile or params that are not pinned are not sent.
- It checks the receipt (MediaTransformReceipt). A spoofed request_digest, a receipt for another request, a swapped operation, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- It computes the digest of the output bytes itself. If it differs from the receipt, the output is not accepted (OUTPUT_REJECTED) and it stops from then on (swapped output is not taken).
- It never sends the same request_id twice. A backend failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not generate QR codes. That is not in the contract; it handles only QR_DECODE reading requests.
- It does not read QR codes itself either. An external conversion backend reads them; this Tsugite only checks before sending and matches after receiving.
- It does not check the correctness of content read from QR codes (as the contract's const specifies, it makes no such claim).
- It does not include an actual backend. Which conversion engine or OCR service to connect to, the port implementation and the profiles to pin are undecided and will be settled at a separate gate when connecting.
- Magic bytes are checked only in the first few bytes. It does not inspect the structure of the whole file (for example, a broken PDF).
- No result is approval (approval: false). Every result carries content_verification: NOT_VERIFIED and trust: NOT_DERIVED.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/document-media/v1. Input: call transform(request, bytes) on createMediaTransformClient({ cfg, port }) with the request and the input bytes. Returns: one of TRANSFORMED (with extracted_ref for QR), PARTIAL, FAILED, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED, OUTPUT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the conversion backend (transform, and optionally lookup) and cfg (allowlists, pins and limits). Side effect: NONE or LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
J-08JTsugite
Watermarks and stamps — Document and Media Transform Tsugite
The Document and Media Transform Tsugite (継手, tsugite) works behind watermarks and stamps. Before it passes a watermark request to an external conversion backend, it checks the input, the operation and the output profile, and it then matches the returned receipt and output bytes. The backend applies the watermark; this Tsugite does not apply watermarks. If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When an external conversion backend applies a watermark as a mark of origin or confidentiality, it prevents sending spoofed input or unpinned settings, and prevents passing on the returned output without checking that it is the one requested (swapping).
When to use
- You leave watermarking documents or images to an external conversion backend and want checks before and after it.
- You want to pin the watermark output profile and params so that watermarks are applied only in the form you decided.
- You want to accept the output bytes returned by the backend only when they match the digest in the receipt.
- You do not want the same request to be sent twice. A resend after a failure is resolved by a lookup.
When not to use
- You need e-signatures. E-signature (J-06) has not been built, and this part does not cover J-06. Requests for an e-signature operation are not sent.
- You want to decide what to mark or what to keep confidential. That decision is the role of the Kernel (redact-facts) and people; this Tsugite only checks before sending and after receiving.
- You want to use watermarked output as grounds for trust or for disclosure authorization. Every result is trust: NOT_DERIVED, and no disclosure authorization is derived.
- You want the watermarking mechanism itself. This Tsugite does not apply watermarks and does not include an actual backend.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the input. Input whose bytes do not match the digest in the request, input over the limit (max_input_bytes), input with a content-type that is not allowed, and input whose leading magic bytes do not match its content-type (spoofed input) are not sent to the conversion backend.
- It checks the operation, output profile and params. Requests for an operation that is not allowed, for an e-signature (J-06) operation, or for an output profile or params that are not pinned are not sent.
- It computes the digest of the output bytes itself. If it differs from the receipt, the output is not accepted (OUTPUT_REJECTED) and it stops from then on (swapped output is not taken).
- It checks the receipt (MediaTransformReceipt). A spoofed request_digest, a receipt for another request, a swapped operation, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- It does not accept a receipt that claims to be verified, disclosure-authorized or trusted (any value other than content_verification NOT_VERIFIED, fidelity_assertion NOT_ASSERTED, disclosure_authorization NOT_DERIVED or trust_assertion NOT_DERIVED).
- It checks the shape of the result. It does not accept SUCCEEDED without an output digest, SUCCEEDED for OCR_EXTRACT or QR_DECODE without extracted_ref, or FAILED that comes with an output or an effect. PARTIAL is not treated as success.
- It never sends the same request_id twice. A backend failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not apply watermarks itself. An external conversion backend applies them; this Tsugite only checks before sending and matches after receiving.
- It does not do e-signatures. E-signature (J-06) has not been built.
- It does not check the visual fidelity of a conversion (whether the watermark is actually applied as it appears).
- It does not include an actual backend. Which conversion engine or OCR service to connect to, the port implementation and the profiles to pin are undecided and will be settled at a separate gate when connecting.
- Magic bytes are checked only in the first few bytes. It does not inspect the structure of the whole file (for example, a broken PDF).
- No result is approval (approval: false). Every result carries content_verification: NOT_VERIFIED and trust: NOT_DERIVED.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/document-media/v1. Input: call transform(request, bytes) on createMediaTransformClient({ cfg, port }) with the request and the input bytes. Returns: one of TRANSFORMED, PARTIAL, FAILED, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED, OUTPUT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the conversion backend (transform, and optionally lookup) and cfg (allowlists, pins and limits). Side effect: NONE or LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
J-09JTsugite
Redaction rendering — Document and Media Transform Tsugite
The Document and Media Transform Tsugite (継手, tsugite) works behind redaction rendering. Before it passes a request to draw redactions to an external conversion backend, it checks the input, the operation and the output profile, and it then matches the returned receipt and output bytes. The backend draws the redactions, and the Kernel (redact-facts) and people decide what to redact. This Tsugite only passes on the rendering request and matches the result; it does not authorize disclosure (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When an external conversion backend renders the redactions decided by the Kernel (redact-facts), it prevents sending spoofed input, accepting a receipt that claims "disclosure authorized", and passing on the returned output without checking that it is the one requested (swapping).
When to use
- You leave rendering the redactions decided by the Kernel (redact-facts) and people to an external conversion backend and want checks before and after it.
- You do not want the rendering result mistaken for disclosure authorization. A receipt whose disclosure_authorization is anything other than NOT_DERIVED is not accepted.
- You want to accept the output bytes returned by the backend only when they match the digest in the receipt.
- You do not want the same request to be sent twice. A resend after a failure is resolved by a lookup.
When not to use
- You want to decide what to redact. Deciding what to redact is the role of the Kernels and people; this Tsugite only passes on the rendering request.
- You want to judge whether redacted output may be disclosed. Rendering a redaction is not disclosure authorization.
- You want to check whether the redaction is actually drawn as it appears (fidelity). This is not checked.
- You want the redaction-drawing mechanism itself. This Tsugite does not render and does not include an actual backend.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It does not accept a receipt that claims to be verified, disclosure-authorized or trusted (any value other than content_verification NOT_VERIFIED, fidelity_assertion NOT_ASSERTED, disclosure_authorization NOT_DERIVED or trust_assertion NOT_DERIVED).
- It checks the input. Input whose bytes do not match the digest in the request, input over the limit (max_input_bytes), input with a content-type that is not allowed, and input whose leading magic bytes do not match its content-type (spoofed input) are not sent to the conversion backend.
- It checks the operation, output profile and params. Requests for an operation that is not allowed, for an e-signature (J-06) operation, or for an output profile or params that are not pinned are not sent.
- It computes the digest of the output bytes itself. If it differs from the receipt, the output is not accepted (OUTPUT_REJECTED) and it stops from then on (swapped output is not taken).
- It checks the receipt (MediaTransformReceipt). A spoofed request_digest, a receipt for another request, a swapped operation, or a receipt that increases the external effect is not accepted, and it stops from then on (HALTED).
- It checks the shape of the result. It does not accept SUCCEEDED without an output digest, SUCCEEDED for OCR_EXTRACT or QR_DECODE without extracted_ref, or FAILED that comes with an output or an effect. PARTIAL is not treated as success.
- It never sends the same request_id twice. A backend failure is treated as UNKNOWN, and a resend is resolved by a lookup.
What it does not do / limits
- It does not render redactions itself. An external conversion backend draws them; this Tsugite only checks before sending and matches after receiving.
- It does not decide what to redact and does not authorize disclosure. Deciding what to redact is the role of the Kernel (redact-facts) and people.
- It does not check the visual fidelity of a conversion.
- It does not include an actual backend. Which conversion engine or OCR service to connect to, the port implementation and the profiles to pin are undecided and will be settled at a separate gate when connecting.
- Magic bytes are checked only in the first few bytes. It does not inspect the structure of the whole file (for example, a broken PDF).
- No result is approval (approval: false). Every result carries content_verification: NOT_VERIFIED and trust: NOT_DERIVED.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/document-media/v1. Input: call transform(request, bytes) on createMediaTransformClient({ cfg, port }) with the request and the input bytes. Returns: one of TRANSFORMED, PARTIAL, FAILED, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED, OUTPUT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the conversion backend (transform, and optionally lookup) and cfg (allowlists, pins and limits). Side effect: NONE or LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
K-01KTsugite
Structured logging — Observability Sink Tsugite
The Observability Sink Tsugite (継手, tsugite) takes batches of machine-readable structured logs (records of ts, level, event and attrs), checks the allowed events and keys, the types, the absence of values that look sensitive, the redaction record and the digest, then sends them to a sink and matches the receipt it returns (SinkReceipt). If anything does not match, it does not send and does not accept (fail-closed). Records the sink did not accept are returned as missing.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When logs are streamed to an external sink for incident investigation, it prevents sensitive values such as email addresses or credentials, or free text, from getting into logs and being copied to the sink, and prevents records the sink did not accept from silently disappearing. It sends only short primitive values under allowed keys, and reports missing records with SUNK_PARTIAL and dropped.
When to use
- You want to send structured logs in the form ts, level, event and attrs to an external sink as machine-readable records.
- You want to decide in advance which events (log_events) and keys (log_attrs) may appear in logs, and send nothing else.
- You do not want sensitive values in logs copied to the sink. The redaction record (redaction_manifest_ref) can be made mandatory.
- You want logs the sink did not accept reported as a fact of missing records, not silently dropped.
- You do not want the same batch streamed twice. The same request_id is not sent again (ALREADY_SUNK).
When not to use
- You want to keep free-text messages or long bodies in logs as they are. There is no free-text field; only short primitive values under allowed keys are sent.
- You need error tracking (K-04) or alerting (K-06). Those are separate parts and are not included in this Tsugite.
- You want a sink you can use right away. No actual sink (a provider's log intake) is included, and it has not been decided.
- You want to treat the sink's accepted count as verification that log contents are correct. An observation is not a verified fact.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks for values that look sensitive. If a batch contains email addresses, runs of 10 or more digits, Bearer tokens, JWTs, keys or text that looks like credentials, it is not sent (REFUSED).
- Records with free-text fields, or with events or keys (attrs) that are not allowed, are not sent.
- It checks sizes. Anything over the limits on batch size (max_payload_bytes), number of records (max_records) or value length (max_attr_len) is not sent.
- It checks the pinned sink. Nothing is sent to a sink that is not pinned. If the payload has been tampered with (payload_digest mismatch), it is not sent.
- It requires the redaction record (redaction_manifest_ref) and does not send anything without it. This is stricter than the contract.
- It checks the receipt (SinkReceipt). A receipt with a spoofed request digest, or one accepting more records than were sent, is not accepted (RECEIPT_REJECTED), and it stops from then on (HALTED).
- A sink failure (a 500 response) is treated as UNKNOWN, and a resend is resolved by a lookup. If the receipt's observation_state is UNKNOWN, it is not matched.
What it does not do / limits
- Deciding the log structure is the role of the Kernel policy-facts, and deciding minimization is the role of the Kernel redact-facts. This Tsugite only checks in front of the sink.
- Detection of sensitive values is an approximation based on character patterns. Free words such as names or addresses are only held back by allowed keys and short values; they are not recognized.
- It does not include an actual sink. Which intake to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- An observation is not a verified fact. verified becomes true only when the receipt shows VERIFIED and a verifier. Success is not approval (approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED). The shape of the payload contents is defined by the Tsugite, not by the contract.
- Besides K-01, this part also serves K-02 (metrics) and K-03 (distributed tracing) as one and the same part, and the tests and inspection are shared. The per-ID rows (K-01, K-02 and K-03) point to this part.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a monitoring service.
How to connect
Contract: erabazu.candidate/certified-tsugite/request-receipt-group/v1 (SinkRequest / SinkReceipt). Input: pass a SinkRequest and the log { records: [...] } as bytes to sink(SinkRequest, bytes) on createSinkClient({ cfg, port }). Returns: one of SUNK, SUNK_PARTIAL, ALREADY_SUNK, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the sink (write, and optionally lookup) and cfg (the pinned sink, the allowed events and keys, and the limits). Side effect: EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
K-02KTsugite
Metrics — Observability Sink Tsugite
The Observability Sink Tsugite (継手, tsugite) takes batches of metrics, which are continuous numeric measurements (records of ts, name, type, value and labels), checks the allowed metrics and labels, the types, the absence of values that look sensitive, the redaction record and the digest, then sends them to a sink and matches the accepted count in the receipt it returns (SinkReceipt). If anything does not match, it does not send and does not accept (fail-closed). Records the sink did not accept are returned as missing.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When metrics are streamed continuously to an external sink, it prevents metrics or labels that were never decided from creeping in, sensitive values in labels from being copied to the sink, and numbers the sink did not accept from silently disappearing. It stops on receipt anomalies such as accepting more records than were sent.
When to use
- You want to send metrics in the form ts, name, type, value and labels to an external sink as continuous numeric measurements.
- You want to decide in advance which metrics (metrics) and labels (metric_labels) may be sent, and send nothing else.
- You want numbers the sink did not accept reported as a fact of missing records (SUNK_PARTIAL and dropped), not silently dropped.
- You want a receipt that reports accepting more records than were sent to be treated as an anomaly, and further sending to stop.
- You do not want the same batch streamed twice. The same request_id is not sent again (ALREADY_SUNK).
When not to use
- You want to judge metric thresholds or raise alerts. Threshold facts are the role of the Kernel monitor-detect, and alerting (K-06) is a separate part.
- You want to put free text or long values in labels. There is no free-text field; only short primitive values under allowed labels are sent.
- You want a sink you can use right away. No actual sink (a provider's metrics intake) is included, and it has not been decided.
- You want to treat the sink's accepted count as verification that measured values are correct. An observation is not a verified fact.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- Records with metrics or labels that are not allowed, or with free-text fields, are not sent (REFUSED).
- It checks for values that look sensitive. If a batch contains email addresses, runs of 10 or more digits, Bearer tokens, JWTs, keys or text that looks like credentials, it is not sent.
- It checks sizes. Anything over the limits on batch size (max_payload_bytes), number of records (max_records) or value length (max_attr_len) is not sent.
- It checks the pinned sink. Nothing is sent to a sink that is not pinned. If the payload has been tampered with (payload_digest mismatch), it is not sent.
- It matches the accepted count in the receipt (SinkReceipt). A receipt accepting more records than were sent, or one with a spoofed request digest, is not accepted (RECEIPT_REJECTED), and it stops from then on (HALTED).
- It requires the redaction record (redaction_manifest_ref) and does not send anything without it. This is stricter than the contract.
- A sink failure (a 500 response) is treated as UNKNOWN, and a resend is resolved by a lookup. If the receipt's observation_state is UNKNOWN, it is not matched.
What it does not do / limits
- It does not judge thresholds. Metric threshold facts are the role of the Kernel monitor-detect, and minimization decisions are the role of the Kernel redact-facts; this Tsugite only checks in front of the sink.
- Detection of sensitive values is an approximation based on character patterns. Free words such as names or addresses are only held back by allowed labels and short values; they are not recognized.
- It does not include an actual sink. Which intake to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- An observation is not a verified fact. verified becomes true only when the receipt shows VERIFIED and a verifier. Success is not approval (approval: false).
- It is not a part dedicated to K-02. It is one and the same part as K-01 (structured logging) and K-03 (distributed tracing), and the tests and inspection are shared. The per-ID rows (K-01, K-02 and K-03) point to this part.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED). The shape of the payload contents is defined by the Tsugite, not by the contract.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a monitoring service.
How to connect
Contract: erabazu.candidate/certified-tsugite/request-receipt-group/v1 (SinkRequest / SinkReceipt). Input: pass a SinkRequest and the metrics { records: [...] } as bytes to sink(SinkRequest, bytes) on createSinkClient({ cfg, port }). Returns: one of SUNK, SUNK_PARTIAL, ALREADY_SUNK, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the sink (write, and optionally lookup) and cfg (the pinned sink, the allowed metrics and labels, and the limits). Side effect: EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
K-03KTsugite
Distributed tracing — Observability Sink Tsugite
The Observability Sink Tsugite (継手, tsugite) takes batches of traces used to follow processing paths (spans of trace_id, span_id, parent_span_id, name, start_ts, end_ts and attrs), checks the span correlation (the same trace, exactly one root span, and every parent in the same batch), the allowed keys, the absence of values that look sensitive, the redaction record and the digest, then sends them to a sink and matches the receipt it returns (SinkReceipt). If anything does not match, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When traces are streamed to an external sink to find the cause of latency, it prevents sending traces with broken correlation (spans from another trace mixed in, spans whose parent cannot be found, multiple roots) or traces that are only a fragment from the middle. It also refuses to let sensitive values mixed into span attrs be copied to the sink.
When to use
- You want to send spans in the form trace_id, span_id, parent_span_id, name, start_ts, end_ts and attrs to an external sink to trace processing paths.
- You want to send a trace only as a complete unit (exactly one root span, with every parent in the same batch).
- You want every trace sent with a correlation reference (correlation_ref). For traces, correlation_ref is required.
- You want to decide in advance which keys (span_attrs) may appear on spans, and send nothing else.
- You want spans the sink did not accept reported as a fact of missing records (SUNK_PARTIAL and dropped), not silently dropped.
When not to use
- You want to send only a fragment from the middle of a trace. Only batches with exactly one root span and every parent in the same batch are sent.
- You want to leave the final judgement on whether trace linkage is correct to this Tsugite. Trace linkage is the role of the Kernel provenance-facts.
- You need error tracking (K-04) or alerting (K-06). Those are separate parts and are not included in this Tsugite.
- You want a sink you can use right away. No actual sink (a provider's trace intake) is included, and it has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks trace correlation. If spans from another trace are mixed in, or a span ends before it starts, the batch is not sent (REFUSED).
- It checks the number of root spans and where parents are. A batch with multiple roots, or containing a span whose parent is outside the batch (orphan), is not sent.
- A trace without a correlation reference (correlation_ref) or a redaction record (redaction_manifest_ref) is not sent. Both are stricter than the contract.
- It checks span attrs. Anything with keys that are not allowed, free-text fields, or email addresses, runs of 10 or more digits, Bearer tokens, JWTs, keys or text that looks like credentials is not sent.
- It checks size and destination. Anything over the limits on batch size, number of records or value length, addressed to a sink that is not pinned, or with a tampered payload (payload_digest mismatch) is not sent.
- It checks the receipt (SinkReceipt). A receipt with a spoofed request digest, or one accepting more records than were sent, is not accepted (RECEIPT_REJECTED), and it stops from then on (HALTED).
- A sink failure (a 500 response) is treated as UNKNOWN, and a resend is resolved by a lookup. The same request_id is not streamed twice (ALREADY_SUNK).
What it does not do / limits
- Deciding trace linkage is the role of the Kernel provenance-facts, and deciding minimization is the role of the Kernel redact-facts. This Tsugite only checks in front of the sink.
- Detection of sensitive values is an approximation based on character patterns. Free words such as names or addresses are only held back by allowed keys and short values; they are not recognized.
- It does not include an actual sink. Which intake to connect to, the port implementation and what to pin are undecided and will be settled at a separate gate when connecting.
- An observation is not a verified fact. verified becomes true only when the receipt shows VERIFIED and a verifier. Success is not approval (approval: false).
- It is not a part dedicated to K-03. It is one and the same part as K-01 (structured logging) and K-02 (metrics), and the tests and inspection are shared. The per-ID rows (K-01, K-02 and K-03) point to this part.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED). The shape of the payload contents is defined by the Tsugite, not by the contract.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a monitoring service.
How to connect
Contract: erabazu.candidate/certified-tsugite/request-receipt-group/v1 (SinkRequest / SinkReceipt). Input: pass a SinkRequest and the span { records: [...] } as bytes to sink(SinkRequest, bytes) on createSinkClient({ cfg, port }). Returns: one of SUNK, SUNK_PARTIAL, ALREADY_SUNK, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the sink (write, and optionally lookup) and cfg (the pinned sink, the allowed span keys, and the limits). Side effect: EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
K-04KTsugite
Error tracking — Observability Extension Tsugite
The Observability Extension Tsugite (継手, tsugite), as K-04 error tracking, checks an error-tracking request (ErrorTrackRequest) against the pinned destinations and a failure reference (failure_ref) that can be resolved before carrying it to the backing service, and matches the returned receipt against the contract. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When error records are sent to an external error-tracking backing service, it prevents sending to a destination that has not been decided, sending a failure reference that cannot be resolved, and treating the record as "recorded" without matching the backing service's receipt. It is a check so that a successful record is not treated as a verified fact.
When to use
- You want to send records of failures such as "it crashes now and then" to an error-tracking backing service.
- You want to decide in advance which error-tracking destinations (sink_refs) may be used, and send to nothing else.
- You want to send a failure reference (failure_ref) only when it can be resolved to an ai-execution-error record.
- You do not want the same error record sent twice. The same request_id is never sent twice, and a resend is resolved by a lookup.
When not to use
- You want to collect the exceptions themselves. This Tsugite carries error-tracking requests and does not include a function for collecting exceptions.
- You want to treat a successful record as a verified fact or as approval. The DONE result is verified_fact: NOT_ASSERTED and approval: false.
- You want the contents of the record that failure_ref points to checked as well. This Tsugite checks only whether it can be resolved.
- You want a backing service you can use right away. No actual backing service (which error tracking, monitoring, notification, flag or metering service) is included, and it has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- Requests to a destination that is not pinned are not sent.
- A request carrying failure_ref is not sent (REFUSED) when the port's resolveFailureRef cannot resolve it to an ai-execution-error record, or when there is no means of resolving it.
- It does not send when a reference is empty, contains a path or ".." (path traversal), or has additional fields. Only references in the pinned lists are allowed, and an empty list allows nothing.
- The same request_id is never sent twice (ALREADY_DONE). A request with the same idempotency key but different content is not sent (REFUSED), and a duplicate (DEDUP_HIT) receipt that claims a new effect is not accepted.
- It matches the receipt. A spoofed request_digest, a receipt that increases the external effect, a receipt whose effect is unknown (UNKNOWN) and a receipt for a different request are not accepted (RECEIPT_REJECTED), and it stops from then on (HALTED).
- A backing-service failure is returned as UNKNOWN. A resend is resolved by a lookup.
What it does not do / limits
- It does not include a function for collecting exceptions. It only checks error-tracking requests, carries them to the backing service and matches the receipt.
- It does not check the contents of the ai-execution-error record that failure_ref points to (only whether it can be resolved).
- It does not treat a successful record as a verified fact (verified_fact: NOT_ASSERTED).
- A successful record is not approval (approval: false).
- Judging authorization, semantic correctness and business facts is the role of the Kernels (monitor-detect and policy-facts) and of people. This Tsugite only checks before sending and matches after receiving.
- It does not include an actual backing service. Which service to connect to, the port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/observability-ext/v1. Returns: submit(request) on createObservabilityExtClient({ cfg, port }) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. DONE carries track_state, observation_state and verified_fact: NOT_ASSERTED in its result. User provides: cfg (the pinned sink_refs and the other lists) and a port (submit, optionally lookup, and resolveFailureRef for failure_ref). Side effect: at most EXTERNAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
K-05KTsugite
Health check — Observability Extension Tsugite
The Observability Extension Tsugite (継手, tsugite), as K-05 health check, checks a health-check request (HealthCheckRequest) against the pinned targets before carrying it to the backing service, and matches the returned receipt against the contract. It does not mistake a health PASS for semantic correctness, and if any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When checking liveness and readiness, it prevents querying a target that has not been decided, and misreading a health PASS as "the contents are correct too". A receipt that claims semantic correctness is not accepted.
When to use
- You want to carry liveness and readiness health-check requests to the backing service.
- You want to decide in advance which targets (health_targets) may be queried, and query nothing else.
- You want to receive the health result (health_state) and semantic correctness (semantic_correctness) separately.
When not to use
- You want to use a health PASS as proof that processing results are semantically correct. This Tsugite does not derive semantic correctness from a PASS.
- You want to detect anomalies and recover automatically. This Tsugite carries health-check requests and does not include a recovery function.
- You want to judge anomalies by thresholds. Thresholds and the facts of detection are the role of the Kernel (monitor-detect).
- You want a backing service you can use right away. No actual backing service (which error tracking, monitoring, notification, flag or metering service) is included, and it has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- Health-check requests to a target that is not pinned are not sent.
- A health receipt that claims semantic correctness is not accepted.
- The effect of a health check is NONE. A receipt that claims an external effect is not accepted, as it increases the effect.
- It does not send when a reference is empty, contains a path or ".." (path traversal), or has additional fields. Only references in the pinned lists are allowed, and an empty list allows nothing.
- The same request_id is never sent twice (ALREADY_DONE). A request with the same idempotency key but different content is not sent (REFUSED), and a duplicate (DEDUP_HIT) receipt that claims a new effect is not accepted.
- It matches the receipt. A spoofed request_digest, a receipt that increases the external effect, a receipt whose effect is unknown (UNKNOWN) and a receipt for a different request are not accepted (RECEIPT_REJECTED), and it stops from then on (HALTED).
- A backing-service failure is returned as UNKNOWN. A resend is resolved by a lookup.
What it does not do / limits
- It does not derive semantic correctness from a health PASS.
- It does not include a recovery function. It only checks health-check requests, carries them to the backing service and matches the receipt.
- A health result is not approval (approval: false).
- Judging authorization, semantic correctness and business facts is the role of the Kernels (monitor-detect and policy-facts) and of people. This Tsugite only checks before sending and matches after receiving.
- It does not include an actual backing service. Which service to connect to, the port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/observability-ext/v1. Returns: submit(request) on createObservabilityExtClient({ cfg, port }) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. DONE carries health_state and semantic_correctness in its result. User provides: cfg (the pinned lists such as health_targets) and a port (submit, and optionally lookup). Side effect: NONE for health checks.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
K-06KTsugite
Alerting and on-call — Observability Extension Tsugite
The Observability Extension Tsugite (継手, tsugite), as K-06 alerting and on-call, checks an alert request (AlertRequest) against the pinned alerts and destinations before carrying it to the backing service, and matches the returned receipt against the contract. It returns a delivered alert and an acknowledged alert separately, and if any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When reporting anomalies, it prevents sending alerts to a destination that has not been decided or to an arbitrary URL, and misreading "delivered" as "acknowledged".
When to use
- You want to carry anomaly-report (alert) requests to the backing service.
- You want to pin alerts (alert_refs) and destinations (alert_destinations) so that nothing is sent to an arbitrary URL.
- You want to receive delivered (delivered) and acknowledged (acknowledged) separately.
When not to use
- You want to confirm that an alert actually reached a person or was acted on. This Tsugite only returns the receipt's state and does not confirm it.
- You want to build on-call rotations or escalate calls step by step. This Tsugite carries alert requests and does not include those functions.
- You want to judge anomalies by thresholds. Thresholds and the facts of detection are the role of the Kernel (monitor-detect), and the alert policy is the role of the Kernel (policy-facts).
- You want a backing service you can use right away. No actual backing service (which error tracking, monitoring, notification, flag or metering service) is included, and it has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- Requests for an alert that is not pinned, or to a destination that is not pinned, are not sent. Requests that inject an arbitrary URL as the destination (destination injection) are not sent either.
- delivered is allowed only when the state is DELIVERED or ACKNOWLEDGED, and acknowledged only when it is ACKNOWLEDGED. A receipt that claims delivery without an external effect is not accepted.
- It does not send when a reference is empty, contains a path or ".." (path traversal), or has additional fields. Only references in the pinned lists are allowed, and an empty list allows nothing.
- The same request_id is never sent twice (ALREADY_DONE). A request with the same idempotency key but different content is not sent (REFUSED), and a duplicate (DEDUP_HIT) receipt that claims a new effect is not accepted.
- It matches the receipt. A spoofed request_digest, a receipt that increases the external effect, a receipt whose effect is unknown (UNKNOWN) and a receipt for a different request are not accepted (RECEIPT_REJECTED), and it stops from then on (HALTED).
- A backing-service failure is returned as UNKNOWN. A resend is resolved by a lookup.
What it does not do / limits
- It does not confirm whether an alert actually reached a person or was acted on. It only returns the receipt's state.
- It does not include on-call rotation or call functions. It only checks alert requests, carries them to the backing service and matches the receipt.
- Delivering an alert is not approval (approval: false).
- Judging authorization, semantic correctness and business facts is the role of the Kernels (monitor-detect and policy-facts) and of people. This Tsugite only checks before sending and matches after receiving.
- It does not include an actual backing service. Which service to connect to, the port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/observability-ext/v1. Returns: submit(request) on createObservabilityExtClient({ cfg, port }) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. DONE carries alert_state, delivered and acknowledged in its result. User provides: cfg with the pinned lists (such as alert_refs and alert_destinations) and a port (submit, and optionally lookup). Side effect: EXTERNAL_REVERSIBLE (NONE for DEDUP_HIT and FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
K-07KTsugite
Feature flags — Observability Extension Tsugite
The Observability Extension Tsugite (継手, tsugite), as K-07 feature flags, checks a feature-flag request (FeatureFlagRequest: READ, SET or CLEAR) against the pinned flags and values before carrying it to the backing service, and matches the returned receipt against the contract. It does not derive authorization from a flag's state, and if any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When reading and writing feature flags to switch features for different users, it prevents setting flags or values that have not been decided, and misreading a set flag as "authorized". A flag receipt that claims authorization is not accepted.
When to use
- You want to carry READ, SET and CLEAR feature-flag requests to the backing service.
- You want to decide in advance which flags (flag_refs) and values (flag_value_refs) may be handled, and handle nothing else.
- You want to receive the flag state (flag_state) and authorization (authorization) separately.
When not to use
- You want to use a flag's state as authorization for a user or a feature. This Tsugite does not derive authorization from a flag's state.
- You want to receive the flag's value itself with READ. The value is not in the receipt, and this Tsugite returns only the state.
- You want to decide the percentage or targets of a staged rollout. This Tsugite carries feature-flag requests and does not include a function for deciding who gets which feature. The flag policy is the role of the Kernel (policy-facts).
- You want a backing service you can use right away. No actual backing service (which error tracking, monitoring, notification, flag or metering service) is included, and it has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- Requests for a flag or a value that is not pinned are not sent.
- value_ref is required only for SET, and a READ request carrying a value is not sent. A request whose op and effect do not match (READ is NONE; SET and CLEAR are EXTERNAL_REVERSIBLE) is not sent either.
- A receipt whose state does not answer the op (anything other than READ→READ, SET→SET, CLEAR→CLEARED or FAILED) is not accepted.
- A flag receipt that claims authorization is not accepted.
- It does not send when a reference is empty, contains a path or ".." (path traversal), or has additional fields. Only references in the pinned lists are allowed, and an empty list allows nothing.
- The same request_id is never sent twice (ALREADY_DONE). A request with the same idempotency key but different content is not sent (REFUSED), and a duplicate (DEDUP_HIT) receipt that claims a new effect is not accepted.
- It matches the receipt. A spoofed request_digest, a receipt that increases the external effect, a receipt whose effect is unknown (UNKNOWN) and a receipt for a different request are not accepted (RECEIPT_REJECTED), and it stops from then on (HALTED).
- A backing-service failure is returned as UNKNOWN. A resend is resolved by a lookup.
What it does not do / limits
- It does not derive authorization from a flag's state.
- It does not return the flag's value itself (what READ returns). It returns only the state.
- It does not include a function for deciding who gets which feature. It only checks feature-flag requests, carries them to the backing service and matches the receipt.
- Setting a flag is not approval (approval: false).
- Judging authorization, semantic correctness and business facts is the role of the Kernels (monitor-detect and policy-facts) and of people. This Tsugite only checks before sending and matches after receiving.
- It does not include an actual backing service. Which service to connect to, the port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/observability-ext/v1. Returns: submit(request) on createObservabilityExtClient({ cfg, port }) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. DONE carries flag_state and authorization in its result. User provides: cfg with the pinned lists (such as flag_refs and flag_value_refs) and a port (submit, and optionally lookup). Side effect: NONE for READ; EXTERNAL_REVERSIBLE for SET and CLEAR (NONE for FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
K-08KTsugite
Usage meter — Observability Extension Tsugite
The Observability Extension Tsugite (継手, tsugite), as K-08 usage meter, checks a request to record a usage event (UsageMeterRequest) against the pinned meters and units and the quantity limit before carrying it to the backing service, and matches the returned receipt against the contract. It does not bill, and it does not check that the quantity is correct. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When usage is recorded to an external metering backing service, it prevents recording with meters or units that have not been decided, sending fractional, negative or over-limit quantities, and misreading a recorded quantity as a verified business fact.
When to use
- You want to carry usage events to a metering backing service.
- You want to decide in advance which meters (meter_refs) and units (unit_refs) may be recorded and the quantity limit (max_quantity), and send nothing else.
- You do not want the same usage recorded twice. A different quantity under the same idempotency key is not sent, and the same request_id is never sent twice.
When not to use
- You want to bill. This Tsugite carries usage events and does not include a billing function.
- You want to use a recorded quantity for billing as is. It does not check that the quantity is correct (whether it can be used for billing), and it is not a verified business fact.
- You want to record fractional quantities. Quantities must be integers of 0 or more; a fractional quantity cannot be given a digest and is not sent.
- You want a backing service you can use right away. No actual backing service (which error tracking, monitoring, notification, flag or metering service) is included, and it has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- Requests for a meter or a unit that is not pinned are not sent.
- A quantity that is fractional, negative or over the limit (max_quantity) is not sent. max_quantity is required when recording usage.
- A receipt that claims the quantity is a verified business fact is not accepted.
- It does not send when a reference is empty, contains a path or ".." (path traversal), or has additional fields. Only references in the pinned lists are allowed, and an empty list allows nothing.
- The same request_id is never sent twice (ALREADY_DONE). A request with the same idempotency key but different content is not sent (REFUSED), and a duplicate (DEDUP_HIT) receipt that claims a new effect is not accepted.
- It matches the receipt. A spoofed request_digest, a receipt that increases the external effect, a receipt whose effect is unknown (UNKNOWN) and a receipt for a different request are not accepted (RECEIPT_REJECTED), and it stops from then on (HALTED).
- A backing-service failure is returned as UNKNOWN. A resend is resolved by a lookup.
What it does not do / limits
- It does not bill. It only checks usage events, carries them to the backing service and matches the receipt.
- It does not check that the quantity is correct (whether it can be used for billing). A recorded quantity is not a verified business fact.
- It does not handle fractional quantities (the contract allows a number, but this Tsugite narrows it to integers of 0 or more).
- Successfully recording usage is not approval (approval: false).
- Judging authorization, semantic correctness and business facts is the role of the Kernels (monitor-detect and policy-facts) and of people. This Tsugite only checks before sending and matches after receiving.
- It does not include an actual backing service. Which service to connect to, the port implementation and the pinned lists are undecided and will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/observability-ext/v1. Returns: submit(request) on createObservabilityExtClient({ cfg, port }) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. DONE carries meter_state and business_fact_verification in its result. User provides: cfg with the pinned lists (such as meter_refs and unit_refs) and max_quantity, and a port (submit, and optionally lookup). Side effect: EXTERNAL_REVERSIBLE (NONE for DEDUP_HIT and FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
L-01LPrimitive
Time zone conversion — Time zone conversion part (Primitive)
The Time zone conversion part (Primitive) is a standalone part that converts between epoch milliseconds (a safe integer) and the wall-clock time (year, month, day, hour, minute, second and millisecond) in an IANA zone. The zone rules follow Node.js's built-in Intl (ICU's tz data), and every result carries the tz data and ICU versions. It does not silently fix a wall-clock time that does not exist or that occurs twice; it returns { ok, errors } and does not throw (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind "times are off", it prevents silently moving a time that does not exist because of a daylight-saving jump, or a time that occurs twice because of an overlap, to some other time, and prevents accepting fields such as 30 February, hour 24 or second 60 by carrying them over. Because results carry the tz data and ICU versions, you can check afterwards which rules produced an answer.
When to use
- You want to turn an instant in epoch milliseconds into the wall-clock time in an IANA zone (for example Asia/Tokyo) and its offset from UTC (toZoned).
- You want to turn a wall-clock time in a zone into an instant in epoch milliseconds (fromZoned). A wall-clock time that occurs twice is stopped by default; only when earlier or later is given explicitly does it choose the earlier or the later one.
- When a wall-clock time that occurs twice is stopped, you want to receive the candidate instants, earlier and later (candidates_ms), and decide on the caller's side which to use.
- You want to keep, together with the result, which tz data and ICU versions produced the answer (tzdata's tz, icu and matches_pin).
- You need conversion that does not throw. Neither toZoned nor fromZoned throws an exception, and neither rewrites its input.
- You want to call it locally, as processing with no outside boundary that reads neither the current time, random numbers nor the environment's locale. It has no dependency packages.
When not to use
- You need the same answer regardless of the tz data or ICU version. Answers depend on this execution environment's tz data (2026c) and ICU (78.3), and if the versions change, the same input may give a different answer. Other versions have not been checked (tzdata.matches_pin becomes false).
- You handle times before 1970 or after 2100. It stops them with OUT_OF_RANGE.
- You want to handle leap seconds. Instants are POSIX time, which does not count leap seconds, and second 60 is stopped with INVALID_DATE.
- You want to pass a zone written as an offset from UTC, such as +09:00. A zone must have the form of an IANA name; anything else is stopped with INVALID_ZONE.
- You want a wall-clock time that does not exist to be moved to a nearby time. It stops with NONEXISTENT_LOCAL_TIME and, whatever disambiguation says, does not move it to another time.
- You use it on a version other than Node.js v24.21.0 (tz data 2026c, ICU 78.3). Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with INVALID_INSTANT when the instant is not a safe integer (a fraction, NaN, Infinity, a string, a Date or a bigint), and with OUT_OF_RANGE when the instant or the year is outside 1970 to 2100.
- It stops with INVALID_ZONE when zone is not a string, is longer than 64 characters or does not have the form of an IANA name (an offset notation, whitespace or an empty segment), and with UNKNOWN_ZONE when the form fits but this execution environment's Intl does not know the zone.
- It stops with INVALID_LOCAL when the wall-clock time is not a plain object, lacks a required field, has other keys, a getter, a non-enumerable property or a symbol key, or has a value that is not an integer. It does not call getters.
- When a field's value is out of range (30 February, 29 February in a common year or in 2100, 31 April, month 13, hour 24, minute 60, second 60, millisecond 1000 or a negative value), it stops with INVALID_DATE without carrying over.
- It stops with NONEXISTENT_LOCAL_TIME for a wall-clock time that does not exist because of a daylight-saving jump, and with AMBIGUOUS_LOCAL_TIME for one that occurs twice because of an overlap (when disambiguation is reject, the default), and attaches the candidates.
- It stops with INVALID_OPTIONS when options is not a plain object, has other keys or a getter, or disambiguation is not reject, earlier or later, and with INSPECTION_FAILED when inspecting the value itself raises an exception (a Proxy trap and so on).
- Even when the wall-clock time is within range, if the converted instant falls outside the range (for example 1970-01-01 08:59 in Asia/Tokyo), it stops with OUT_OF_RANGE.
What it does not do / limits
- It holds no tz data of its own. The conversion rules are those of Node.js's ICU, and answers are correct only when this execution environment's tz data is correct. Future times are projections under the rules as of when that tz data was made, and rule changes (such as abolishing daylight saving time) are not reflected until a tz data version containing them is released.
- Answers were independently cross-checked only for UTC and Asia/Tokyo (1970 to 2100) and for America/New_York and Europe/London (2010 to 2035). Other zones have only round-trip and property checks; their answers have not been separately confirmed.
- fromZoned builds candidates from the offsets seen every 3 hours over the 15 hours before and after. In zones and periods where transitions follow one another at intervals shorter than 3 hours, it may miss candidates (whether this happens in the tz data for 1970 to 2100 has not been fully checked).
- It does not judge meaning such as which time is the cutoff, which zone to use, or business days. That stays with the caller (the Kernel and the business).
- Compatibility of answers with Temporal or outside libraries has not been checked. Proxy traps were checked only for the operations this part inspects (getPrototypeOf, ownKeys and getOwnPropertyDescriptor).
- Being able to convert does not mean the time is correct or that it may be accepted (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular date library.
How to connect
Call toZoned(instantMs, zone) and fromZoned(local, zone, options) in src/timezone.js (they do not throw). Input: epoch milliseconds or a wall-clock time, an IANA zone name, and an optional { disambiguation }. Returns: { ok: true, errors: [], local or instant_ms, offset_seconds, zone, resolved_zone, tzdata, … } or { ok: false, errors: [{ code }], tzdata } (code is from error_taxonomy). Side effect: NONE. There is no outside connection and no credential, and the caller provides nothing. The state is a cache of formatters (only within the process) and does not affect the answers.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
L-02LTsugite
Machine translation transport (i18n) — Localization Transport Tsugite
The Localization Transport Tsugite (継手, tsugite), for the machine translation transport (i18n) item, checks a machine-translation request (TranslationRequest) against the pinned backends and locales (source and target), carries it to a translation backend, and matches the returned receipt (TranslationReceipt) against the contract. It carries only references, not the text itself. It does not switch display text and does not manage catalogs. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When carrying requests to a translation backend, it prevents sending the text itself, sending to a backend or locale that is not pinned, and mistaking a machine translation for a human-verified translation or an authoritative text.
When to use
- You want to carry machine-translation requests to a pinned translation backend using only references, not the text itself.
- You want to limit the source and target locales to pinned lists and not send requests whose source and target locales are the same.
- You want to accept the returned receipt only after matching request_id, request_digest and references. The DONE result carries translation_state, output_ref, human_verification and authoritative_status.
- Even if the result becomes unknown (UNKNOWN) because of a backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want to switch display text by language, or to manage a multilingual text catalog. This Tsugite carries machine-translation requests and does not include any function for switching text or managing catalogs.
- You want to treat machine-translation output as a human-verified translation or an authoritative text. Receipts that claim so are not accepted.
- You want to check that a translation is correct, or to obtain approval to publish translated text. Checking translations and approving publication are the role of external judgement and of people and organizations.
- You want localization that local computation can handle, such as time conversion, currency or number formatting, unit conversion or sorting. That is not in scope for this Tsugite.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a backend you can use right away. No actual translation, address or reference-data backend is included.
What it checks
- Requests to a backend or locale that is not pinned are not sent. Requests are also not sent when a configured list is empty or contains an empty entry (an empty list allows nothing).
- Requests with an empty reference, a reference containing a path or .., additional fields, or a value different from the fixed value (const) are not sent.
- Requests whose source and target locales are the same are not sent.
- Receipts that claim a human-verified translation (human verification) or an authoritative text (authoritative) are not accepted.
- Receipts that report success (PRODUCED) without an output reference (output_ref), and receipts that report failure (FAILED or UNAVAILABLE) yet claim an output reference or an effect, are not accepted.
- Requests whose side effect differs from the intent are not sent. Receipts that increase the external effect beyond the intent and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted, and when an unacceptable receipt arrives, it stops (HALTED).
What it does not do / limits
- It does not switch display text and does not manage multilingual catalogs. This Tsugite carries machine-translation requests and does not include those functions.
- A machine translation is neither a human-verified translation nor an authoritative text. It does not check whether a translation is correct.
- No result is approval (approval: false). Translated or found is not approval to publish, not confirmation of a deliverable address, and not authoritative.
- It does not include an actual translation, address or reference-data backend and has never connected to one. The port implementation and the pinned lists will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for using any specific translation, map or calendar service.
How to connect
Contract: erabazu.candidate/certified-tsugite/localization/v1. Input: pass a TranslationRequest to submit(request) on createLocalizationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: cfg (the pinned lists backends, locales, regions and datasets) and a port to the backend (submit, and optionally lookup). Secrets are held by the port. Side effect: EXTERNAL_REVERSIBLE (NONE when FAILED or UNAVAILABLE).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
L-03LPrimitive
Currency formatting — Currency formatting part (Primitive)
The Currency formatting part (Primitive) is a standalone part that turns a safe integer counted in a currency's minor unit (amount_minor) into a string in a fixed notation, using a currency table (7 currencies) and a locale table (ja-JP and en-US) fixed inside the part. It only formats for display; it does not do currency conversion, rounding policy or payment decisions. Values it cannot accept are stopped with fixed codes, and it does not throw (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind the display, it prevents the notation from changing with the internationalization API (ICU) of the environment it runs in, the notation no longer mapping back to the original integer because digits were dropped or rounded, and currencies or locales not in the table, or extra keys (meant as rounding settings and the like), being silently mapped to something close or ignored.
When to use
- You want to turn a safe integer already expressed in a currency's minor unit into a notation string with a symbol and digit grouping, for a currency in the table (the currencies of Japan and the United States, and EUR, GBP, KRW, CNY and BHD) and a locale in the table (ja-JP or en-US) (formatMinor).
- You need a notation that does not depend on the environment it runs in. It uses no clock, no random numbers, no environment locale and no internationalization API (ICU); the tables are fixed inside this part's file, and the same input always gives the same text.
- You need the notation, when turned back into the minor unit, to return exactly to the absolute value of the integer received (no digits dropped, no rounding).
- You want every applicable problem with an input it cannot accept to be returned with the field (field) and a code.
- You need formatting that does not throw. formatMinor does not throw, does not rewrite its input and does not call getters.
When not to use
- You want to pass a value with a fraction or a value not yet in the minor unit. This part accepts integers only and does no rounding either. The rounding policy and conversion used when going to the minor unit are the role of the caller or of the G-series parts.
- You want to handle currencies or locales other than the 7 currencies and 2 locales in the table. It stops with UNKNOWN_CURRENCY or UNKNOWN_LOCALE and does not map ja-jp, en-GB, ja and the like to a close match.
- You need the same output as CLDR or Intl.NumberFormat. It claims no match. How symbols are chosen and written is this part's own rule: the symbol for Japan's currency is the half-width U+00A5 (not the full-width U+FFE5 of CLDR's ja), and BHD is written as its ISO 4217 code followed by a space (U+0020).
- You need the symbol placed after the number, other digit grouping (periods, spaces or Indian 2-digit grouping), accounting notation (negative numbers in parentheses), or full-width digits and symbols.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with INVALID_INPUT when the input is not an object (null, an array, a number, a string and so on), when a key is a getter or setter, or when inspecting the input itself raises an exception (a Proxy trap and so on). It does not call getters.
- When the input has keys other than amount_minor, currency and locale (including non-enumerable keys and symbol keys), it stops with UNKNOWN_FIELD instead of silently ignoring them. It does not read inherited keys.
- It stops with INVALID_AMOUNT when amount_minor is missing, is not a number (a bigint or a string), is not an integer, is outside the safe integer range, or is NaN or Infinity.
- It stops with UNKNOWN_CURRENCY when currency is missing, is not a string, or is not a code in the table (including lowercase, surrounding whitespace, and inherited names such as __proto__).
- It stops with UNKNOWN_LOCALE when locale is missing, is not a string, or is not a locale in the table, without mapping it to a close locale.
- When several fields cannot be accepted, it lists every applicable problem in errors in the order amount_minor → currency → locale (only one when the input has the wrong shape or has an extra key).
What it does not do / limits
- The Currency formatting part only formats for display; it does not include currency conversion, rounding, tax, billing or payment decisions. Those stay with the G-series parts and the caller.
- It does not check what the received integer means (the rounding used when it was put into the minor unit, or whether the currency is correct). It writes the received integer as it is.
- It does not handle anything other than the 7 currencies and 2 locales in the table. The output for ja-JP and en-US is the same (comma for digit grouping, period for the decimal point, grouping every 3 digits, symbol before the number), and it creates no per-locale differences.
- Symbols are the Unicode characters as they are, with no normalization. The tables are not a copy of CLDR or of each country's practice but this part's own rule. The tables are frozen, and the caller cannot rewrite them.
- Producing a notation string does not mean that the figure is correct or that billing or payment may go ahead (result_is_approval: false). It creates no authority either (derives_authority: false).
- The workshop's definition tsugite-l-03 is only an inspection type; it has no formatting implementation, so there is nothing to compare against. This part is a candidate to become the formatting implementation.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular internationalization library.
How to connect
Call formatMinor({ amount_minor, currency, locale }) in src/currency.js (it does not throw). Input: an object with only those three keys; amount_minor is a safe integer in the minor unit, currency is an ISO 4217 code in the table, and locale is ja-JP or en-US. Returns: { ok: true, errors: [], text } or { ok: false, errors: [{ code, field }], text: null } (code is from error_taxonomy). text is, in order, a sign only when negative, the symbol from the table, the integer part grouped every 3 digits, and a fractional part with as many digits as the minor unit has. Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
L-04LPrimitive
Unit conversion — Unit conversion part (Primitive)
The Unit conversion part (Primitive) is a standalone part that converts a value given as a decimal string using exact rational coefficients as defined (a BigInt numerator and denominator), and returns a decimal string rounded half-even to 0 to 18 fractional digits. Length, mass and pressure are proportional, temperature (°C, °F and K) is linear, and no floating point is used. The dB family (dBm↔mW) is not in v1. Values it cannot accept are stopped with fixed codes, and it does not throw (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents floating-point error from entering midway through a conversion ("0.1" no longer being exactly 1/10 and so on), silently computing a conversion between different dimensions or a temperature below absolute zero, and silently mapping similarly spelled units or extra keys (meant as precision settings and the like) to something close or ignoring them.
When to use
- You want to convert a value within the same dimension: length (m, km, cm, mm, in, ft, mi), mass (kg, g, lb, oz), pressure (Pa, kPa, MPa, bar, psi) or temperature (K, C, F) (convert).
- You want the result of the conversion as a value rounded half-even only once, from the exact rational number to fraction_digits fractional digits (no rounding midway).
- You need the coefficients to come from defined values (in = 0.0254 m, lb = 0.45359237 kg, bar = 100000 Pa, K = °C + 273.15 and so on).
- You want to use it as the conversion rule when injecting derived values.
- You want to call it locally, as processing with no outside boundary and no state, unaffected by the clock, random numbers or the environment's locale. convert does not throw, does not rewrite its input and does not call getters.
When not to use
- You want to convert the dB family (dBm↔mW, dB and so on). The catalog's one-liner says "dBm/MPa etc.", but the dB family is logarithmic and its results are not rational numbers, so it is not in v1 and is stopped with UNKNOWN_UNIT as a unit not in the table.
- You want to use units other than the 19 in the table. It does not automatically build prefix combinations such as μm or hPa.
- You want to convert temperature differences (Δ°C ↔ Δ°F). Temperatures are converted as absolute temperatures, and values below absolute zero are stopped.
- You need significant figures, propagation of error (uncertainty), or rounding other than half-even.
- You need locale-dependent notation (a comma as the decimal point and so on) or output with digit grouping. The output is only a decimal string without digit grouping.
- You need the same results as other implementations that convert with floating point. Results may differ near rounding boundaries.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with INVALID_INPUT when the input is not an object, when a key is a getter or setter, or when inspecting the input itself raises an exception (a Proxy trap and so on), and with UNKNOWN_FIELD when it has keys other than the five (including non-enumerable keys and symbol keys).
- It stops with MALFORMED_VALUE when value is not a string, is longer than 64 characters, or does not fit the decimal grammar (exponents, hexadecimal, NaN, Infinity, +, whitespace, digit grouping, "1.", ".5", extra leading zeros, or non-ASCII digits).
- It stops with UNKNOWN_UNIT when from or to is not a unit in the table (including the dB family such as dBm and mW, similar spellings such as "°C", "KM" and "kpa", and inherited names such as __proto__).
- When from and to have different dimensions, it stops with INCOMPATIBLE_UNITS instead of silently computing.
- When a temperature value is below absolute zero (0 K = -273.15 °C = -459.67 °F), it stops with BELOW_ABSOLUTE_ZERO. Exactly absolute zero is accepted.
- It stops with INVALID_FRACTION_DIGITS when fraction_digits is not an integer from 0 to 18, and with UNSUPPORTED_ROUNDING when rounding is missing or is not exactly "half-even".
- When several fields cannot be accepted, it lists every applicable problem in errors in the order value → from → to → fraction_digits → rounding.
What it does not do / limits
- The Unit conversion part does not include conversion of the dB family, including the dBm in the catalog's one-liner. v1 covers only length, mass, pressure (including MPa) and temperature, which can be written exactly as rational numbers.
- It does not decide which values may be converted, nor does it reconcile (reconcile). That stays with the caller.
- The coefficient values were copied from the definitions, and the oracle was built from the same definitions, so a copying error in a definition itself could enter both in the same way. What was checked is limited to known-answer tests (1 in = 25.4 mm, 1 lb = 453.59237 g, 1 psi = 6894.757293168361 Pa and so on).
- Unit names are the ASCII symbols as they are ("C" is Celsius, not the coulomb).
- Being able to convert does not mean the original value or the choice of unit is correct, or that design or decisions may be based on the value (result_is_approval: false). It creates no authority either (derives_authority: false).
- The workshop's definition tsugite-l-04 is only an inspection type; it has no conversion implementation, so there is nothing to compare against.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular unit conversion library.
How to connect
Call convert({ value, from, to, fraction_digits, rounding }) in src/unitconvert.js (it does not throw). Input: an object with only those five keys; value is a decimal string of up to 64 characters, from and to are unit names in the table, fraction_digits is an integer from 0 to 18, and rounding is exactly "half-even" (it cannot be omitted). Returns: { ok: true, errors: [], value } or { ok: false, errors: [{ code, field }], value: null } (code is from error_taxonomy). Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
L-05LTsugite
Address candidate lookup — Localization Transport Tsugite
The Localization Transport Tsugite (継手, tsugite), for the address candidate lookup item, checks an address candidate lookup request (AddressLookupRequest) against the pinned backends and regions, carries it to an address backend, and matches the returned receipt (AddressLookupReceipt) against the contract. It carries only references, not the address text. It does not verify that an address is deliverable and does not prove residence. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When carrying lookups to an address backend, it prevents sending the address text itself, sending to a backend or region that is not pinned, and mistaking a found address candidate for confirmation of a deliverable address or proof of residence.
When to use
- You want to carry address candidate lookups to a pinned address backend using only a reference (query_ref), not the address text.
- You want to limit the regions looked up to a pinned list.
- You want to carry the lookup as one with no external effect (side effect NONE).
- You want to accept the returned receipt only after matching request_id, request_digest and references. The DONE result carries lookup_state, candidate_ref, deliverability and residence_proof.
- Even if the result becomes unknown (UNKNOWN) because of a backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want to verify whether an address is deliverable (whether delivery is possible). An address candidate is not confirmation of a deliverable address, and receipts that claim so are not accepted.
- You want proof that a person lives at an address. An address candidate is not proof of residence. Proving a person's address is the role of external judgement and of people and organizations.
- You want the mechanism that unifies variant address notations (normalization) itself. This Tsugite carries address candidate lookup requests and does not include any normalization function.
- You want to send the address text to the backend. Only references are carried, and a reference containing whitespace is treated as address text and not sent.
- You want localization that local computation can handle, such as phone number normalization. That is not in scope for this Tsugite.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a backend you can use right away. No actual translation, address or reference-data backend is included.
What it checks
- Requests to a backend or region that is not pinned are not sent. Requests are also not sent when a configured list is empty or contains an empty entry (an empty list allows nothing).
- If the lookup reference (query_ref) contains whitespace, it is treated as address text and not sent.
- Requests with an empty reference, a reference containing a path or .., additional fields, or a value different from the fixed value (const) are not sent.
- Receipts that claim confirmation of a deliverable address (deliverability) or proof of residence (residence proof) are not accepted.
- Receipts that report not found (NOT_FOUND) yet carry a candidate reference (candidate_ref), and receipts that report failure yet claim an output reference or an effect, are not accepted.
- Requests whose side effect differs from the intent are not sent. Receipts that increase the external effect beyond the intent and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted, and when an unacceptable receipt arrives, it stops (HALTED).
What it does not do / limits
- It does not verify that an address is deliverable and does not prove residence. An address candidate is neither of these. Confirming a deliverable address and proving a person's address are the role of external judgement and of people and organizations.
- It does not include any address normalization function (unifying variant notations). This Tsugite carries address candidate lookup requests.
- It does not check whether an address candidate is correct. Detection of address text is also only a net that looks for whitespace and does not recognize every address text; the main protection is the design of carrying only references.
- No result is approval (approval: false). Translated or found is not approval to publish, not confirmation of a deliverable address, and not authoritative.
- It does not include an actual translation, address or reference-data backend and has never connected to one. The port implementation and the pinned lists will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for using any specific translation, map or calendar service.
How to connect
Contract: erabazu.candidate/certified-tsugite/localization/v1. Input: pass a AddressLookupRequest to submit(request) on createLocalizationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: cfg (the pinned lists backends, locales, regions and datasets) and a port to the backend (submit, and optionally lookup). Secrets are held by the port. Side effect: NONE (an address lookup).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
L-06LPrimitive
Phone number normalization (E.164) — Phone number normalization (E.164) part (Primitive)
The Phone number normalization (E.164) part (Primitive) is a standalone part that turns a phone number string into E.164 ("+", the country code, then the national significant number, NSN). The only table it uses is the small versioned table this part holds (erabazu-e164-metadata/2026-09-30), which covers three regions: JP (+81), NANP (US/CA, +1) and GB (+44). Values it cannot accept are stopped with fixed codes (fail-closed). Being able to normalize is not proof that the number exists or can be reached.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind SMS, it brings variations in how recipient numbers are written (spaces, hyphens, dots, parentheses, national and international forms) into a fixed form. In doing so, it does not guess a country code or region not in the table, and does not silently drop an extension or another international prefix; it stops with a fixed code.
When to use
- You want to receive a JP, NANP (US/CA) or GB phone number string as an E.164 string, split into the table's region, country code and NSN (normalize).
- You want to turn a number in national form (not starting with +) into E.164 with the national prefix (trunk) removed, passing default_region (JP, US, CA or GB).
- You want to produce a string that passes the SMS recipient format check. This part's output always matches the notification Tsugite's SMS recipient format check (/^\+[1-9][0-9]{6,14}$/) (this was cross-checked once in the workspace and is not included in the part's tests).
- You want to keep, together with the result, which table version it was normalized with. Every result carries metadata_revision.
- You need a way of calling it that does not throw. normalize does not throw, does not rewrite its input (including options) and does not call getters on options.
When not to use
- You want to confirm that a number exists, is in use, can be reached or belongs to someone. This part does not confirm that (existence_verified is always false). Confirming it needs a separate means (an SMS confirmation code and so on).
- You want to check against the whole numbering plan (allocated number ranges, the list of area codes, number types). It also accepts numbers that merely match the table's lengths and leading digits.
- You handle regions other than JP, NANP and GB, or you want to tell US and CA apart. region is the table's heading (NANP), not the country where the line is.
- You want numbers with extensions, numbers starting with another international prefix (00, 010, 011), full-width digits, or mnemonic letters to be converted. v1 does not accept them.
- You want changes to numbering plans to be tracked automatically. The table's values are conservative ones made on 2026-09-30.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with NOT_A_STRING when text is not a string, and with INPUT_TOO_LONG, without reading the content, when it is longer than 64 characters.
- It stops with INVALID_OPTIONS when opts is not a plain object, has keys other than default_region or a getter, or inspecting it itself raises an exception (a Proxy trap and so on), and with UNKNOWN_REGION when default_region is not JP, US, CA or GB.
- It stops with EXTENSION_NOT_SUPPORTED when there is an extension marker (#, ; or a comma, or x, ext or extension), and with LETTERS_NOT_ALLOWED when there are ASCII letters, instead of silently dropping them.
- It stops with INVALID_CHARACTER when there are characters other than those it accepts (full-width digits, tabs, line breaks, no-break spaces, a + that is not at the start, a second + and so on), and with EMPTY_NUMBER when there are no digits at all.
- It stops with UNSUPPORTED_COUNTRY_CODE when, in international form, the country code is not 1, 44 or 81, and with MISSING_REGION when the number is in national form but default_region is missing; it does not guess.
- It stops with INTERNATIONAL_PREFIX_NOT_SUPPORTED when a national-form number starts with that region's international prefix (010 or 00 for JP, 00 for GB, 011 for NANP), and with MISSING_TRUNK_PREFIX when a JP or GB national-form number does not start with 0.
- It stops with INVALID_LENGTH when the number of NSN digits is not a length in the table, and with INVALID_NUMBER_PATTERN when a digit in a fixed position is below the lower bound (a trunk 0 after the country code, or a leading 0 or 1 in a NANP area code or exchange code).
- When it stops, there is always exactly one error, and the order of checks is fixed (the order of error_taxonomy).
What it does not do / limits
- The Phone number normalization (E.164) part only puts the recipient string into a fixed form. It does not include sending SMS or placing calls; that is on the side of the notification Tsugite (D).
- Being able to normalize is not proof that the number exists, can be reached or belongs to someone, or that that person may be contacted (existence_verified is always false).
- It only looks at ASCII characters. It does not convert full-width digits and the like to half-width or do Unicode normalization, and it does not check that parentheses match or where decoration is placed.
- The table is a conservative one limited to 3 regions. Adding a region is separate work: raising the table's version, adding tests and oracles, and re-verifying.
- Being able to normalize does not mean the number may be accepted (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a copy of, or a template for how to use, a particular phone number library (libphonenumber and so on).
How to connect
Call normalize(text, opts) in src/phone.js (it does not throw). Input: a string text of up to 64 characters and opts, which is omitted or only { default_region } (required for the national form). Returns: { ok: true, errors: [], e164, region, country_code, national_number, metadata_revision, existence_verified: false } or { ok: false, errors: [{ code }], e164: null, … } (code is from error_taxonomy). toE164 throws PhoneError when it cannot accept a value. Side effect: NONE. There is no state, no outside connection and no credential, and the caller provides nothing.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
L-07LTsugite
Calendar reference data — Localization Transport Tsugite
The Localization Transport Tsugite (継手, tsugite), for the calendar reference data item, checks a reference-data request (ReferenceDataRequest), such as for a holiday calendar, against the pinned backends, datasets and locales, carries it to a reference-data backend, and matches the returned receipt (ReferenceDataReceipt) against the contract. It only fetches reference data and does not perform date calculations such as Japanese era dates or business days. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When carrying requests to a reference-data backend, it prevents sending to a backend, dataset or locale that is not pinned, and mistaking fetched reference data for an authoritative calendar or a jurisdictional authority.
When to use
- You want to fetch reference data, such as a holiday calendar, from a pinned backend and dataset.
- You want to limit the locale of the reference data (locale_ref) to a pinned list.
- You want to carry the fetch as one with no external effect (side effect NONE).
- You want to accept the returned receipt only after matching request_id, request_digest and references. The DONE result carries data_state, data_ref, authoritative_status and jurisdiction_authority.
- Even if the result becomes unknown (UNKNOWN) because of a backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want date calculations, such as conversion to Japanese era dates or business-day calculation. This Tsugite carries requests that fetch reference data and does not include any date calculation function.
- You want to treat fetched reference data as an authoritative calendar or a jurisdictional authority. Receipts that claim so are not accepted.
- You want localization that local computation can handle, such as time conversion. That is not in scope for this Tsugite.
- You want to check that reference data is correct. Judging the authoritative calendar is the role of external judgement and of people and organizations.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a backend you can use right away. No actual translation, address or reference-data backend is included.
What it checks
- Requests to a backend, locale or dataset that is not pinned are not sent. Requests are also not sent when a configured list is empty or contains an empty entry (an empty list allows nothing).
- Requests with an empty reference, a reference containing a path or .., additional fields, or a value different from the fixed value (const) are not sent.
- Receipts that claim authoritative reference data (authoritative) or a jurisdictional authority (jurisdiction authority) are not accepted.
- Receipts that report failure (FAILED, UNAVAILABLE or NOT_FOUND) yet claim an output reference (data_ref) or an effect are not accepted.
- Requests whose side effect differs from the intent are not sent. Receipts that increase the external effect beyond the intent and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted, and when an unacceptable receipt arrives, it stops (HALTED).
What it does not do / limits
- It does not perform date calculations (Japanese era dates or business days). This Tsugite carries requests that fetch reference data and does not include any date calculation function.
- Reference data is neither an authoritative calendar nor a jurisdictional authority. ERABAZU does not claim jurisdictional authority. Judging the authoritative calendar is the role of external judgement and of people and organizations.
- It does not check whether reference data is correct.
- No result is approval (approval: false). Translated or found is not approval to publish, not confirmation of a deliverable address, and not authoritative.
- It does not include an actual translation, address or reference-data backend and has never connected to one. The port implementation and the pinned lists will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for using any specific translation, map or calendar service.
How to connect
Contract: erabazu.candidate/certified-tsugite/localization/v1. Input: pass a ReferenceDataRequest to submit(request) on createLocalizationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: cfg (the pinned lists backends, locales, regions and datasets) and a port to the backend (submit, and optionally lookup). Secrets are held by the port. Side effect: NONE (reference data).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
L-08LPrimitive
Collation order (collation) — Collation order part (Primitive)
The Collation order part (Primitive) is a standalone part that compares and sorts strings by a language's collation order. It leaves the collation itself to Node.js's built-in Intl.Collator (ICU); this part fixes the locale (ja or en) and the strength (base, accent or variant) by an allow-list, and stops if they are not resolved exactly as requested. Being equal under collation (sameKey) is not being identical. Values it cannot accept are stopped with fixed codes (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind lists, it prevents the sort order from silently falling back to the execution environment's default locale and changing, strings that are equal under collation from being treated as identical, and the order among strings that are equal under collation from being left undetermined. Because results carry the ICU version, you can check afterwards which version's order it is.
When to use
- You want to sort an array of strings by the ja or en collation order and receive the result and the ICU version (sortList; it does not throw).
- You want strings that are equal under collation to stay in their original order (sortStable), or you want ties decided by UTF-16 code unit order to make a total order (sortTotal).
- You want to compare two strings and receive one of -1, 0 or 1 (compare; it is antisymmetric and never returns -0).
- You want to sort only by an explicitly given locale, independent of the execution environment's default locale. If the Collator is not resolved exactly as requested, it stops.
- You want to sort without rewriting the input. It returns a new array even if the input is already sorted, and does not call getters on elements.
When not to use
- You need the same order regardless of the ICU version. The order depends on the execution environment's ICU (78.3), and if the version changes, the same input may give a different order. Other versions have not been checked (icu.matches_pin becomes false).
- You want to use it for duplicate detection or as keys. Being equal under collation is not being identical (for example, hiragana A U+3042 and katakana A U+30A2 under ja with variant).
- You want to sort kanji by reading (on or kun readings). The order of kanji in ja follows ICU's ja rules (JIS order). To sort by reading, the caller passes the readings and sorts by them.
- You want to use locales other than ja and en (including ja-JP, EN and und). It stops with UNSUPPORTED_LOCALE.
- You need numeric collation (comparing "10" and "2" as numbers). This part does not do that.
- You use it on a version other than Node.js v24.21.0 (ICU 78.3). Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with INVALID_OPTIONS when opts is not a plain object, when its keys are not exactly locale and strength (plus order for sortList), or when order is neither stable nor total.
- It stops with UNSUPPORTED_LOCALE when locale is neither ja nor en, and with UNSUPPORTED_STRENGTH when strength is not base, accent or variant.
- When Intl.Collator is not resolved exactly to the requested locale and the fixed settings (for example because ICU lacks that locale), it stops with LOCALE_NOT_AVAILABLE instead of silently falling back to another locale.
- It stops with NOT_A_STRING when a value or element being compared is not a string (including String objects, holes and getter elements), and with STRING_TOO_LONG when it exceeds 65,536 code units; for an element it returns the position in index.
- It stops with NOT_AN_ARRAY when list is not an array (including array-like objects and Sets), and with LIST_TOO_LONG when it exceeds 100,000 elements.
- When inspecting a value itself raises an exception in sortList (a Proxy trap and so on), it returns INSPECTION_FAILED and does not throw.
What it does not do / limits
- It holds no collation order of its own. The collation order is ICU's (78.3) and has not been independently rebuilt. The fuzz oracle also uses the same ICU Collator, so whether ICU's order is correct has not been checked (what was checked is the sorting properties, stability, tie-breaking, the codes it stops with, and a few known answers for ja and en).
- The sort order is not necessarily what users expect (dictionary, phone book or reading order and so on). The judgement of which order to show stays with the caller.
- sortTotal's code-unit comparison does no Unicode normalization.
- Only sortList turns Proxy traps into INSPECTION_FAILED. The throwing functions (compare, sameKey, sortStable and sortTotal) let the trap's exception propagate as it is.
- Being sorted does not mean the order is correct or that it may be accepted (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular sorting library.
How to connect
Call sortList(list, { locale, strength, order }) in src/collation.js (it does not throw). Input: an array of up to 100,000 strings and a plain object with only those three keys. Returns: { ok: true, errors: [], sorted, icu, … } or { ok: false, errors: [{ code, index? }], icu } (code is from error_taxonomy). compare, sameKey, sortStable and sortTotal throw CollationError on a value they cannot accept. Side effect: NONE. There is no outside connection and no credential, and the caller provides nothing. The state is a cache of Collators (only within the process) and does not affect the answers.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
M-01MTsugite
Video call control (WebRTC/SFU) — Two-way Communication Transport Tsugite
The Two-way Communication Transport Tsugite (継手, tsugite), for the video call control (WebRTC/SFU) item, checks a call signal request (CallSignalRequest) for initiate, answer or end, carries it to a communication backend, and matches the returned receipt (CallSignalReceipt) against the contract. It only carries call signals: it does not carry the video and audio themselves (WebRTC or SFU media). If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When carrying call signals to a communication backend, it prevents placing an email address or phone number in a participant reference, accepting a call state that does not match the signal's op, and mistaking a connection for the participants' consent or identity verification.
When to use
- You want to carry the initiate (INITIATE), answer (ANSWER) and end (END) signals of a video call to a communication backend.
- You want to verify that the returned call state corresponds to the signal's op (INITIATE → RINGING or CONNECTED, ANSWER → CONNECTED, END → ENDED; FAILED is allowed for each).
- You want to accept the returned receipt only after matching request_id, request_digest, references and op. The DONE result carries op, call_state, consent and identity_verification.
- Even if the result becomes unknown (UNKNOWN) because of a communication backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want to carry the video and audio themselves (WebRTC or SFU media). This Tsugite carries call signals and does not include any function for carrying video or audio.
- You want to check the quality, latency or encryption of the video and audio. That is outside the scope of this part and has not been checked.
- You want to treat a connection as the participants' consent or as identity verification. Receipts that claim so are not accepted.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a communication backend you can use right away. No actual communication backend is included.
What it checks
- Requests with an empty reference, a reference containing a path or .., additional fields, a value different from the fixed value (const), or an op not in the contract are not sent.
- If a participant or subject reference has the form of an email address or phone number, the request is not sent (detection covers only the typical forms).
- Receipts whose call state does not match the signal's op are not accepted.
- Receipts that claim consent or identity verification are not accepted.
- Requests whose side effect differs from the intent are not sent. Receipts that claim an effect despite a failure (FAILED), receipts that increase the external effect beyond the intent, and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted, and when an unacceptable receipt arrives, it stops (HALTED).
What it does not do / limits
- It does not carry the video and audio themselves (WebRTC or SFU media). For the video call item, it carries only call signals (initiate, answer and end).
- The quality, latency and encryption of the video and audio are outside the scope of this part and have not been checked.
- Connected is neither the participants' consent nor identity verification. Identity verification and consent are the role of external judgement and of people and organizations.
- No result is approval (approval: false). Delivered, read or connected is not identity verification, consent or authority.
- It does not include an actual communication backend (WebRTC, SFU, SIP, or presence or messaging services) and has never connected to one. The port implementation and the pinned lists will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for using any specific call or chat service.
How to connect
Contract: erabazu.candidate/certified-tsugite/communication/v1. Input: pass a CallSignalRequest (op is INITIATE, ANSWER or END) to submit(request) on createCommunicationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: cfg (channel_refs and presence_subjects) and a port to the communication backend (submit, and optionally lookup). Secrets are held by the port. Side effect: EXTERNAL_REVERSIBLE (NONE when FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
M-02MTsugite
Voice call control (SIP) — Two-way Communication Transport Tsugite
The Two-way Communication Transport Tsugite (継手, tsugite), for the voice call control (SIP) item, checks a call signal request (CallSignalRequest) for initiate, answer or end, carries it to a communication backend, and matches the returned receipt (CallSignalReceipt) against the contract. It only carries call signals: it does not carry the audio itself (SIP media). If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When carrying call signals to a communication backend, it prevents placing an email address or phone number in a participant reference, accepting a call state that does not match the signal's op, and mistaking a connection for the participants' consent or identity verification.
When to use
- You want to carry the initiate (INITIATE), answer (ANSWER) and end (END) signals of a voice call to a communication backend.
- You want to verify that the returned call state corresponds to the signal's op (INITIATE → RINGING or CONNECTED, ANSWER → CONNECTED, END → ENDED; FAILED is allowed for each).
- You want to accept the returned receipt only after matching request_id, request_digest, references and op. The DONE result carries op, call_state, consent and identity_verification.
- Even if the result becomes unknown (UNKNOWN) because of a communication backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want to carry the audio itself (SIP media). This Tsugite carries call signals and does not include any function for carrying audio.
- You want to check the quality, latency or encryption of the audio. That is outside the scope of this part and has not been checked.
- You want to treat a connection as the participants' consent or as identity verification. Receipts that claim so are not accepted.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a communication backend you can use right away. No actual communication backend is included.
What it checks
- Requests with an empty reference, a reference containing a path or .., additional fields, a value different from the fixed value (const), or an op not in the contract are not sent.
- If a participant or subject reference has the form of an email address or phone number, the request is not sent (detection covers only the typical forms).
- Receipts whose call state does not match the signal's op are not accepted.
- Receipts that claim consent or identity verification are not accepted.
- Requests whose side effect differs from the intent are not sent. Receipts that claim an effect despite a failure (FAILED), receipts that increase the external effect beyond the intent, and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted, and when an unacceptable receipt arrives, it stops (HALTED).
What it does not do / limits
- It does not carry the audio itself (SIP media). For the voice call item, it carries only call signals (initiate, answer and end).
- The quality, latency and encryption of the audio are outside the scope of this part and have not been checked.
- Connected is neither the participants' consent nor identity verification. Identity verification and consent are the role of external judgement and of people and organizations.
- No result is approval (approval: false). Delivered, read or connected is not identity verification, consent or authority.
- It does not include an actual communication backend (WebRTC, SFU, SIP, or presence or messaging services) and has never connected to one. The port implementation and the pinned lists will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for using any specific call or chat service.
How to connect
Contract: erabazu.candidate/certified-tsugite/communication/v1. Input: pass a CallSignalRequest (op is INITIATE, ANSWER or END) to submit(request) on createCommunicationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: cfg (channel_refs and presence_subjects) and a port to the communication backend (submit, and optionally lookup). Secrets are held by the port. Side effect: EXTERNAL_REVERSIBLE (NONE when FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
M-03MTsugite
Presence (carries publishing) — Two-way Communication Transport Tsugite
The Two-way Communication Transport Tsugite (継手, tsugite), for the presence item, checks a presence publish request (PresencePublishRequest) against the pinned subjects (references to the people whose presence may be published), carries it to a communication backend, and matches the returned receipt (PresencePublishReceipt) against the contract. Publishing "online" does not mean a person is there. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When publishing presence to a communication backend, it prevents publishing the presence of a person not designated as publishable, placing an email address or phone number in a subject reference, and mistaking "online" for a person being there.
When to use
- You want to carry presence (availability and status) publishing to a communication backend, limited to pinned subject references (pseudonyms).
- You want to accept the returned receipt only after matching request_id, request_digest and references. The DONE result carries publish_state and human_availability.
- You want to treat presence publishing as a reversible external effect (EXTERNAL_REVERSIBLE).
- Even if the result becomes unknown (UNKNOWN) because of a communication backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want to use "online" as confirmation that a person is there. Receipts that claim so are not accepted.
- You want to publish the presence of a person not designated as publishable. The presence of subjects that are not pinned is not published.
- You want to use an email address or phone number as is in a subject reference. Such requests are not sent.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a communication backend you can use right away. No actual communication backend is included.
What it checks
- The presence of subjects that are not pinned is not published. Requests are also not sent when a configured list is empty or contains an empty entry (an empty list allows nothing).
- Requests with an empty reference, a reference containing a path or .., additional fields, a value different from the fixed value (const), or an op not in the contract are not sent.
- If a subject reference has the form of an email address or phone number, the request is not sent (detection covers only the typical forms).
- Receipts that claim a person is there (human availability) are not accepted.
- Requests whose side effect differs from the intent are not sent. Receipts that claim an effect despite a failure (FAILED), receipts that increase the external effect beyond the intent, and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted, and when an unacceptable receipt arrives, it stops (HALTED).
What it does not do / limits
- Publishing "online" does not mean a person is there. It does not check whether a person is there.
- It does not decide whose presence may be published. Which subjects are published is decided by the user through the pinned list (presence_subjects).
- No result is approval (approval: false). Delivered, read or connected is not identity verification, consent or authority.
- It does not include an actual communication backend (WebRTC, SFU, SIP, or presence or messaging services) and has never connected to one. The port implementation and the pinned lists will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for using any specific call or chat service.
How to connect
Contract: erabazu.candidate/certified-tsugite/communication/v1. Input: pass a PresencePublishRequest to submit(request) on createCommunicationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: cfg (channel_refs and presence_subjects) and a port to the communication backend (submit, and optionally lookup). Secrets are held by the port. Side effect: EXTERNAL_REVERSIBLE (NONE when FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
M-04MTsugite
Live caption transmission (carries caption segments as digests) — Two-way Communication Transport Tsugite
The Two-way Communication Transport Tsugite (継手, tsugite), for the live caption transmission item, checks caption segments as real-time message requests (RealtimeMessageRequest) against the pinned channels, carries them to a communication backend, and matches the returned receipt (RealtimeMessageReceipt) against the contract. It carries only the digest, not the caption text. It does not check whether captions are correct. If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When carrying caption segments to a communication backend, it prevents sending the text placed in the digest field, sending to a channel that is not pinned, and mistaking delivered or read for identity verification, comprehension or authority.
When to use
- You want to carry live caption segments to a pinned channel as a digest (payload_digest), not as the text.
- You want to accept the returned receipt only after matching request_id, request_digest, references and op. The DONE result carries delivery_state, delivered, identity_verification, comprehension and authority_assertion.
- You want to carry messages without placing the form of an email address or phone number in a recipient reference.
- Even if the result becomes unknown (UNKNOWN) because of a communication backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want to carry the caption text itself. This Tsugite carries caption segments as digests and does not carry the text. Requests that place the text in payload_digest are not sent.
- You want to check whether captions are correct. It does not check caption correctness; that is the role of external judgement and of people and organizations.
- You want to check the quality, latency or encryption of the captions. That is outside the scope of this part and has not been checked.
- You want to treat delivered or read as identity verification, comprehension or authority. Receipts that claim so are not accepted.
- You want to retract a delivered message. The side effect of a message is an irreversible external effect (EXTERNAL_IRREVERSIBLE).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a communication backend you can use right away. No actual communication backend is included.
What it checks
- Requests to a channel that is not pinned are not sent. Requests are also not sent when a configured list is empty or contains an empty entry (an empty list allows nothing).
- Requests that place the text in payload_digest, and requests with a malformed digest, are not sent.
- Requests with an empty reference, a reference containing a path or .., additional fields, a value different from the fixed value (const), or an op not in the contract are not sent.
- If a recipient reference has the form of an email address or phone number, the request is not sent (detection covers only the typical forms).
- Receipts that claim identity verification, comprehension or authority are not accepted.
- Requests whose side effect differs from the intent are not sent. Receipts that claim an effect despite a failure (FAILED), receipts that increase the external effect beyond the intent, and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted, and when an unacceptable receipt arrives, it stops (HALTED).
What it does not do / limits
- It does not carry the caption text. It carries only the digest of each caption segment. Carrying the caption body, and its quality, latency and encryption, are outside the scope of this part and have not been checked.
- It does not check whether captions are correct or what a delivered message contains.
- Delivered or read is not identity verification, comprehension, consent or authority.
- No result is approval (approval: false). Delivered, read or connected is not identity verification, consent or authority.
- It does not include an actual communication backend (WebRTC, SFU, SIP, or presence or messaging services) and has never connected to one. The port implementation and the pinned lists will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for using any specific call or chat service.
How to connect
Contract: erabazu.candidate/certified-tsugite/communication/v1. Input: pass a RealtimeMessageRequest to submit(request) on createCommunicationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: cfg (channel_refs and presence_subjects) and a port to the communication backend (submit, and optionally lookup). Secrets are held by the port. Side effect: EXTERNAL_IRREVERSIBLE (NONE when FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
M-05MTsugite
Large and resumable transfer — Large and Resumable Transfer Tsugite
The Large and Resumable Transfer Tsugite (継手, tsugite) sends large content to a storage destination as one object: it starts a transfer, sends chunks in order from committed_offset, and, if the transfer stops partway, queries the resume position and continues. It finalizes only when all chunks are present and the sha256 of the content equals the payload_digest declared at the start. It does not treat the receipt of chunks or finalization as acceptance of the content (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a huge file is sent in chunks, this Tsugite prevents stacking a chunk twice whose result became unknown after a failure partway, and treating a transfer with missing or different content as "it was sent". It checks the length and sha256 of each chunk itself before sending, and builds up the overall sha256 only from chunks that were received.
When to use
- You want to send a file too large to send in one go to a storage destination in chunks.
- When a transfer stops partway, you want to query the resume position (committed_offset) and send from where it left off.
- You want to finalize only after confirming that the sha256 of the complete content equals the payload_digest given at the start.
- You want to resolve a chunk whose result is unknown without stacking it twice. It is resolved by a resume position query or by resending the same request (which can also be resolved with lookup).
- You want the transfer to stop until a person checks it when a receipt with a conflicting state appears.
When not to use
- You want to send chunks in parallel or receive them out of order. That is outside the scope of contract v1, and this Tsugite does not send them.
- You only need storage for sizes that can be sent in one go. That is the Object Store Tsugite (C-01).
- You want to treat finalization as a decision that the content is correct or may be accepted. Finalization is not acceptance of the content (content_acceptance is NOT_DERIVED).
- You want to resume from another process or across a restart. The transfer state exists only inside this Tsugite and is lost on restart.
- You want a storage destination you can use right away. It has never been connected to an actual storage backend, and the number of actual transfers is 0.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the order of chunks. A chunk that is not in order from committed_offset (a skipped offset, an offset that goes back, or a chunk past the total size), or a chunk other than the last that is not chunk_size, is not sent, and nothing is sent to the backend.
- It checks the content of chunks. A chunk whose content length or sha256 differs from what was declared is not sent. It checks this itself before sending.
- It checks finalization. A finalization before all chunks are present, and a finalization with a sha256 different from the payload_digest given at the start, are not sent. COMPLETED is not accepted without MATCH evidence and a stored_digest.
- It checks the configured limits. A content-type that is not allowed, a total size or chunk size over the limit, and WORM (a finalization that cannot be undone) when allow_worm is not true are not sent.
- It checks the transfer state. Another send while a chunk is still unresolved, and a send to a transfer that has expired, been closed, been finalized or been aborted, are not sent. A transfer cannot be resumed after it is aborted.
- It matches the receipt. A receipt with a conflicting state, a receipt that increases the external effect, a receipt that claims the request_digest of another request, and a completion that claims acceptance are not accepted, and the client stops on a receipt it cannot accept (HALTED).
- It checks backend failures. A chunk whose result became unknown due to a failure is treated as UNKNOWN, and if the answer to the resume position query does not match, it is not accepted. An effect that leaves it unknown what remains is not accepted.
What it does not do / limits
- It does not store the huge file itself. This Tsugite only carries chunks to the storage backend and matches the returned receipts. It has never been connected to an actual storage backend, and the number of actual transfers is 0.
- It does not split in parallel or receive out of order. That is outside the scope of contract v1; chunks are sent only in order from committed_offset.
- It does not inspect the content itself. Decisions on confirming, accepting and retaining content are the role of external decisions and of people and organizations. Finalization is not acceptance of the content (content_acceptance is NOT_DERIVED).
- It does not persist the transfer state. The state exists only inside this Tsugite and is lost on restart. It does not resume from another process either.
- It checks only the form of the expiry (expires_at). Expiry is learned from the backend's answer.
- No result is approval (approval: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use the multipart upload of a specific cloud storage service.
How to connect
Contract: erabazu.candidate/certified-tsugite/object-store-resumable/v1. Input: pass a TransferStart, ChunkSend, ResumeQuery, TransferComplete or TransferAbort to submit(request, bytes) on createResumableClient({ cfg, port }); bytes is only for ChunkSend. Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: a port to the backend (submit, and optionally lookup). Side effect: EXTERNAL_IRREVERSIBLE for a WORM finalization and for an abort.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
M-06MTsugite
Screen sharing call control — Two-way Communication Transport Tsugite
The Two-way Communication Transport Tsugite (継手, tsugite), for the screen sharing call control item, checks a call signal request (CallSignalRequest) for initiate, answer or end, carries it to a communication backend, and matches the returned receipt (CallSignalReceipt) against the contract. It only carries call signals: it does not carry the screen itself (screen-sharing media). If any check fails, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When carrying call signals to a communication backend, it prevents placing an email address or phone number in a participant reference, accepting a call state that does not match the signal's op, and mistaking a connection for the participants' consent or identity verification.
When to use
- You want to carry the initiate (INITIATE), answer (ANSWER) and end (END) signals of a screen sharing to a communication backend.
- You want to verify that the returned call state corresponds to the signal's op (INITIATE → RINGING or CONNECTED, ANSWER → CONNECTED, END → ENDED; FAILED is allowed for each).
- You want to accept the returned receipt only after matching request_id, request_digest, references and op. The DONE result carries op, call_state, consent and identity_verification.
- Even if the result becomes unknown (UNKNOWN) because of a communication backend failure, you want to resolve it with a lookup instead of sending the same request_id twice.
When not to use
- You want to carry the screen itself (screen-sharing media). This Tsugite carries call signals and does not include any function for carrying the screen.
- You want to check the quality, latency or encryption of the screen. That is outside the scope of this part and has not been checked.
- You want to treat a connection as the participants' consent or as identity verification. Receipts that claim so are not accepted.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a communication backend you can use right away. No actual communication backend is included.
What it checks
- Requests with an empty reference, a reference containing a path or .., additional fields, a value different from the fixed value (const), or an op not in the contract are not sent.
- If a participant or subject reference has the form of an email address or phone number, the request is not sent (detection covers only the typical forms).
- Receipts whose call state does not match the signal's op are not accepted.
- Receipts that claim consent or identity verification are not accepted.
- Requests whose side effect differs from the intent are not sent. Receipts that claim an effect despite a failure (FAILED), receipts that increase the external effect beyond the intent, and receipts whose effect is unknown (UNKNOWN) are not accepted.
- A receipt for a different request (a spoofed request_digest) is not accepted, and when an unacceptable receipt arrives, it stops (HALTED).
What it does not do / limits
- It does not carry the screen itself (screen-sharing media). For the screen sharing item, it carries only call signals (initiate, answer and end).
- The quality, latency and encryption of the screen are outside the scope of this part and have not been checked.
- Connected is neither the participants' consent nor identity verification. Identity verification and consent are the role of external judgement and of people and organizations.
- No result is approval (approval: false). Delivered, read or connected is not identity verification, consent or authority.
- It does not include an actual communication backend (WebRTC, SFU, SIP, or presence or messaging services) and has never connected to one. The port implementation and the pinned lists will be settled at a separate gate when connecting.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for using any specific call or chat service.
How to connect
Contract: erabazu.candidate/certified-tsugite/communication/v1. Input: pass a CallSignalRequest (op is INITIATE, ANSWER or END) to submit(request) on createCommunicationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED. Every result has approval: false. User provides: cfg (channel_refs and presence_subjects) and a port to the communication backend (submit, and optionally lookup). Secrets are held by the port. Side effect: EXTERNAL_REVERSIBLE (NONE when FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-01NTsugite
Service discovery and registration — Service Discovery & Registration Tsugite
The Service Discovery & Registration Tsugite (継手, tsugite) checks requests (RegistryRequest) to an external registry backing service (registry or control plane), namely registering a service instance (REGISTER, with a lease), cancelling a registration (DEREGISTER), extending a lease (HEARTBEAT) and querying (QUERY), carries them, and matches the returned receipt (RegistryReceipt) against the contract. A query result is a timestamped observation; it does not treat a listed endpoint as correct, healthy, permitted or genuine (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When you rely on an external registry backing service to locate a counterpart, it prevents treating an expired lease as registered, returning an old instance as healthy, and mistaking being listed for correctness, permission or identity. Lease expiry and query staleness are checked with an injected clock.
When to use
- You want to register microservice instances with an external registry backing service with a lease, extend them with HEARTBEAT, and cancel them when they are no longer needed.
- You want to query where a counterpart is and receive old instances too, without hiding them, marked with stale: true and effective_health: STALE.
- You want the configuration to decide which registries and services may be used, which endpoints may be registered and the ttl limit, and not to send anything outside that.
- You want lease expiry and query staleness to be checked with an injected clock rather than on the registry backing service's word.
When not to use
- You want purely local static name resolution. This Tsugite carries requests to an external registry backing service; local name resolution is within the scope of a separate Primitive.
- You want to use an endpoint obtained from a query directly as a genuine, healthy counterpart that may be called. This Tsugite does not check that (endpoint_identity is NOT_VERIFIED; correctness and authority are NOT_DERIVED).
- You want lease state to carry over after a restart. The state exists only inside this Tsugite, and after a restart it does not send HEARTBEAT or DEREGISTER for instances registered earlier.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
- You want a connection to a registry backing service that is ready to use. It has never connected to an actual registry backing service; the port implementation, credentials and configuration values will be settled at a separate gate when connecting.
What it checks
- It checks the configuration. If there is no configuration, or for a registry, service or endpoint not in the lists, a REGISTER without register_endpoints or max_ttl_seconds, or a QUERY without stale_after_seconds, it does not send.
- It checks the ttl. A ttl that is not an integer, a ttl above the configured limit, and a ttl or a configured limit above 86400 are not sent.
- It checks the clock. If the clock is missing or broken, it sends nothing and accepts no receipts.
- It checks lease handling. A REGISTER for an instance that still has a valid lease, a HEARTBEAT for a missing or expired lease, and a DEREGISTER for an instance this Tsugite did not register are not sent.
- It checks the form of references and of the request. References containing .., absolute paths, drives or control characters, a request_id that is not a Ref, a malformed service_name, an unknown op, and fields the op does not use are not sent.
- It matches the receipt. A spoofed request_digest, a mismatch in request_id, registry, service_name or op, a state outside the table, and an increased or unknown effect are not accepted, and it stops (HALTED).
- It matches the lease and query contents. A lease longer than the requested ttl, a lease not in timestamp form, a last_seen_at in the future, a query that lists the same instance twice, and a receipt whose observation time is before the request or later than now are not accepted.
- A receipt that derives correctness or permission (where correctness or authority is not NOT_DERIVED) is not accepted.
What it does not do / limits
- It does not check whether a listed endpoint is genuine (identity, certificate), or whether it is correct, healthy or may be called. observed_health only carries the registry backing service's observation as is. Checking identity is the role of, for example, the B-09 certificate and the caller's trust policy.
- Being able to register or query is not approval (approval: false). Whether a counterpart may be called is for an external decision and for people and organizations.
- It does not check whether the length of an instance's lease_expires_at is reasonable (the contract has no criterion for it). With a backing service that returns leases far in the future, an old instance may not show as old. It does not DEREGISTER other parties' instances.
- It does not check the correctness of the clock itself, or the actual drift from the registry backing service's clock.
- With a port that has no lookup, when a send whose result is unknown can no longer pass the checks, it stops with the send unresolved. It also does not accept a FAILED with an UNKNOWN effect and stops; whether the registration remains is checked by a person.
- It holds no credentials for authenticating with the registry backing service; the port holds them (HELD_BY_PORT). It does not change the contract either. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for using any specific registry backing service (such as a service mesh or a cloud's name resolution).
How to connect
Contract: erabazu.candidate/certified-tsugite/service-registry/v1. Input: pass a RegistryRequest to submit(request) on createServiceRegistryClient({ cfg, port, clock }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries the facts for each op in its result (for QUERY, instances and endpoint_identity). Every result has approval: false. User provides: a port to the registry backing service (submit, and optionally lookup) and a clock. Side effect: NONE for QUERY, EXTERNAL_REVERSIBLE for the other three.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-02NComponent
API gateway routing — API gateway routing part (Component)
The API gateway routing part (Component) is a part that bundles checking the gateway's route table locally (the route-config Primitive) and then applying it to the gateway (the gateway-config Tsugite). Changes are two-step: prepareApply and prepareRollback return the subject to be signed off by a person or organization (authority subject) and its digest, and apply and rollback are called with evidence that includes that digest. It does not send a change without evidence, a change different from the subject of the evidence, or a table the check refused (fail-closed). Being applied is not proof that the routing is correct and is not approval of the change.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind the gateway, the aggregation point for authentication and traffic, it prevents applying a table that has not passed the check or a table different from the checked digest, applying a change without evidence from a person or organization or with sign-off given for a different change, and resending a change whose result is unknown as a different request.
When to use
- You want to check the route table locally (shape, references, conflicts, hidden routes) and APPLY to the gateway only tables that passed the check. Inject the check of route-config as checkRoutes and a client of the gateway-config Tsugite as gateway.
- You want to split a change into two steps. prepareApply and prepareRollback send nothing and return the subject to be signed off (gateway_ref, op, base_version_ref, and config_digest or rollback_to_version_ref) and its digest; apply and rollback take evidence from a person or organization that includes that digest (origin: HUMAN or ORGANIZATION, evidence_ref, subject_digest).
- You want the APPLY to carry the validation evidence (validation_evidence) that the Tsugite made from the output of the local check, and then to pass the evidence from a person or organization to the Tsugite before sending.
- When going back to a previous version (rollback), you also want the evidence from a person or organization passed to the Tsugite before sending.
- You want to observe the current version without effect (observe, STATUS) and, when rollout_of_request_id is passed, to receive whether that rollout has finished exactly as the Tsugite's result. observe can be sent even while a change is pending.
- You want to hold a change whose result is unknown as pending and resend it with resolvePending only as the same request. base_version_ref prevents a double change.
When not to use
- You want to use being applied (APPLIED) as proof that the routing is correct or as approval of the change. Every result carries approval: false, authority: NOT_DERIVED and traffic_correctness: NOT_DERIVED.
- You want to change routes without evidence from a person or organization. Route changes (APPLY, ROLLBACK) need evidence from a person or organization; this part does not create that evidence and receives it from the caller.
- You want this part to check the content of the evidence (who, and until when). This part only checks that the subject's digest matches and passes the evidence to the Tsugite.
- You need a part that does the request routing itself (directing traffic at the entrance). This part checks the route table and only has the bundled Tsugite send the change that alters the routing.
- You want to rely on the gateway reading the table by the same rules as the check, or computing the same digest. Neither is checked (the limit of route-config remains).
- You need a part checked against a real gateway. It has never connected to a real gateway or control plane.
- You want to continue a pending change across a restart. The state is only inside this process, and a restart loses the pending request.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed, a reference is invalid or a port function is missing, it stops with INVALID_OPTIONS, when the input is malformed, with INVALID_INPUT, and when a getter on the input or on a port's result throws an exception, with INSPECTION_FAILED.
- When the local check refuses the table, it stops with ROUTES_REFUSED (with route_errors attached), when the config_digest in the check's result is malformed (domain, algorithm, 64 hexadecimal digits, target), with CHECK_RESULT_INVALID, and when the check function throws, with PORT_FAILED, and it does not send the APPLY.
- When the evidence is missing, has missing or extra fields, has an origin other than HUMAN or ORGANIZATION, has an invalid reference, or has a subject_digest that is not 64 hexadecimal digits, it stops with NO_AUTHORITY_EVIDENCE, and when the evidence's subject_digest differs from the subject's digest recomputed on the spot (sign-off for a different change), with AUTHORITY_SUBJECT_MISMATCH, and it does not send the APPLY or ROLLBACK.
- When the Tsugite does not return the validation evidence, it stops with EVIDENCE_REFUSED, and when it does not return exactly true for recording the evidence, with AUTHORITY_REFUSED, and it does not send.
- When the Tsugite's result is unknown (UNKNOWN, an exception), it holds the change as pending with GATEWAY_UNKNOWN, and while it is pending, anything other than resolvePending and observe is stopped with CHANGE_PENDING. Calling resolvePending when nothing is pending returns NOTHING_PENDING.
- When the Tsugite did not send, it reports GATEWAY_REFUSED (with gateway_errors attached), and when the Tsugite stopped because the receipt could not be matched, GATEWAY_HALTED, without changing the state and without keeping a pending change.
- It sets applied to true only when the Tsugite's result has applied: true, and rollout_completed to true only when the Tsugite's result is exactly true. observe (STATUS) is sent without effect (intended_side_effect NONE).
- When timestamp does not return a date-time string or throws, it stops with CLOCK_FAILED, and when the previous operation has not finished, with BUSY (only one operation at a time).
What it does not do / limits
- The API gateway routing part is not a Tsugite. It has no Request or Receipt of its own; the external effect of changing the routing is caused by the bundled gateway-config Tsugite under its own contract (gateway-config/v1). This part itself does not touch the outside.
- The meaning of the bundled parts (passing the check is not proof that the routing is correct; being applied is not approval) stays on each part's side.
- Being applied is not proof that the routing is correct and is not approval of the change (approval: false, authority: NOT_DERIVED, traffic_correctness: NOT_DERIVED, derives_authority: false).
- It does not create evidence from a person or organization. It receives the evidence for a route change from the caller and does not check the content of the evidence (who, and until when).
- Whether a rollout has finished after ACCEPTED, APPLYING or PARTIAL is only returned as the Tsugite's result when rollout_of_request_id is passed to observe; this part does not judge it.
- It has never connected to a real gateway or control plane. The unit tests and fuzzing use stand-ins, and the wiring with the code of the real two parts was checked against the Tsugite's test stand-in for the gateway.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular gateway product.
How to connect
Create it with createGatewayRouting(options) in src/routing.js. Input: gateway_ref, request_prefix, ports and an optional timestamp. Returns: { ok, errors, instance }; each operation returns { ok, errors, approval: false, record_pending, last_change, … }. User provides (injected through ports): checkRoutes (the check of route-config) and gateway (the client of the gateway-config Tsugite: submit, and two functions that pass the validation evidence and the evidence from a person or organization). Side effect: caused by the bundled Tsugite (EXTERNAL_REVERSIBLE); credentials are held by the ports.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-03NTsugite
Webhook subscription registry — Integration and ETL Tsugite
The Integration and ETL Tsugite (継手, tsugite) checks requests to register, update or revoke a Webhook subscription (WebhookSubscription) against the pinned list of Webhook destinations, sends them to the integration target, and matches the returned receipt against the contract. If the destination, a reference or the receipt is off, it does not send and does not accept (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When notification destinations (Webhook destinations) are registered externally, it prevents an arbitrary callback URL from being injected, prevents the destination from being substituted through the receipt, and prevents mistaking "registered" for "the notification was delivered".
When to use
- You want to register and manage notification destinations (Webhook destinations) with an external integration target. Destinations are chosen only from a list pinned in advance.
- You want to match the result of subscribing (SUBSCRIBE), updating (UPDATE) or revoking (REVOKE) against the receipt's state (REGISTERED, UPDATED or REVOKED, or FAILED) before acting on it.
- You do not want the same request to be sent twice. When a failure at the integration target leaves the result unknown, it is treated as UNKNOWN and resolved by a lookup.
- You want further sending to stop once a receipt that cannot be matched arrives (HALTED after RECEIPT_REJECTED).
When not to use
- You want to deliver Webhooks, verify their signatures or receive them. This Tsugite only handles registering subscriptions.
- You want to use a subscription receipt as evidence that a notification was delivered. A receipt can only speak to the registration stage (SUBSCRIPTION_REGISTERED).
- You want users to specify an arbitrary callback URL. It does not send to destinations that are not pinned.
- You want a connector dedicated to a specific SaaS. This Tsugite is not a per-SaaS connector; it applies the same checks to every integration target.
- You want an integration target you can use right away. No actual integration target is included, and where to connect has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the destination. It does not send to a destination that is not in the pinned list of Webhook destinations (callback URL injection). An empty list allows nothing.
- It matches the destination in the receipt. If the destination has been substituted, it does not accept the receipt and stops (HALTED).
- It does not accept a subscription receipt that claims delivery or receipt by the other side. It also does not accept a receipt whose state does not match the operation (not SUBSCRIBE→REGISTERED, UPDATE→UPDATED or REVOKE→REVOKED, and not FAILED).
- It checks references. Empty references, references shaped like a path or containing "..", additional fields, and values that differ from the fixed values (const) are not sent.
- It does not accept a receipt that escalates the external effect, a receipt whose effect is UNKNOWN, or a receipt for a different request (spoofed request_digest). It stops (HALTED) on a receipt it cannot accept. The effect of a subscription is EXTERNAL_REVERSIBLE (NONE for FAILED).
- It never sends the same request_id twice. It does not send a request that reuses an idempotency key with different content (REFUSED). A failure at the integration target is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- It does not deliver Webhooks, verify their signatures or receive them. That is outside the scope of this Tsugite (it only registers subscriptions).
- Deciding semantic correctness, the Schema Authority and business authorization is the role of the Kernels (reconcile-facts and policy-facts) and of people. This Tsugite only checks before sending and matches what it receives afterwards.
- Having carried, registered or connected something is not approval (every result carries approval: false).
- It does not include an actual integration target. Which SaaS or ETL platform to connect to, the port implementation and the lists to pin are undecided and will be settled at a separate gate when connecting.
- This Tsugite has no N-01 service discovery or N-02 API gateway function. N-01 is handled by the Tsugite service-registry-tsugite (contract service-registry/v1), and N-02 by the Component gateway-routing-component.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/integration-etl/v1 (WebhookSubscription). Input: pass a WebhookSubscription request to submit(request) on createIntegrationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries subscription_state, subscription_ref and lifecycle_stage. Every result has approval: false. User provides: a port to the integration target (submit, and optionally lookup) and the pinned lists in cfg. Side effect: EXTERNAL_REVERSIBLE (NONE for FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-04NTsugite
Data import (ETL) connector — Integration and ETL Tsugite
The Integration and ETL Tsugite (継手, tsugite) checks data import requests (DataImport) from outside into your own system against the pinned sources, schemas and mappings, sends them to the integration target, and matches the returned receipt's counts and rejected-record references against the contract. A partial failure is returned as is, never collapsed into success or failure (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When importing data from outside, it prevents importing from sources or mappings that are not pinned, and prevents fudging the counts or partial failures and treating the import as "everything was imported".
When to use
- You want to import data from outside into your own system (migration or sync), limited to the pinned sources, schemas and mappings.
- You want to match the import result against the counts (received = accepted + rejected) and the rejected-record references (rejected_record_refs) before acting on it.
- You want an import that partly failed to be received as a partial failure (PARTIAL).
- You want an irreversible import to be sent only when the settings explicitly allow it (allow_irreversible: true).
- You do not want the same request to be sent twice. When a failure at the integration target leaves the result unknown, it is treated as UNKNOWN and resolved by a lookup.
When not to use
- You want to check whether the meaning of the imported data or the mapping is correct. This Tsugite looks only at counts and references (semantic_correctness: NOT_VERIFIED).
- You want to hand over the judgement of reconciling an import. Reconciliation is the role of the Kernel (reconcile-facts).
- You want a connector dedicated to a specific SaaS. This Tsugite is not a per-SaaS connector; it applies the same checks to every integration target.
- You want an integration target you can use right away. No actual integration target is included, and where to connect has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks sources, schemas and mappings. Anything not in the pinned lists is not sent. An empty list allows nothing.
- It checks references. Empty references, references shaped like a path or containing "..", additional fields, and values that differ from the fixed values (const) are not sent.
- An irreversible import is sent only when the settings have allow_irreversible: true. Otherwise it is not sent.
- It matches the counts. It does not accept a receipt where received = accepted + rejected does not hold, where the number of rejections does not match the number of rejected-record references, or where the rejected-record references contain duplicates.
- It matches the result state. It does not accept a PARTIAL with only acceptances or only rejections, a receipt that says complete despite rejections, or a receipt that says FAILED despite acceptances.
- It does not accept a receipt that escalates the external effect, a receipt whose effect is UNKNOWN, or a receipt for a different request (spoofed request_digest). It stops (HALTED) on a receipt it cannot accept. The effect of an import is EXTERNAL_* (NONE for FAILED).
- It never sends the same request_id twice. It does not send a request that reuses an idempotency key with different content (REFUSED). A failure at the integration target is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- It does not check the semantic correctness of the imported data or the correctness of the mapping (only counts and references).
- Deciding semantic correctness, the Schema Authority and business authorization is the role of the Kernels (reconcile-facts and policy-facts) and of people. This Tsugite only checks before sending and matches what it receives afterwards.
- Having carried, registered or connected something is not approval (every result carries approval: false).
- It does not include an actual integration target. Which SaaS or ETL platform to connect to, the port implementation and the lists to pin are undecided and will be settled at a separate gate when connecting.
- This Tsugite has no N-01 service discovery or N-02 API gateway function. N-01 is handled by the Tsugite service-registry-tsugite (contract service-registry/v1), and N-02 by the Component gateway-routing-component.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/integration-etl/v1 (DataImport). Input: pass a DataImport request to submit(request) on createIntegrationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries partial_state, the counts, rejected_record_refs and semantic_correctness: NOT_VERIFIED. Every result has approval: false. User provides: a port to the integration target (submit, and optionally lookup) and the pinned lists in cfg. Side effect: EXTERNAL_* (NONE for FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-05NTsugite
Data export connector — Integration and ETL Tsugite
The Integration and ETL Tsugite (継手, tsugite) checks data export requests (DataExport) from your own system to the outside against the pinned destinations, schemas and profiles, sends them, and matches the returned receipt against the contract. It returns "generated", "delivered" and "accepted" separately, and does not accept a receipt that does not match (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When exporting your own data to the outside, it prevents sending to destinations that are not pinned, and prevents treating "generated" alone as "delivered to the other side" or "accepted by the other side".
When to use
- You want to export data from your own system to the outside, limited to the pinned destinations, schemas and profiles. The export selection can also be pinned, optionally (selection_refs).
- You want to handle the export result separately as generated (GENERATED), delivered (DELIVERED) and accepted (ACCEPTED). DONE comes with delivery_state, delivered and destination_accepted.
- You want an irreversible export to be sent only when the settings explicitly allow it (allow_irreversible: true).
- You do not want the same request to be sent twice. When a failure at the integration target leaves the result unknown, it is treated as UNKNOWN and resolved by a lookup.
When not to use
- You want to use "generated" (GENERATED) as evidence that the data reached the other side. This Tsugite returns the three stages separately.
- You want to check the semantic correctness of the exported data. Semantic correctness is the role of the Kernels and of people; this Tsugite only checks before sending and after receiving.
- You want to decide which integrations to allow. That decision is the role of the Kernel (policy-facts).
- You want a connector dedicated to a specific SaaS. This Tsugite is not a per-SaaS connector; it applies the same checks to every integration target.
- You want an integration target you can use right away. No actual integration target is included, and where to connect has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks destinations, schemas and profiles. Anything not in the pinned lists is not sent. An empty list allows nothing.
- It checks references. Empty references, references shaped like a path or containing "..", additional fields, and values that differ from the fixed values (const) are not sent.
- An irreversible export is sent only when the settings have allow_irreversible: true. Otherwise it is not sent.
- It matches the stage and the effect. It does not accept a receipt that says generated (GENERATED) but claims an external effect, or one that says delivered (DELIVERED) but has no effect. GENERATED has effect NONE or LOCAL_REVERSIBLE; DELIVERED and ACCEPTED have EXTERNAL_*.
- It does not accept a receipt that escalates the external effect, a receipt whose effect is UNKNOWN, or a receipt for a different request (spoofed request_digest). It stops (HALTED) on a receipt it cannot accept.
- It never sends the same request_id twice. It does not send a request that reuses an idempotency key with different content (REFUSED). A failure at the integration target is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- Deciding semantic correctness, the Schema Authority and business authorization is the role of the Kernels (reconcile-facts and policy-facts) and of people. This Tsugite only checks before sending and matches what it receives afterwards.
- Having carried, registered or connected something is not approval (every result carries approval: false).
- It does not include an actual integration target. Which SaaS or ETL platform to connect to, the port implementation and the lists to pin are undecided and will be settled at a separate gate when connecting.
- This Tsugite has no N-01 service discovery or N-02 API gateway function. N-01 is handled by the Tsugite service-registry-tsugite (contract service-registry/v1), and N-02 by the Component gateway-routing-component.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/integration-etl/v1 (DataExport). Input: pass a DataExport request to submit(request) on createIntegrationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries delivery_state, delivered and destination_accepted. Every result has approval: false. User provides: a port to the integration target (submit, and optionally lookup) and the pinned lists in cfg. Side effect: it differs by stage (NONE to EXTERNAL_*).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-06NTsugite
Schema registry — Integration and ETL Tsugite
The Integration and ETL Tsugite (継手, tsugite) checks schema registration requests (SchemaRegistration) against the pinned list of schemas, sends them to the integration target, and matches the returned receipt against the contract. It does not mistake a successful registration for the Schema Authority or Canonical (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When registering schemas to share contracts (types), it prevents registering schemas that are not pinned, and prevents treating "registered" as "the schema has become the authority (Schema Authority) or Canonical".
When to use
- You want to register only pinned schemas with the integration target, to share contracts (types).
- You want to match the registration result against the receipt's registration_state, schema_authority and canonical_status before acting on it.
- You want to match the registration effect as EXTERNAL_REVERSIBLE, and duplicates (DUPLICATE) and failures (FAILED) as no effect (NONE).
- You do not want the same request to be sent twice. When a failure at the integration target leaves the result unknown, it is treated as UNKNOWN and resolved by a lookup.
When not to use
- You want registration to make the schema the authority or Canonical. Registering a schema creates neither a Schema Authority nor Canonical.
- You want to decide which schema is the authority (Schema Authority). That is the role of the Kernels and of people, and the authority for this Tsugite's contract has not been set yet either (NOT_SPECIFIED).
- You want a connector dedicated to a specific SaaS. This Tsugite is not a per-SaaS connector; it applies the same checks to every integration target.
- You want an integration target you can use right away. No actual integration target is included, and where to connect has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks schemas. Anything not in the pinned list of schemas is not sent. An empty list allows nothing.
- It checks references. Empty references, references shaped like a path or containing "..", additional fields, and values that differ from the fixed values (const) are not sent.
- It does not accept a registration receipt that claims to be the authority (Schema Authority) or claims to be Canonical.
- It matches digest targets. As read from the contract, the target of schema_digest is schema_identity and the target of content_digest is request_id.
- It does not accept a receipt that escalates the external effect, a receipt whose effect is UNKNOWN, or a receipt for a different request (spoofed request_digest). It stops (HALTED) on a receipt it cannot accept. The effect of a registration is EXTERNAL_REVERSIBLE (NONE for DUPLICATE and FAILED).
- It never sends the same request_id twice. It does not send a request that reuses an idempotency key with different content (REFUSED). A failure at the integration target is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- Registering a schema creates neither a Schema Authority nor Canonical.
- Deciding semantic correctness, the Schema Authority and business authorization is the role of the Kernels (reconcile-facts and policy-facts) and of people. This Tsugite only checks before sending and matches what it receives afterwards.
- Having carried, registered or connected something is not approval (every result carries approval: false).
- It does not include an actual integration target. Which SaaS or ETL platform to connect to, the port implementation and the lists to pin are undecided and will be settled at a separate gate when connecting.
- This Tsugite has no N-01 service discovery or N-02 API gateway function. N-01 is handled by the Tsugite service-registry-tsugite (contract service-registry/v1), and N-02 by the Component gateway-routing-component.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/integration-etl/v1 (SchemaRegistration). Input: pass a SchemaRegistration request to submit(request) on createIntegrationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries registration_state, schema_authority and canonical_status. Every result has approval: false. User provides: a port to the integration target (submit, and optionally lookup) and the pinned lists in cfg. Side effect: EXTERNAL_REVERSIBLE (NONE for DUPLICATE and FAILED).
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-07NTsugite
Event schema evolution — Integration and ETL Tsugite
The Integration and ETL Tsugite (継手, tsugite) checks schema compatibility check requests (SchemaCompatibility) against the pinned schemas, sends them to the integration target, and matches the returned receipt's compatibility claim and test result (test_result) against the contract. Compatibility UNKNOWN is not treated as compatible (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When changing an event schema, it prevents treating a result whose compatibility has not been confirmed (UNKNOWN), or a result that failed its test, as "compatible" and so breaking backward compatibility.
When to use
- You want to match the result of a schema compatibility check before acting on it, to manage backward compatibility.
- You want something treated as compatible (compatible: true) only when a compatibility claim (BACKWARD, FORWARD or FULLY) and a test PASS are both present.
- You want to check compatibility only for pinned schemas (including event schemas).
- You do not want the same request to be sent twice. When a failure at the integration target leaves the result unknown, it is treated as UNKNOWN and resolved by a lookup.
When not to use
- You want compatibility UNKNOWN, or a claim whose test was not run (NOT_RUN), to be treated as compatible. This Tsugite does not treat them as compatible.
- You want to decide the semantic correctness of a schema or which schema is the authority (Schema Authority). That is the role of the Kernels and of people.
- You want a connector dedicated to a specific SaaS. This Tsugite is not a per-SaaS connector; it applies the same checks to every integration target.
- You want an integration target you can use right away. No actual integration target is included, and where to connect has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks schemas. Anything not in the pinned list of schemas is not sent. An empty list allows nothing.
- It checks references. Empty references, references shaped like a path or containing "..", additional fields, and values that differ from the fixed values (const) are not sent.
- It does not accept a receipt with compatibility UNKNOWN as compatible, even if the test is PASS.
- It does not accept a receipt that says compatible (compatible: true) although the test failed (FAIL), or a contradictory receipt that pairs a compatibility claim (BACKWARD, FORWARD or FULLY) with FAIL.
- It does not accept a receipt that escalates the external effect, a receipt whose effect is UNKNOWN, or a receipt for a different request (spoofed request_digest). It stops (HALTED) on a receipt it cannot accept. The effect of a compatibility check is NONE.
- It never sends the same request_id twice. It does not send a request that reuses an idempotency key with different content (REFUSED). A failure at the integration target is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- Deciding semantic correctness, the Schema Authority and business authorization is the role of the Kernels (reconcile-facts and policy-facts) and of people. This Tsugite only checks before sending and matches what it receives afterwards.
- Having carried, registered or connected something is not approval (every result carries approval: false).
- It does not include an actual integration target. Which SaaS or ETL platform to connect to, the port implementation and the lists to pin are undecided and will be settled at a separate gate when connecting.
- This Tsugite has no N-01 service discovery or N-02 API gateway function. N-01 is handled by the Tsugite service-registry-tsugite (contract service-registry/v1), and N-02 by the Component gateway-routing-component.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/integration-etl/v1 (SchemaCompatibility). Input: pass a SchemaCompatibility request to submit(request) on createIntegrationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries compatibility, test_result and compatible. Every result has approval: false. User provides: a port to the integration target (submit, and optionally lookup) and the pinned lists in cfg. Side effect: NONE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-08NTsugite
External OAuth connection management — Integration and ETL Tsugite
The Integration and ETL Tsugite (継手, tsugite) checks external connection registration requests (ExternalConnectionRegister) against the pinned connections, credential references and scopes, sends them, and matches the returned receipt against the contract. It does not run the actual OAuth flow or issue or store tokens, and does not mistake being connected for business authorization (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When registering a connection for an integration account, it prevents passing token or key values in place of references, prevents registering with scopes that are not allowed, and prevents treating "connected" as "allowed for business".
When to use
- You want to register an integration account connection, limited to the pinned connections (connection_refs), credential references (credential_refs) and allowed scopes (allowed_scopes).
- You want to pass credentials only as references, not as values (bytes).
- You want to match the registration result against the receipt's connection_state, credential_binding_state and business_authorization before acting on it.
- You do not want the same request to be sent twice. When a failure at the integration target leaves the result unknown, it is treated as UNKNOWN and resolved by a lookup.
When not to use
- You want it to run the OAuth flow, or to issue or store tokens. This Tsugite does not do that. Resolving what a credential reference points to is the role of the secret-ref / portable-credential-ref mechanism.
- You want to use a successful connection as business authorization. Deciding which integrations to allow is the role of the Kernel (policy-facts).
- You want to rely on it as a mechanism that recognizes and blocks token- or key-shaped values. This detection is a net that looks only at typical shapes and does not recognize every secret. The real guard is the pinned list of references.
- You want a connector dedicated to a specific SaaS. This Tsugite is not a per-SaaS connector; it applies the same checks to every integration target.
- You want an integration target you can use right away. No actual integration target is included, and where to connect has not been decided.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks connections, credential references and scopes. Connections or references not in the pinned lists, and scopes that are not allowed, are not sent. An empty list allows nothing.
- If a credential_ref is shaped like a token or key, it is not sent even if it is pinned.
- It does not accept a receipt that derives business authorization from a successful connection.
- It checks references. Empty references, references shaped like a path or containing "..", additional fields, and values that differ from the fixed values (const) are not sent.
- It does not accept a receipt that escalates the external effect, a receipt whose effect is UNKNOWN, or a receipt for a different request (spoofed request_digest). It stops (HALTED) on a receipt it cannot accept. The effect of registering a connection is NONE or LOCAL_REVERSIBLE.
- It never sends the same request_id twice. It does not send a request that reuses an idempotency key with different content (REFUSED). A failure at the integration target is treated as UNKNOWN and resolved by a lookup.
What it does not do / limits
- It does not run the actual OAuth flow, or issue and store tokens. Resolving what a credential reference points to is the role of the secret-ref / portable-credential-ref mechanism.
- The detection of token- or key-shaped values is a net that looks only at typical shapes; it does not recognize every secret.
- Deciding semantic correctness, the Schema Authority and business authorization is the role of the Kernels (reconcile-facts and policy-facts) and of people. This Tsugite only checks before sending and matches what it receives afterwards.
- Having carried, registered or connected something is not approval (every result carries approval: false).
- It does not include an actual integration target. Which SaaS or ETL platform to connect to, the port implementation and the lists to pin are undecided and will be settled at a separate gate when connecting.
- This Tsugite has no N-01 service discovery or N-02 API gateway function. N-01 is handled by the Tsugite service-registry-tsugite (contract service-registry/v1), and N-02 by the Component gateway-routing-component.
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/integration-etl/v1 (ExternalConnectionRegister). Input: pass a ExternalConnectionRegister request to submit(request) on createIntegrationClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; DONE carries connection_state, credential_binding_state and business_authorization. Every result has approval: false. User provides: a port to the integration target (submit, and optionally lookup) and the pinned lists in cfg. Side effect: NONE or LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-09NPrimitive
Fetch record — Fetch record (fetch.json) part (Primitive)
The Fetch record (fetch.json) part (Primitive) is a standalone part that creates and checks fetch.json (schema erabazu.raw.fetch/1), a record that holds only the facts of when, from where and what was fetched. The fetch_record inside it has the same keys and the same patterns as RawFetchReceipt.fetch_record in the contract raw-fetch/v1. It canonicalizes the record with RFC 8785 and returns its sha256. It fetches nothing.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It is a foundation for the provenance of ingestion. A record it cannot accept is not silently skipped; it is stopped with a fixed code and location, and traces of presigned URLs (://, X-Amz-Signature, X-Amz-Credential, Signature=, the form of a URL with a query) are not let into any part of the record. The record's hash is the sha256 of the canonical bytes and does not depend on key order or white space.
When to use
- You want to create a fetch.json record from the facts of a fetch (source, platform_profile, retention_class, kind_declared, fetch_state, fetch_record) (buildFetchRecord). The part adds schema and folder_stamp.
- You want to copy the fetch_record of a receipt from the contract raw-fetch/v1 (RawFetchReceipt) into the record unchanged.
- You want to read a stored record back, check it, and get the same canonical and record_sha256 (checkFetchRecord; it rebuilds folder_stamp from fetched_at and compares).
- You want a fetch that stopped while has_more was still true (PARTIAL) to be kept as a record of what was obtained.
- You want to call it locally as deterministic processing that has no clock, no random numbers and no outside connection. fetched_at is passed by the caller.
When not to use
- You want to confirm that a file exists, or that byte_size and sha256 match the content. That is the role of I-10; this part does not check it.
- You want to confirm that the record contains no secrets. The net for URL and signature traces is coarse; it cannot find secrets that do not contain :// (a token or the value of a key itself) or URLs whose traces have been broken up.
- You want to confirm that the fetch really was FETCHED or PARTIAL, or that fetched_at is correct as the fetch time or is not in the future. It accepts them as copies and checks only form and calendar.
- You want to decide the retention policy (the meaning and period of retention_class). It is not decided here.
- You want to find overlapping names on a file system that does not distinguish uppercase and lowercase. It accepts a and A as different names.
- You want to confirm that the declared media type matches the content or that charset is correct. It records the declaration as given (reading is done by I-09 and I-10).
- You use it after the contract raw-fetch/v1 has been revised without copying the patterns again. The pattern strings are copies from the contract, and after a revision they need to be copied again and re-checked. Versions other than Node.js v24.21.0 have not been checked either (NOT_VERIFIED).
What it checks
- It stops with NOT_AN_OBJECT when the record (or the build input) is not an object, and with NOT_CANONICALIZABLE (with an I-08 code in detail) when it contains values that cannot be canonicalized with RFC 8785 (fractions, undefined, getters, Date and so on). It does not call getters, and it checks on the canonicalized copy.
- It stops with UNKNOWN_FIELD when there is a key that is not defined (including schema or folder_stamp passed to build), and with MISSING_FIELD when a required key is missing. fetch_params_digest and files[].file_ref are also required.
- It stops with INVALID_SCHEMA, INVALID_SOURCE, INVALID_PLATFORM_PROFILE, INVALID_RETENTION_CLASS, INVALID_KIND_DECLARED or INVALID_FETCH_STATE when schema, source, platform_profile, retention_class, kind_declared or fetch_state is not a defined value or form. It does not accept states that have no fetch_record (NOT_READY, FAILED and so on).
- It stops with INVALID_FETCHED_AT when fetched_at does not match the contract's pattern (1970 or later, UTC Z, no leap second) or is a day that does not exist in that month (February 30, February 29 in a common year and so on), and with FOLDER_STAMP_MISMATCH when folder_stamp differs from the value rebuilt from fetched_at.
- It stops with INVALID_DIGEST when the form of a Digest (keys, algorithm, value, domain) is wrong or files[].sha256.target differs from name; with INVALID_FILES when files is empty, has the wrong keys, or byte_size is not an integer of 0 or more; with INVALID_FILE_NAME when name does not match the contract's pattern (including names containing ..); with DUPLICATE_FILE_NAME when the same name appears two or more times; and with INVALID_MEDIA_TYPE when declared_media_type is not of the contract's form or is longer than 255 characters.
- It stops with INVALID_PAGES when pages is empty, seq does not go up by 1 from 0, cursor does not chain, or has_more does not fit the state (for FETCHED only the last is false; for PARTIAL all are true).
- It stops with LINEAGE_REQUIRED when platform_profile is amazon-sp-api-reports or amazon-pay-reports but lineage is missing, and with API_VERSION_REQUIRED when it is stripe but api_version is null (for both FETCHED and PARTIAL). When lineage has the wrong form, it is INVALID_LINEAGE.
- It stops with URL_WITH_QUERY_REFUSED when any string in the record contains the form of a URL with a query, and with URL_FORM_REFUSED when it contains ://, X-Amz-Signature, X-Amz-Credential or Signature= (case-insensitive; the search runs in linear time). When it stops, it returns the first problem, in a fixed order, as code and path (a JSON Pointer).
What it does not do / limits
- Stripe (Amazon) is named only as a connection target; this is not a partnership or an endorsement.
- It fetches nothing. It does not read or write files either.
- That a record was accepted does not mean that the fetch was correct, that the files exist, that the digests match the content, that it contains no secrets, or that it may be accepted.
- Among the contract's runtime rules, it does not check those that cannot be confirmed without the request (whether the target and value of fetch_params_digest match the request), or matters outside the record.
- The readability check (I-09), the seal (H-11) and the retention policy remain outside this part.
- Creating a record is not an approval (result_is_approval: false). It creates no authority either (derives_authority: false).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for the record format of a particular fetching tool.
How to connect
Call buildFetchRecord({ source, platform_profile, retention_class, kind_declared, fetch_state, fetch_record }) and checkFetchRecord(record) in src/fetch-record.js (they do not throw). check takes a record of 8 keys including schema and folder_stamp. It returns { ok: true, errors: [], record, canonical, record_sha256 } or { ok: false, errors: [{ code, path }] }. There are no side effects, no state, no outside connection and no credentials, and there is no port for the caller to provide. Its only dependency is the Node.js built-in crypto.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-10NTsugite
Raw data fetch (Stripe) — Raw Data Fetch (Stripe) Tsugite
The Raw Data Fetch (Stripe) Tsugite (継手, tsugite) fetches the response body of each page from Stripe's list APIs exactly as received and returns it together with a fetch record (fetch_record). It checks the request (RawFetchRequest) before carrying it to the port, and matches the receipt (RawFetchReceipt) against the received bytes. It does not read the content, and neither normalizes nor judges it (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind an integration import, it prevents losing sight of whether the data is exactly as received. It recomputes the length and sha256 of the received bytes and compares them with the record, checks the cursor chain, the API version and the declarations, does not keep presigned URLs, and does not mistake a fetch for a judgement or an acceptance.
When to use
- You want to keep the responses of Stripe's list APIs (charges, payment_intents, refunds, balance_transactions, payouts, disputes, invoices and events) exactly as received.
- You want to accept the received bytes only after confirming that their byte_size and sha256 are the same as in the fetch record (fetch_record).
- You want to be able to continue after stopping partway through the pages. The result.resume_cursor of a PARTIAL result becomes the starting_after of the next request.
- You want to copy the fetch record as it is into the N-09 fetch record (fetch.json). result.fetch_record is exactly as in the receipt.
When not to use
- You want to read, type or normalize the content of the responses. Mapping to a common form, judging duplicates and reconciliation are the role of other layers and of people and rules.
- You want to have a report created (CREATE_REPORT). This Tsugite sends only op FETCH, and sends no other profile.
- You want a template for a Stripe API client. This Tsugite is not such a template.
- You want retention periods to be enforced. Enforcing retention is the role of storage (C-01) and its partitions.
- You want a fetch mechanism you can use right away. No actual fetch from Stripe has been made (0 times); the port implementation, the credentials and the setting values will be decided at a separate gate when connecting.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the request. It sends only a request with profile stripe, op FETCH, limit 1 to 100, and only one of starting_after and ending_before, and only when the account (account_refs) and the credentials (credentials) are pinned in the settings. Otherwise it does not send (REFUSED).
- Credentials are only references. It uses only references declared with purpose FETCH and scope READ_ONLY, and does not accept a configuration in which credentials contains the same reference as a signing key (signing_key_refs).
- It copies the received bytes before matching them. It accepts them only when they have the same order, names and count as files[], byte_size is the actual number of bytes, and sha256 is the sha256 of the actual bytes.
- It matches pages. It accepts only an unbroken cursor chain in which seq starts at 0 and increases by 1, where FETCHED ends with has_more false and PARTIAL stops holding the cursor for the continuation. FETCHED and PARTIAL require an api_version string.
- It does not record presigned URLs. presigned_url_recorded is false, and no value in the request or the receipt may contain ://, X-Amz-Signature, X-Amz-Credential or Signature= (case-insensitive).
- It accepts only a receipt whose declarations (kind_declared, retention_class and identity_basis) have the same values as the request.
- It does not accept a receipt with an inconsistent state, a receipt that escalates the effect (FETCH is NONE in every state), a receipt whose effect is unknown, or a receipt for a different request, and it stops (HALTED after RECEIPT_REJECTED).
- It does not send different content under the same request_id. A failure at Stripe (the port) is treated as UNKNOWN; because the request only reads, the same request may be sent again.
What it does not do / limits
- Stripe is named only as a connection target; this is not a partnership or an endorsement.
- It does not check the content of the received bytes (form, number of records, correctness, or whether they contain PII). It also does not check whether the declarations match the content, or whether declared_media_type matches the actual type of the content.
- The actual behavior of Stripe's list APIs (sort order, the direction of ending_before, has_more) was only imitated with a stand-in and has not been compared with the actual Stripe. It also does not check the form of the API version value.
- For times (observed_timestamp and fetched_at), it checks only the form and that the date and time exist; it does not check whether they are correct or stale. How many pages to fetch at once and the time budget are the role of the port.
- Records of what has been matched exist only inside this Tsugite and are lost on restart. Writing out fetch.json and the payloads is the role of N-09 and of storage.
- A successful fetch is not approval, and does not mean the content is correct, accepted or reconciled (every result carries approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/raw-fetch/v1 (RawFetchRequest / RawFetchReceipt, profile stripe, op FETCH). Input: pass a RawFetchRequest to submit(request) on createRawFetchClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; every result has approval: false. User provides: a Stripe port (submit returns { receipt, files }) and cfg (account_refs, credentials and signing_key_refs). Side effect: NONE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-11NTsugite
Raw data fetch (Amazon SP-API reports) — Raw Data Fetch (Amazon SP-API Reports) Tsugite
The Raw Data Fetch (Amazon SP-API Reports) Tsugite (継手, tsugite) fetches report documents (such as gzipped TSV) from the Amazon SP-API Reports API exactly as received and returns them together with a fetch record (fetch_record and lineage). It checks the request (RawFetchRequest) before carrying it to the port, and matches the receipt (RawFetchReceipt) against the received bytes. It does not read the content, and neither decompresses, normalizes nor judges it (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind a report import, it prevents losing sight of whether the data is exactly as received. It carries documents still gzipped, compares length and sha256, lineage and declarations, does not keep presigned URLs, and does not have a report it requested created twice.
When to use
- You want to fetch an automatically generated report (a settlement report) by report_id after the REPORT_PROCESSING_FINISHED notification (received through a separate port) (op FETCH).
- You want to have an on-demand report created and receive its report_id (op CREATE_REPORT). Only the report types listed in the setting allowed_report_types may be created.
- You want to accept received documents only after confirming that their byte_size and sha256 are the same as in the fetch record and that a GZIP document has not been decompressed.
- You want to copy the fetch record as it is into the N-09 fetch record (fetch.json). result.fetch_record is exactly as in the receipt and includes the lineage.
When not to use
- You want to decompress documents, read the TSV, or map it to a common form. That is the role of other layers, and judgement is the role of people and rules.
- You want to have an automatically generated settlement report (GET_V2_SETTLEMENT_REPORT_…) created. Those types cannot be put in the settings.
- You want to receive the REPORT_PROCESSING_FINISHED notification. The receiver for notifications is a separate port.
- You want a template for an SP-API Reports client. This Tsugite is not such a template.
- You want retention periods (such as the period for customer PII) to be enforced. Enforcing retention is the role of storage (C-01) and its partitions.
- You want a fetch mechanism you can use right away. No actual fetch from SP-API has been made (0 times); the port implementation, the credentials and the setting values will be decided at a separate gate when connecting.
What it checks
- It checks the request. It sends only requests with profile amazon-sp-api-reports, and only when the account (account_refs) and the credentials (credentials) are pinned in the settings. It does not send a report type that is not allowed, or different content under the same request_id (REFUSED).
- Credentials are only references. It uses only references declared with purpose FETCH and scope REPORTS_ONLY, and does not accept a configuration in which credentials contains the same reference as a signing key (signing_key_refs).
- It copies the received bytes before matching them. It accepts them only when they have the same order, names and count as files[], byte_size is the actual number of bytes, and sha256 is the sha256 of the actual bytes. A GZIP document is accepted only when it is declared as application/gzip and starts with the gzip header (1f 8b).
- It matches the lineage. FETCHED and PARTIAL require a lineage, and for FETCH it does not accept a lineage.report_id that differs from the request's report_id. The lineage may not contain a document URL.
- It does not record presigned URLs. presigned_url_recorded is false, and no value in the request or the receipt may contain ://, X-Amz-Signature, X-Amz-Credential or Signature= (case-insensitive).
- It matches the effect. It accepts REPORT_REQUESTED only when it claims exactly EXTERNAL_REVERSIBLE; every other state must be NONE. It does not accept a receipt that leaves unclear whether a report was created, and it stops.
- A CREATE_REPORT whose result is unknown is remembered as unresolved and resolved by sending the same request again (searching first through port.lookup, if present). It does not have a report created twice, and it sends no other request until it is resolved.
- It accepts only a receipt whose declarations (kind_declared, retention_class and identity_basis) have the same values as the request. It stops after a receipt it cannot match (HALTED after RECEIPT_REJECTED).
What it does not do / limits
- Amazon is named only as a connection target; this is not a partnership or an endorsement.
- It does not check the content of the received bytes (form, number of records, correctness, or whether they contain PII). It also does not check whether the declarations match the content, or whether declared_media_type matches the actual type of the content (for GZIP, it looks only at the first 2 bytes, the gzip header).
- The actual behavior of SP-API (report processing time, handling of CANCELLED and FATAL, the lifetime of document URLs, whether GZIP is used) was only imitated with a stand-in and has not been compared with the actual SP-API.
- An unresolved CREATE_REPORT is remembered only inside this Tsugite and is lost on restart. Sending again through a port without lookup may cause one more report to be created.
- Which types can be created on demand is left to the setting allowed_report_types. It does not know of any automatically generated type other than the settlement report.
- A successful fetch is not approval, and does not mean the content is correct, accepted or reconciled (every result carries approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/raw-fetch/v1 (RawFetchRequest / RawFetchReceipt, profile amazon-sp-api-reports). Input: pass a RawFetchRequest to submit(request) on createRawFetchClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; every result has approval: false. User provides: an SP-API port (submit, which returns { receipt, files }, and optionally lookup) and the lists in cfg. Side effect: EXTERNAL_REVERSIBLE for CREATE_REPORT only.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
N-12NTsugite
Raw data fetch (Amazon Pay reports) — Raw Data Fetch (Amazon Pay Reports) Tsugite
The Raw Data Fetch (Amazon Pay Reports) Tsugite (継手, tsugite) fetches the settlement reports that the Amazon Pay Reports API generates automatically for settlements, by report_id after the notification, exactly as received, and returns them together with a fetch record (fetch_record and lineage). It checks the request (RawFetchRequest) before carrying it to the port, and matches the receipt (RawFetchReceipt) against the received bytes. It does not have reports created (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind a settlement report import, it prevents losing sight of whether the data is exactly as received. It carries settlement reports exactly as received, compares length and sha256, lineage and declarations, does not keep short-lived download URLs, and does not mistake a fetch for a judgement or an acceptance.
When to use
- You want to fetch a settlement report by its report_id after receiving the settlement report notification (through a separate port) (op FETCH). While the report is being processed, NOT_READY is returned; for a report that does not exist, REJECTED is returned.
- You want to accept a received settlement report only after confirming that its byte_size and sha256 are the same as in the fetch record.
- You want to fetch without keeping the download URL (which expires in 30 seconds) in the record. The port uses the URL immediately and discards it; it is not passed to the Tsugite.
- You want to copy the fetch record as it is into the N-09 fetch record (fetch.json). result.fetch_record is exactly as in the receipt and includes the lineage.
When not to use
- You want to have a report created or scheduled. Settlement reports are generated automatically, and this Tsugite does not send CREATE_REPORT (it is invalid under the contract).
- You want to read the CSV or map it to a common form. That is the role of other layers, and judgement is the role of people and rules.
- You want to receive the settlement report notification. The receiver for notifications is a separate port.
- You want a template for an Amazon Pay Reports client. This Tsugite is not such a template.
- You want retention periods to be enforced. Enforcing retention is the role of storage (C-01) and its partitions.
- You want a fetch mechanism you can use right away. No actual fetch from Amazon Pay has been made (0 times); the port implementation, the credentials and the setting values will be decided at a separate gate when connecting.
What it checks
- It checks the request. It sends only a request with profile amazon-pay-reports, op FETCH and a report_id only, and only when the account (account_refs) and the credentials (credentials) are pinned in the settings. It does not send CREATE_REPORT, any other profile, or different content under the same request_id (REFUSED).
- Credentials are only references. It uses only references declared with purpose FETCH and scope READ_ONLY or NO_READ_ONLY_SCOPE_OFFERED (meaning the other side has stated that it offers no read-only scope). It does not accept a configuration in which credentials contains the same reference as a record signing key (signing_key_refs).
- It copies the received bytes before matching them. It accepts them only when they have the same order, names and count as files[], byte_size is the actual number of bytes, and sha256 is the sha256 of the actual bytes. When GZIP is stated, it accepts them only when they are declared as application/gzip and start with the gzip header (1f 8b).
- It matches the lineage. FETCHED and PARTIAL require a lineage, and for FETCH it does not accept a lineage.report_id that differs from the request's report_id.
- It does not record presigned URLs. presigned_url_recorded is false, and no value in the request or the receipt may contain ://, X-Amz-Signature, X-Amz-Credential or Signature= (case-insensitive).
- It accepts only a receipt whose declarations (kind_declared, retention_class and identity_basis) have the same values as the request.
- It does not accept a receipt with an inconsistent state, a receipt that escalates the effect (FETCH is NONE in every state), a receipt whose effect is unknown, or a receipt for a different request, and it stops (HALTED after RECEIPT_REJECTED).
- A failure at Amazon Pay (the port) is treated as UNKNOWN. Because the request only reads, the same request may be sent again.
What it does not do / limits
- Amazon is named only as a connection target; this is not a partnership or an endorsement.
- It does not check the content of the received bytes (form, number of records, correctness, or whether they contain PII). It also does not check whether the declarations match the content, or whether declared_media_type matches the actual type of the content (when GZIP is stated, it looks only at the first 2 bytes, the gzip header).
- The actual behavior of the Reports API (report processing, the lifetime of download URLs, the values of compressionAlgorithm) was only imitated with a stand-in and has not been compared with the actual Amazon Pay.
- It has not been checked whether Amazon Pay credentials can have a read-only scope. The actual purpose of the keys is checked by the port.
- Records of what has been matched exist only inside this Tsugite and are lost on restart. Writing out fetch.json and the payloads is the role of N-09 and of storage. For times, it checks only the form and that the date and time exist; it does not check whether they are correct or stale.
- A successful fetch is not approval, and does not mean the content is correct, accepted or reconciled (every result carries approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/raw-fetch/v1 (RawFetchRequest / RawFetchReceipt, profile amazon-pay-reports, op FETCH). Input: pass a RawFetchRequest to submit(request) on createRawFetchClient({ cfg, port }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; every result has approval: false. User provides: an Amazon Pay port (submit returns { receipt, files }) and cfg (account_refs, credentials and signing_key_refs). Side effect: NONE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
O-01OTsugite
Indexing, search and retrieval — Index, Search and Retrieval Tsugite
The Index, Search and Retrieval Tsugite (継手, tsugite) checks requests for index writes (UPSERT, tombstone UPSERT and DELETE), searches (KEYWORD, VECTOR, HYBRID and STRUCTURED) and retrievals (the source's digest and provenance) against the contract and the settings, carries them to the port of a replaceable index backend, and matches the returned receipt. It does not treat what is found as fact (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind "it came up in the search", it prevents losing sight of the index revision and freshness. It carries requests narrowed by the settings, compares the digest of the query document and the determinism of cursors, and does not mistake retrieved results for verified facts or for an authority.
When to use
- You want to carry index writes, searches and retrievals to an index backend (by default the user's SQLite FTS5 part; it is replaceable) with the same checks applied.
- You want to search with exact-match filtering (STRUCTURED). The sha256 of the query document canonical({filters:[{field, op, value}]}) is compared with query_digest before the document is passed on.
- When continuing through pages, you want to use only a cursor received in an ANSWERED answer for the same index, revision and query.
- When erasing personal information, you want to overwrite the index entry with a tombstone (an UPSERT with tombstone true and no content_digest).
When not to use
- You want to use retrieved results as verified facts or as an authority. Retrieved is not true, similar is not a verified fact, and rank is not authority. Verifying and judging results is the role of external decision-makers and of people and organizations.
- You want to receive the bytes of the retrieved content. Only the digest and the provenance pass through this Tsugite; whether the content matches the digest is checked by the receiving side.
- You want a template for using a specific search engine, vector database or SQLite extension. This Tsugite is not such a template.
- You want to use it on top of a category and contract that are fixed as Canonical. Category O and the contract search-retrieval/v1 are DESIGN_CANDIDATE, and this Tsugite is also a candidate built on them.
- You want an index backend you can use right away. No call to an actual backend has been made (0 times); the port implementation and the setting values will be decided at a separate gate when connecting.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- Settings only narrow and never widen. It does not send requests outside index_profiles, index_ops, retrieval_modes, max_page_size, structured_fields and source_prefixes (if DELETE is removed, DELETE is not sent). If the settings contain an unknown key, it sends nothing.
- It checks the STRUCTURED document. It does not send when the form (one or more filters; op EQ, GTE or LT; value a string; field in the setting structured_fields) or the sha256 does not match query_digest. For results, it accepts only score 1.
- It checks cursors. It does not send a cursor for a different index_revision or a different query, or a cursor it has not received. It does not accept a non-deterministic cursor, the same cursor on the first page and a following page, more results than page_size, or duplicates of the same source.
- It matches search answers. An answer that is not ANSWERED must carry neither results nor a cursor, and is accepted only when its freshness is UNKNOWN. Result items are only {source_ref, score, provenance_ref}; fields such as verified are not accepted.
- It matches the effect. INDEXED and DELETED are EXTERNAL_REVERSIBLE, search and retrieval are NONE, and UNKNOWN is allowed only for FAILED on index writes. It does not accept a receipt that escalates the effect, a receipt for a different revision or op, or a receipt for a different request, and it stops (HALTED).
- An index write whose result is not visible (a port failure, or a FAILED receipt with effect UNKNOWN) is kept as unresolved. Before sending again, it reads the index entry with EntryReadRequest / EntryReadReceipt (port.readEntry), and does not send again if the write has taken effect. When it cannot read, it does not send again, and it sends nothing else until the write is resolved.
What it does not do / limits
- It does not check whether the ACL was applied before results were returned, whether the freshness (freshness_state) is real, or whether results really match or are relevant (these are what the backend reports; results are carried as NOT_VERIFIED).
- It does not check whether the backend compared STRUCTURED values by value, or whether filter values fit the declared types (the backend answers with FILTER_INVALID).
- The meaning of VECTOR and HYBRID (which embedding, how results are combined) is not in the contract; the score is carried only as a likelihood. Vector search is not made a requirement of the backend.
- Cursor matching and records exist only inside this client and are lost on restart. A cursor from before a restart cannot continue the pages; the search starts again from the first page.
- It does not check whether a readEntry answer really is the current entry in the index. With a port without readEntry, it does not send again after FAILED or UNKNOWN, and a person checks what remains in the index.
- It does not detect personal information written into the doc_ref, acl_ref or provenance_ref strings. Keeping personal information itself out of the index is an operational rule.
- Having carried a request or added an entry to the index is not approval, and does not mean the content was verified (every result carries approval: false). It does not change the contract, and the schema authority for the contract is NOT_SPECIFIED.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/search-retrieval/v1 (Request and Receipt for Index, Query, Retrieve and EntryRead). Input: pass a request, and for STRUCTURED the query document, to submit(request, query) on createSearchRetrievalClient({ cfg, port, clock? }). Returns: one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED; every result has approval: false. User provides: an index backend port (submit, and optionally lookup and readEntry) and the lists in cfg. Side effect: EXTERNAL_REVERSIBLE for index writes; NONE for search and retrieval.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
O-02OComponent
Fetch and transaction index — Fetch and transaction index (fetch.db, txn.db) part (Component)
The Fetch and transaction index part (Component) is a part that bundles the indexing and search Tsugite (O-01), the record check (the N-09 part (Primitive)) and the transaction event ID (the G-12 part (Kernel)) and builds two indexes. Into the fetch index it puts every raw fetch, and into the txn index the transaction date, transaction amount, counterparty and raw references, with UPSERT through O-01. fetch.json is checked with N-09 and txn_id with G-12, and nothing is put in when they disagree. Queries are STRUCTURED, and the results are txn_id candidates. It does not send DELETE. The index is an aid to finding things, and its results are not facts.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It is a part for the index behind searching the books. It prevents putting unchecked records or transactions whose txn_id does not match into the index, putting a pii counterparty as is into an append-only index, erasing index entries with DELETE, and reading a query the index did not answer as "a success with no results".
When to use
- You want to check the fetch.json record with N-09 and put one fetch (source, folder_stamp, fetched_at, retention_class, the probe state and version, the number of files) into the fetch index with UPSERT (indexFetch). doc_ref is raw/<source>/<folder_stamp>.
- You want to recompute the txn_id of a transaction in the common form (erabazu.common.txn/1) with G-12 and put only the transactions that match into the txn index (indexTxn). The document is occurred_on, amount_key, currency, counterparty_key and raw_refs, and doc_ref is txn_id.
- You want to search transactions by transaction date (EQ, GTE, LT), amount (EQ, GTE, LT on amount_key, combined with EQ on the same currency), and currency and counterparty (EQ only), and receive txn_id candidates (queryTxn).
- You want to build query values by turning an amount into a 58-character fixed-width amount_key whose string order is the same as numeric order, and a counterparty into a counterparty_key (a value starting with "id:" or "name:"; sha256 for pii) (amountKey, counterpartyKey).
- You want to remove a pii transaction from an UPSERT-only index. It overwrites the txn index entry with a tombstone (an UPSERT with tombstone true and no content_digest) (tombstoneTxn). A tombstoned transaction is not put back in by this instance.
- You want to resend an index write whose result is unknown (including a tombstone) as the same request (resolvePending).
When not to use
- You want to use the query results as facts about the transactions that meet the conditions, or as a complete list. The results are candidates (NOT_VERIFIED); whether a matched transaction really meets the conditions and whether the index has no gaps are as stated by the backend.
- You want to treat the sha256 of a pii counterparty as anonymization. The sha256 has no salt (secret key), so it can be found by trying candidate names or ids one by one (it is pseudonymization, not anonymization). Dates, amounts, currency and raw references go into the index as they are, even for pii transactions.
- You want to compare amounts across currencies. Filtering on amount_key is meaningful only within the same currency, and it does not convert currencies.
- You want full-text search. Full-text search (FTS5) is on the backend side; this part only sends structured filters. There is no operation in v1 for querying the fetch index.
- You want a tombstone to also delete the raw data in storage. A tombstone only overwrites the index entry. It does not check whether storage really deleted the raw data. Entries in the fetch index have no tombstone operation.
- You want to prevent a tombstoned transaction from being put back in after a restart or from another process. The record of tombstoned txn_id values is only inside this instance. Where to keep that record is up to the caller's operations.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When options are missing or malformed or the two profiles are the same, it stops with INVALID_OPTIONS; when the input is malformed, with INVALID_INPUT; when a transaction record is not in a shape usable for the index, with INVALID_TXN; and when a query is malformed (for example, amount_key without an EQ on currency), with INVALID_QUERY (with the position in where).
- When N-09 does not accept fetch.json, it reports FETCH_RECORD_REFUSED, and when N-09 throws or its answer is inconsistent, CHECK_PORT_FAILED; and it sends nothing. When a different fetch.json comes for the same folder_ref, or a different document for the same probe_version, it does not send (FETCH_INDEX_CONFLICT) (a correction uses a new probe_version).
- When G-12 does not accept the input, it reports TXN_ID_REFUSED; when G-12 throws or its answer is inconsistent, TXN_ID_PORT_FAILED; and when the recomputed txn_id differs from the record, TXN_ID_MISMATCH (with recomputed_txn_id attached); and it does not send. G-12 receives only copies of the 5 facts.
- When a txn_id sent as pii is sent or tombstoned in another partition, it reports TXN_CLASS_CONFLICT, and when there is an attempt to put a tombstoned txn_id back in, TXN_TOMBSTONED; and it does not send.
- When the result of an index write is unknown, it holds it as pending with INDEX_UNKNOWN and accepts no operations while pending (INDEX_PENDING). When the Tsugite did not send, it reports INDEX_REFUSED, and when the entry was not indexed, the answer cannot be matched, or it has a different doc_ref, a different op, a different index_revision or DELETED, INDEX_FAILED.
- When the result of a query is unknown, it reports QUERY_UNKNOWN; when the Tsugite did not send, QUERY_REFUSED; and when the results fail the result check (score 1, NOT_VERIFIED, the version sent, the shape and duplicates of txn_id) or a tombstoned txn_id is in the answer, QUERY_FAILED. When the index did not answer (query_state is not ANSWERED), it returns QUERY_NOT_ANSWERED with query_state.
- In amountKey, when the amount is outside the shape or range (38 digits, scale 0 to 18), it reports INVALID_AMOUNT; in counterpartyKey, when the counterparty is malformed, INVALID_COUNTERPARTY; and when the partition is none of the 3, INVALID_RETENTION_CLASS. When timestamp does not return an existing date-time, it sends nothing (CLOCK_FAILED), and when the previous operation has not finished, it reports BUSY.
What it does not do / limits
- The Fetch and transaction index part is not a Tsugite. It has no Request or Receipt of its own; index writes and queries are made by the bundled O-01 Tsugite under its own contract (search-retrieval/v1) (queries are NONE). This part itself does not touch the outside.
- The meaning of the bundled parts (indexed ≠ the content was checked, similar ≠ fact, the record was accepted ≠ the fetch is correct, the ID matches ≠ the transaction exists) stays on each part's side. The index backend (SQLite, FTS5) and full-text search are on the backend side.
- The results are neither facts nor approval (approval: false, authority: NOT_DERIVED, result_verification: NOT_VERIFIED, derives_authority: false).
- It does not send DELETE. It sends only IndexRequest with UPSERT and QueryRequest with STRUCTURED.
- For transaction records, it checks only the fields used by the index and the set of keys. Checking the content, such as line items, is the job of G-13. retention_class is used as passed by the caller, and whether it matches the raw partition is not checked. Passing the document content to the backend is the job of the wiring between the caller and the backend.
- In this part's tests it is not connected to the real O-01, N-09 or G-12. The unit tests and fuzzing use stand-ins. The wiring check passed, but the wiring for tombstones, query_state and some other paths is not in that check. It is not connected to a real index backend. The state is only inside the process and is lost on restart.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for a particular search engine or database.
How to connect
Create it with createLedgerIndex(options) in src/ledger-index.js; it returns { ok, errors, instance }. options are the profiles and revisions of the two indexes, request_prefix, ports and an optional timestamp. The operations are indexFetch, indexTxn, tombstoneTxn, queryTxn, resolvePending, status, amountKey and counterpartyKey, and the results carry approval: false. Ports: index.submit (the client of the O-01 Tsugite), checkFetchRecord (N-09) and computeTxnId (G-12). The field types of the txn index are declared with TXN_FIELD_TYPES.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-01PKernel
Contract and input check — Contract and input check part (Kernel)
The Contract and input check part (Kernel) is a standalone part that, from a schema's id and digest, the schema, a value (a JSON string or an already-parsed value) and a declaration of capabilities, outputs as a fact whether the value conforms to the schema (conforms). It handles only a fixed subset of schema keywords; a schema containing other keywords, or a schema whose digest does not match, is not trusted and is not treated as conforming. Issues are listed with their locations (JSON Pointer). The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Checking the form of input is always needed behind the screen. It prevents treating a value as conforming without knowing which schema it was checked against, overlooking duplicate keys in JSON, and processing that never ends because of catastrophic backtracking in a pattern's regular expression (ReDoS).
When to use
- You want to output as a fact whether a value conforms to a schema the caller passes (type, properties, required, additionalProperties: false, enum, const, items, minItems/maxItems, minLength/maxLength, minimum/maximum, pattern) (checkContract).
- You want to confirm the identity of the schema before checking. If schema_ref.digest does not equal "sha256:" followed by the hex SHA-256 of the RFC 8785 canonical form, the value is not checked, the result is SCHEMA_UNKNOWN, and conforms is false. The digest alone can be produced with schemaDigest({ schema }).
- You want a JSON string (value_text, up to 1 MiB in UTF-8) read by the part's own strict reader, and duplicate keys returned as DUPLICATE_KEY with their locations. For duplicates, the later value is checked, as with JSON.parse.
- In the same call, you also want to receive the missing fields (missing) and the required capabilities that are not declared (capabilities_missing: the set capabilities_required minus capabilities_declared).
- You want missing input made need_input rather than treated as empty. When there is no digest, or when capabilities_required is present and capabilities_declared is not, state is need_input.
- You want to receive issues in a fixed order, up to 1000, and to know from issues_total and issues_truncated that the list was cut.
- You need byte-for-byte identical output from the same input without using the clock, random numbers or the environment's locale. The same schema gives the same digest regardless of key order.
When not to use
- You need full JSON Schema validation. It does not handle the reference keyword ref, allOf/anyOf/oneOf, format, if/then, the null type, arrays of types, tuple items, fractional ranges for numbers and so on; these give UNSUPPORTED_KEYWORD or INVALID_SCHEMA.
- You want it to confirm that a declaration of capabilities is genuine (the authenticity of authority). This part only outputs the difference between the declared sets; the Host confirms authenticity.
- You want to use back-references, lookbehind, lookahead, nested quantifiers, \p, case-insensitive matching, the m flag or the s flag in a pattern. Forms it does not handle give SCHEMA_PATTERN_UNSAFE and do not conform.
- You want it to judge whether a value is correct for the business (the meaning of the schema).
- You want duplicate keys checked on an already-parsed value (value). Duplicate keys are not visible in value, and duplicate_keys_checked is false. Non-integer numbers are accepted but match no type and no enum.
- You expect the same results as the I-01 input schema validation part. Both the range of keywords and the form of the results differ, and they are not compatible.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the argument is malformed (an unknown key, a getter, no schema_ref or schema, both or neither of value_text and value, a malformed schema_ref), it stops with INVALID_ARGUMENT, and when inspecting the values itself throws an exception (a Proxy trap and so on), with INSPECTION_FAILED.
- When a known keyword is malformed (type is not one of the 6 names, duplicates in required, an empty enum, items that is not a schema, a negative count, a pattern that cannot be read as a regular expression with the u flag, and so on), it stops with INVALID_SCHEMA. Unknown keywords and values of additionalProperties other than false become UNSUPPORTED_KEYWORD issues, and the schema is not trusted.
- A pattern is limited to 128 characters, 4,096 instructions and {n,m} up to 1000, and back-references, lookbehind and nested quantifiers become SCHEMA_PATTERN_UNSAFE. Matching uses the part's own linear-time matcher (a Pike VM); the total work is counted in advance, and when it exceeds 50,000,000 it stops with RESOURCE_LIMIT before matching begins.
- When there is no digest the state is need_input, and when the digest differs the state is done; in both cases there is a SCHEMA_UNKNOWN issue and the value does not conform. The only accepted notation is "sha256:" followed by lower-case hex; upper case or a missing prefix does not match. When capabilities_declared is absent, the state is also need_input.
- When value_text cannot be read as JSON (RFC 8259) (including a BOM, a leading zero, control characters or invalid escapes), it stops with INVALID_JSON, and a lone surrogate stops with LONE_SURROGATE, both with the character position offset. Exceeding 64 levels of nesting, 1 MiB or the limit on the number of values gives RESOURCE_LIMIT.
- When value is not plain JSON (undefined, a function, BigInt, NaN, Infinity and so on), it stops with INVALID_VALUE, and when a list of capabilities is not an array of unique strings of 1 to 128 characters (up to 256 items), with INVALID_CAPABILITIES. "__proto__" is treated as an ordinary key, and the prototype is not rewritten.
- Fuzzing that compares the results against an independent oracle rewritten from the rules without using the helper functions in src confirmed that mismatches and fail-open cases were 0. Pattern matching was compared against the Node.js RegExp (u flag) on the oracle side.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null; the result is not an approval (result_is_approval: false) and creates no authority (derives_authority: false).
- The results are facts about whether a value conforms to a fixed subset of a schema. They do not show that the meaning of the value is correct, that its use is permitted, or that authority is genuine, and they are neither a data validation service nor a decision on authority.
- The meaning of pattern matching was compared with the Node.js v24.21.0 RegExp (u flag) using generated patterns and strings. It is not a proof covering all of ECMAScript regular expressions.
- The limit on matching work (pattern_steps) and the limit on the number of values in value (value_nodes) were confirmed by unit tests only (the fuzzing does not generate them).
- It holds no business-specific rules. The caller passes the schema.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call checkContract({ schema_ref, schema, value_text | value, capabilities_declared?, capabilities_required? }) in src/contract-check.js (it does not throw exceptions). Returns: { ok: true, errors: [], facts } (state, conforms, schema_trusted, issues, missing and so on, with inference: null and advice: null) or { ok: false, errors: [{ code, path, ... }] }. schemaDigest({ schema }) outputs only the digest. There is no side effect, no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-02PKernel
Mapping, transformation and reconciliation — Mapping, transformation and reconciliation part (Kernel)
The Mapping, transformation and reconciliation part (Kernel) is a standalone part that copies records of a source schema into the target form using a mapping table (source location to target location, with a transform) and reconciles the counts, keys and sums. It applies only mappings that are confirmed (confirmed) and not AI-derived (ai_candidate), and lists candidates as candidates. Conflicting mappings are not applied, and missing, unknown, zero and empty are counted separately. The result of the transformation is a candidate, not a final result. The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When copying data across for a migration or an integration, it prevents applying unconfirmed mappings or AI proposals as facts, filling values that are not in the table with guesses, merging two values with different meanings (such as paid and slot reserved) into one, and sums going wrong through floating point or rounding.
When to use
- You want to copy records into the target form using only confirmed mappings (reconcileMap). Mappings whose status is candidate and mappings whose origin is ai_candidate are not applied; they are listed in candidates_not_applied with a reason (STATUS_CANDIDATE, ORIGIN_AI_CANDIDATE).
- You want to limit transforms to four (copy for copying, enum_map for replacement by a table, unit for exact decimal multiplication, decimal_scale for aligning decimal places). There is no rounding; rounding is "none" only.
- You want to reconcile counts, keys and sums. Sums add the decimal strings and integers at the sum_paths locations exactly with BigInt, and the results of comparing source, target and expected are output together with the number of values that could not be added.
- You want missing (missing_source_values), unknown (unknown_values; copied as { "unknown": true }), zero and empty counted separately. Zero, empty strings and empty arrays are copied as values.
- You want to receive, in lists and without anything hidden, unmapped fields, values not in the table, values that could not be transformed, and duplicate and missing keys (unmapped_fields, unmapped_values, transform_failures, duplicate_keys, missing_keys).
- You need byte-for-byte identical output from the same input without using the clock, random numbers or the environment's locale. There is no external boundary and no state.
When not to use
- You want it to judge whether the meaning of a mapping is correct (whether same-name-different-meaning cases were really told apart). SAME_NAME_DIFFERENT_TARGET only lists mappings from the same source location to different target locations, and it does not find the same meaning under different names.
- You want it to confirm that a confirmation (status: confirmed, origin) is genuine. That depends on the caller's records. When a person adopts an AI proposal, the caller sets origin to human or registered when passing it.
- You need locations that go into arrays by index, or a transform per array element. In v1, an array is copied whole or counted as an unmapped leaf.
- You want to check a system of units (dimensions, correctness of the conversion). It only compares the from and to name strings, and the correctness of factor is the caller's responsibility.
- You need rounding. If decimal_scale would need to discard non-zero digits, the value is counted in transform_failures as INEXACT_SCALE.
- You want to apply or store the results of the transformation. This part only outputs the copied candidates; atomic application is a role outside this part.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the argument is malformed (an unknown key, a required key absent, an id or version not 1 to 256 characters, duplicates in sum_paths), it stops with INVALID_ARGUMENT, when inspecting the values itself throws an exception, with INSPECTION_FAILED, and when a value is not in JSON Pointer form (more than 16 levels, more than 1024 characters, a lone surrogate), with INVALID_PATH.
- When the mapping table is malformed, it stops with INVALID_MAP, and when the kind of a transform is not one of the four or rounding is not "none", with UNSUPPORTED_TRANSFORM (rounding is never implicit).
- When a record is not a plain JSON object (null, a non-integer number, a lone surrogate, a form resembling { "unknown": true }), it stops with INVALID_RECORDS, when expected is malformed, with INVALID_EXPECTED, and when a limit is exceeded, with RESOURCE_LIMIT (with limit). The prototype is not rewritten through a "__proto__" key.
- Among the candidate mappings to apply, it lists as conflicts two for the same target (TWO_SOURCES_ONE_TARGET), nested target locations (TARGET_PATH_OVERLAP), the same source to different targets (SAME_NAME_DIFFERENT_TARGET) and different from values for unit on the same source (UNIT_MISMATCH), and does not apply them.
- When, in one enum_map table, two or more source values map to the same target value, it lists them as MERGED_ENUM_VALUES and does not apply the mapping unless merge_confirmed: true is present.
- Values not in the enum table are not filled by guesses but counted in unmapped_values; non-decimal values (NOT_A_DECIMAL) and values that would lose digits (INEXACT_SCALE) are counted in transform_failures; in both cases that field of converted is not created. If no mapping that reads key_path is applied, state is need_input (KEY_NOT_MAPPED).
- Fuzzing that compares the results against an independent oracle rewritten from the rules without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null; the result is not an approval (result_is_approval: false) and creates no authority (derives_authority: false).
- The results are candidates copied mechanically from the mapping table and the records, and facts of reconciliation. They are not the correctness of a mapping's meaning, the adoption of a data migration, or permission to apply it. Adoption and application are done by the Host, the Runtime and a person.
- It does not make AI-derived mappings (ai_candidate) into facts. converted is a candidate, not finalized or applied.
- It holds no business-specific mapping tables of meaning. The caller passes the mapping table.
- The limit on the number of values in records (record_nodes) was confirmed only by unit tests and rare fuzz generation, and the limit on expected.keys (100,000) has not been tested.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call reconcileMap({ source_schema_id, target_schema_id, source_version, map, records, key_path, sum_paths?, expected? }) in src/map-reconcile.js (it does not throw exceptions and does not modify the input). Returns: { ok: true, errors: [], facts } (converted, candidates_not_applied, conflicts, reconciliation, need_input and so on, with inference: null and advice: null) or { ok: false, errors: [{ code, path[, limit] }] }. There is no side effect, no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-03PKernel
Diff, inverse operation and conflict check — Diff, inverse operation and conflict check part (Kernel)
The Diff, inverse operation and conflict check part (Kernel) is a standalone part that, for JSON documents, outputs the diff between two versions (a patch with old values), the inverse of a patch, a candidate with a patch applied, and an undo plan per purpose (intent). Before applying, it checks that the base version matches, protected locations and the expected prior values; for undo, it lists, without hiding them, overlaps with later separate changes and changes that cannot be inverted. It does not finalize or store anything. The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
In the diffs behind "undo", it prevents applying a patch to an old version, rewriting protected locations, breaking later separate changes when undoing, and getting by with inverting only part of a change that cannot be inverted.
When to use
- You want the diff between two versions of JSON output as a patch with old values (add, remove, replace) (diff). Object keys are walked in UTF-16 code unit order.
- You want the inverse of a patch (invert). Operations that cannot be inverted (remove or replace without old) are all listed as NOT_INVERTIBLE, and in that case no inverse patch is output.
- You want a candidate with a patch applied, without finalizing it (apply). If base_revision differs from document_revision it gives STALE_BASE, and if a protected location is touched it gives PROTECTED_PATH; all of these are listed before applying. committed is always false.
- You want an undo plan per purpose (intent) (plan_undo). It inverts the changes with the same intent, newest first, and chains them; locations that overlap later changes that are not undone are listed as LATER_CHANGE_OVERLAPS.
- You want intent assignments made by AI (intent_origin: ai_candidate) kept out of the facts and separated into candidate_intent_changes. Those changes are not included in the undo, and they are treated as kept changes that overlaps are checked against.
- You need byte-for-byte identical output from the same input without using the clock, random numbers or the environment's locale. There is no external boundary and no state.
When not to use
- You want to finalize or store changes, or to assign version numbers. Atomic application, storage and version numbering are roles outside this part, and apply only outputs a candidate.
- You need character-level diffs inside strings. If strings differ, the result is a single replace (editing character fragments of a document is within the scope of P-09).
- You want to detect insertion or movement of array elements. It does no alignment (LCS and so on), so an insertion at the front becomes a series of replaces of the following elements.
- You want to use it as RFC 6902 JSON Patch. It does not handle move, copy, test or the "-" for the end of an array, and it also differs in carrying old values and in add not placing a value at a key that already exists.
- You want it to map natural-language purposes to changes. The caller decides the assignments, and assignments made by AI are passed as ai_candidate.
- You want to use it to undo sending something outside or a payment. These are out of scope.
- You want to see semantic dependencies (one change relying on another). It looks only at overlapping locations (semantic dependencies are within the scope of P-04).
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the argument is not a plain object holding only the fixed keys (an unknown key, a missing key, a getter, a non-enumerable property, a symbol key), it stops with INVALID_ARGUMENT, when inspecting the values itself throws an exception, with INSPECTION_FAILED, and when operation is none of the four, with UNSUPPORTED_OPERATION.
- When a document, value or old is not a JSON value (undefined, NaN, a lone surrogate, a key named __proto__, a sparse array, an array subclass, a class instance and so on), it stops with INVALID_VALUE.
- When a patch op is move, copy or test, it stops with UNSUPPORTED_OP, when a patch is malformed (the path of add or remove is the root), with INVALID_PATCH, and when a JSON Pointer is malformed (including a token named __proto__), with INVALID_POINTER.
- When a version is not a well-formed string of 1 to 128 characters, it stops with INVALID_REVISION, when origin or intent_origin is outside the vocabulary, with INVALID_ORIGIN, when a change is malformed, with INVALID_CHANGE, when two changes have the same change_id, with DUPLICATE_CHANGE_ID, and when a limit (10,000 operations, depth 64, 200,000 values and so on) is exceeded, with RESOURCE_LIMIT (with limit).
- In apply, STALE_BASE and PROTECTED_PATH (equal, below, above, or a location moved by re-indexing an array) are all listed before applying, and if either is present nothing is applied. Otherwise the operations are applied in order on a copy, stopping at the first PRECONDITION_FAILED (old does not match, or a value already exists at the add location) or PATH_NOT_FOUND (including "-" and leading zeros).
- In plan_undo, breaks in the history (HISTORY_GAP), differences from the current version (STALE_BASE), changes that cannot be inverted (NOT_INVERTIBLE) and overlaps with later changes (LATER_CHANGE_OVERLAPS) are listed in conflicts, and if there is even one, no undo_patch is output. If there is no change to undo, the result is no_matching_change.
- Unit tests and fuzzing confirmed that the candidate of apply(base, diff(base, target)) equals target and that the candidate of apply(target, invert(diff(base, target))) equals base.
- Fuzzing that compares the results against an independent oracle rewritten from the rules without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null; the result is not an approval (result_is_approval: false) and creates no authority (derives_authority: false).
- The results are a presentation of facts (diffs, inverses, candidates, plans), not the finalizing or storing of changes or the execution of an undo. Which change belongs to which purpose, and whether to adopt a candidate, are decided outside this part (by the user or the Runtime).
- Array re-indexing is determined from the form of the token only. Objects with numeric-looking keys are also treated as re-indexing, so it stops more often than strictly needed.
- It does not use need_input. facts.state is always done, and the reasons for stopping are listed in refusals, conflicts and not_invertible (in that case ok is true).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call patchConflict({ operation, ... }) in src/patch-conflict.js. diff, invert, apply and planUndo can also be called directly (they do not throw exceptions and do not modify the input). Returns: { ok: true, errors: [], facts } (state: "done", operation and the facts of each operation, with inference: null and advice: null) or { ok: false, errors: [{ code, path }] } (RESOURCE_LIMIT also carries limit). The caller holds the history, the versions and the storage. There is no side effect, no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-04PKernel
Provenance and dependency graph — Provenance and dependency graph part (Kernel)
The Provenance and dependency graph part (Kernel) is a standalone part that, from versioned nodes, edges (registered ones and AI candidates, ai_candidate) and a set of changes, outputs separately the nodes reached downstream through registered edges only (impacted, with the shortest path) and the nodes that cannot be reached without candidate edges (candidate_impacted). It also outputs as facts cycles, edges to absent nodes or with mismatched versions, nodes outside the caller's permission, and nodes that were not reached. The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
The scope of a change's impact cannot be seen from a table. It prevents mixing AI-candidate dependencies with registered dependencies as facts, silently following missing edges or version mismatches, and treating nodes that were not reached as "unrelated".
When to use
- From the changes (changed), you want the nodes the impact reaches through registered edges only, with depth and the shortest path (path, edges) (analyzeProvenance). Among paths of the same length, the one whose sequence of node ids is smallest is chosen.
- You want the nodes that cannot be reached without AI-candidate edges (ai_candidate) separated into candidate_impacted, and to also receive the indexes of the candidate edges in the path. impacted and candidate_impacted do not overlap.
- You want cycles received separately as strongly connected components of registered edges only (cycles) and as components that first appear or grow with candidate edges (candidate_cycles).
- You want edges to absent nodes (NODE_ABSENT) and edges with mismatched versions (VERSION_MISMATCH) received as missing, without following them, and state set to need_input.
- For nodes outside the caller's permission (allowed: false), you want only the id output in not_allowed even when they are reached, without kind or version. Reachability continues through such a node.
- You want to set the search depth (max_depth, 1 to 256, 256 if absent) and to know from depth_limit_hit (separately for the registered search and the search including candidates) that the search was cut by depth.
- You need byte-for-byte identical output from the same input, regardless of the order of nodes or keys, without using the clock, random numbers or the environment's locale.
When not to use
- You want it to judge whether there is an impact or whether a fix is needed. It only outputs the facts of reachability and gaps.
- You want to show that nodes that were not reached are unrelated. unreached is the fact that they were not reached in the search, not proof that they are unrelated (unreached_meaning: not_reached).
- You want the way edges are followed to vary by edge type (kind). It does not read kind and follows every kind the same way.
- You want to see the order of versions. from_version and to_version are only compared as strings for equality.
- You want it to confirm that the allowed marks are correct (the authenticity of authority), or to block the contents of nodes outside the permission. Both are the Host's role, and the Host does not pass the contents of nodes outside the permission.
- You want the part to decide the direction of edges. An edge { from, to } is passed in the direction "a change to from affects to" (for provenance, origin to derivative).
- You use it on a version other than Node.js v24.21.0 (NOT_VERIFIED), or you want to rely on the time and memory for inputs at the full limits. Inputs at the full limits were checked only once each in unit tests, and time and memory were not measured.
What it checks
- When the input is not plain data (getters, class instances, sparse arrays, extra properties on arrays, symbol keys, non-enumerable properties), or when there is an unknown key, a missing key, null, a value outside the vocabulary (an origin other than registered or ai_candidate and so on), an id that is not 1 to 128 characters, or a lone surrogate, it stops with INVALID_INPUT.
- When max_depth is not an integer from 1 to 256, it stops with INVALID_INPUT, when inspecting the values itself throws an exception (a Proxy trap, a revoked Proxy), with INSPECTION_FAILED, and when ids overlap, with DUPLICATE_ID.
- When a limit (100,000 nodes, 400,000 edges, 100,000 changed, a total of 4,000,000 node ids in the output paths) is exceeded, it stops with RESOURCE_LIMIT, putting which limit in limit. It does not turn a failure into an empty success.
- When from_version or to_version is written on an edge and differs from that node's version, it outputs VERSION_MISMATCH in missing, and when an end node is absent, NODE_ABSENT. Such an edge is not followed and is not included in the cycle calculation.
- A changed node with a mismatched version is used as a starting point, because the change is a fact stated by the Host, and VERSION_MISMATCH is also output. The changed nodes themselves are not put into impacted, candidate_impacted or unreached.
- When it stops, it returns only the first problem found, as { code, path, limit? }. path is a JSON Pointer (the empty string for the whole input).
- Fuzzing that compares the results against an independent oracle rewritten from the rules without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null; the result is not an approval (result_is_approval: false) and creates no authority (derives_authority: false). It does not turn AI-derived items (ai_candidate) into facts.
- The results are a presentation of facts about reachability and gaps, not a judgement of whether there is an impact or whether a fix is needed. Nor do they judge impact or plan fixes on anyone's behalf. Final authority, blocking and application are the roles of the Host and the Runtime.
- When cut by max_depth, a node that lies deep along registered edges can end up in candidate_impacted through a short path over candidate edges (in that case depth_limit_hit.registered is true).
- The input and output forms are those set in v1, and acceptance by the side that uses them has not yet been done.
- It uses no files or network communication and holds no credentials.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call analyzeProvenance({ nodes, edges, changed, max_depth? }) in src/provenance-graph.js (it does not throw exceptions and does not modify the input). Returns: { ok: true, errors: [], facts } (state, impacted, candidate_impacted, cycles, candidate_cycles, missing, not_allowed, unreached, depth_limit_hit and need_input, with inference: null and advice: null) or { ok: false, errors: [{ code, path, limit? }] }. There is no side effect, no state, no external connection, no credentials and no dependency package, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-05PKernel
Constraints, assignment and time slots — Constraints, assignment and time slots part (Kernel)
The Constraints, assignment and time slots part (Kernel) is a standalone part that, from tasks written in whole minutes (duration, whether indivisible, the minimum when split, prerequisites, resource, time window) and slots (start, end, resource), runs a bounded search in a fixed order and returns as facts an assignment plan, the reasons it cannot be done, and whether the search limit was reached. infeasible is returned only when it is proven; stopping at the limit is kept apart as search_limit. What is unknown is not guessed but made need_input, and malformed input is stopped with a fixed code and location (fail-closed). The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When a schedule is built, it prevents shrinking an indivisible task to fit into a short slot, calling something "impossible" when the search was only cut off partway, presenting a plan as optimal when its optimality has not been proven, and filling unknown durations or time windows with guesses.
When to use
- You want an assignment plan from tasks (0 to 200) and slots (0 to 200) for the objective set in objective (solveSchedule). There are three objectives: feasible (a plan that places every task), min_unassigned (the fewest tasks left out) and earliest_finish (the earliest latest end when every task is placed).
- You want an indivisible task (indivisible: true) placed whole in one slot, at its full duration.min. If the overlap of the slot and the time window is shorter than min, the task is not put in that slot (a 20-minute indivisible task is not put in a 10-minute slot).
- You want a splittable task split and placed so that every part is at least splittable_min and the parts add up to exactly duration.min. Parts of the same task do not overlap in time, and part is numbered from 1 in time order.
- You need an assignment that keeps the prerequisites (requires), resources (resource) and time windows (window). A prerequisite task ends before every part of this task, and if a prerequisite task is not placed, this task is not placed either. A resource match is required only when both the task and the slot have a resource.
- You want to receive, with reason codes, that a plan cannot be made. The static reasons (NO_SLOTS, NO_SLOT_FOR_RESOURCE, WINDOW_EMPTY, INDIVISIBLE_TOO_LONG, CAPACITY_TOO_SMALL, PRECEDENCE_CYCLE) and the marker SEARCH_EXHAUSTED, which shows the search was carried through to the end, are put in reasons.
- You want to bound the search steps with max_nodes (1 to 1,000,000) and receive, kept apart, whether the limit was reached, through search_limit_reached and status (search_limit), and the optimality, through optimality (proven or not_proven).
- You want to receive, kept apart, whether AI-derived tasks (origin: ai_candidate) are included, through origin on each assignment row, ai_candidate_tasks and plan_includes_ai_candidate.
- You need byte-for-byte identical output from the same input, regardless of the order of arrays or keys, without using the clock, random numbers or the environment's locale.
When not to use
- You want it to decide whether to adopt a plan, re-split tasks, or whether durations are correct. The result is the facts of an assignment plan, the reasons it cannot be done and the search limit; it is not a decision to adopt or carry out anything.
- You want the part to decide how tasks are split or how long they take. The caller decides both and passes them. This part does not create tasks and does not guess durations.
- You want it to stop different tasks from being placed at the same time in two overlapping slots of the same person. Slots are treated as independent containers. For one person's schedule, the caller passes slots that do not overlap.
- You want to handle travel or set-up time between tasks, calendars or time zones, or times finer than a minute. Times and lengths are only whole minutes from 0 to 100,000,000.
- You need objectives such as weights, priorities, soft constraints or the least lateness against deadlines. There are only three objectives; anything else stops with UNSUPPORTED_OBJECTIVE. min_unassigned counts the number of tasks, not their length.
- You want to use the slack of a task that can take longer than min. duration.max is only checked for its form; the length placed is always duration.min.
- You want to solve a large problem within a fixed time. The search is exponential, and a large problem can end in search_limit. Speed is not promised. It always stops when it reaches max_nodes.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the argument is not a plain object holding exactly the four keys { tasks, slots, objective, search }, it stops with INVALID_ARGUMENT; when inspecting the values itself throws an exception (a Proxy trap and so on), with INSPECTION_FAILED; and when it is not plain data (getters, class instances, sparse arrays, extra properties on an array, symbol keys, a __proto__ key, non-enumerable keys), with NOT_PLAIN_DATA. It checks the values after copying them into plain data, and the values it checks are the same values the search uses.
- When a task is malformed (an unknown key, a missing key, a malformed id, origin or indivisible, a duration.min below 1, a max below min or a non-integer, a malformed unknown marker, splittable_min on an indivisible task, a splittable_min above min, duplicated or non-array requires, a time window whose end is not after its start, or a negative value), it stops with INVALID_TASK. It does not accept null.
- When a slot is malformed (an end not after its start, a non-integer, a negative value and so on), it stops with INVALID_SLOT; when search is not { max_nodes }, with INVALID_SEARCH; when objective is none of the three objectives, with UNSUPPORTED_OBJECTIVE; when two tasks or two slots have the same id, with DUPLICATE_ID; and when requires points to the id of a task that does not exist, with UNKNOWN_TASK_REF.
- When a limit is exceeded (200 tasks, 200 slots, 200 prerequisites per task, max_nodes 1,000,000, times and lengths of 100,000,000 minutes, 4096 array elements, 4 levels of nesting), it stops with RESOURCE_LIMIT, putting which limit in limit. When it stops, it returns { code, path, limit? }, where path is a JSON Pointer.
- When duration or window is the unknown marker { unknown: true }, it does not guess: it sets state to need_input and lists what is missing in need_input. It does not search; status, assignment and objective_value are null and reasons is empty (no partial result is returned).
- For feasible and earliest_finish, if there is even one static reason, it does not search and returns infeasible (nodes_explored is 0). If the limit is reached and there is no plan, it does not say infeasible but returns search_limit, and a plan left after the limit was reached is marked not_proven (when search_limit_reached is true, nodes_explored = max_nodes).
- Fuzzing that compares the results against an independent oracle written from the rules without using the helper functions in src (including brute force over small problems and a recheck of the assignment) confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only the facts of the assignment. The fields inference and advice are always null, and no string in the output contains words of correction or priority. The result is not an approval (result_is_approval: false) and creates no authority (derives_authority: false). It does not turn AI-derived tasks (ai_candidate) into facts and does not judge whether they are correct.
- The result presents the facts of an assignment plan, the reasons it cannot be done and the search limit; it is not a scheduling decision and does not carry anything out. Whether to adopt a plan, re-split tasks, or whether durations are correct is decided by the user outside this part.
- That the search is complete (that a plan placing every task is found if one exists) was checked by a left-packing argument and by 1,000,000 comparisons against brute force over small problems. It has not been formally proven.
- The brute-force comparison covers only small problems, up to 4 tasks, 3 slots and times around 20. infeasible and proven for larger problems have not been checked by brute force.
- nodes_explored is this part's own fixed way of counting and cannot be compared with the step counts of other solvers. Which of several plans with the same value is returned is decided by the search order.
- The input and output forms and the way steps are counted were defined in v1; there is no existing implementation (new).
- It has no external boundary, no state, no clock and no random numbers. Side effect: NONE, and it uses no credentials.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call solveSchedule({ tasks, slots, objective, search }) in src/constraint-schedule.js (it does not throw exceptions and does not modify the input). Returns: { ok: true, errors: [], facts } (state, status, optimality, assignment, unassigned, reasons, need_input, search_limit_reached and so on, with inference: null and advice: null) or { ok: false, errors: [{ code, path, limit? }] }. Side effect: NONE. There is no state, no external connection, no credentials and no dependency package, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-06PKernel
Model and bounded rule evaluation — Model and bounded rule evaluation part (Kernel)
The Model and bounded rule evaluation part (Kernel) is a standalone part that evaluates, within fixed limits, a model (an expression written as a JSON tree) and bounded rules (a small DSL of conditions and effects only) passed by the caller. evaluateModel produces a value and a unit for each output by decimal BigInt arithmetic (34-digit precision, half_even), and evaluateRules and runRules produce the legal moves for each role, the next state (a candidate), and the end and its result. It does not accept loops, recursion or code execution. UNKNOWN is not guessed but made need_input, and malformed input is stopped with a fixed code and location (fail-closed). The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When expressions for estimates or determinations are evaluated, it prevents floating-point errors entering the values, division by zero or out-of-range results slipping in as 0 or NaN, missing input being filled with guesses and a partial calculation being output, and rule evaluation turning into unending loops or code execution. Random numbers are also made only from an explicit seed, so the same input and seed produce the same result.
When to use
- You want to produce, as decimal strings, the value and the canonical unit of each output from a model (params declaring the parameters and their units, and outputs holding an expression for each output) and inputs (value, unit and origin). The expression operations are + - * / min max neg, round (scale 0 to 34; half_even, half_up, floor or ceil) and rand.
- You want to vary one parameter over 1 to 1000 points with sweep and list the values at each point (the number of points × the number of nodes is at most 1,000,000).
- You want expressions containing random numbers evaluated reproducibly from an explicit seed (an integer from 0 to 2^53−1). The generator is xorshift128, whose 4 words are made from the seed by SplitMix64, and it restarts for each output and for each sweep point.
- You want to calculate with consistent unit dimensions. Units are compared as sets of exponents per symbol; * and / add and subtract exponents, and + - min max require the dimensions to match.
- From bounded rules written as roles, moves (when and effects of each move) and end, and a state, you want evaluateRules to produce the legal moves for each role; when a move is given, the next state (a candidate), or ILLEGAL_MOVE with the first condition that did not hold; and the end and its result.
- You want runRules to apply a sequence of moves in order up to max_steps (1 to 1000), stop at the first move that could not be applied, and receive the state before that move as final_state.
- You want to receive, separately, whether an AI-derived model, input, sweep or rules (origin ai_candidate) were used, through basis (registered_only or includes_ai_candidate) and the locations in candidate_sources.
When not to use
- You want to use the results as a forecast of actual demand. The results are the calculation of the expressions passed. It does not check whether the model's expressions, the rules or the input values are correct.
- You want the part to convert units (for example, m and km). Different units of the same dimension are treated as different symbols. To multiply by a factor, write it in the expression.
- You need cryptographic random numbers or random numbers that have passed statistical tests. xorshift128 is not for cryptographic use, and no statistical tests have been run.
- You handle rules with moves that take arguments, simultaneous turns, probabilities over hidden information, or time-outs. Bounded rules do not have these.
- You want to write loops, recursion, expression evaluation or code execution into the rules. Anything outside the vocabulary is stopped with UNSUPPORTED_CONSTRUCT.
- You want to put booleans, null or fractional numbers in the state. State values are only integers, strings, arrays, objects and the UNKNOWN marker { unknown: true }.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the argument is not a plain object holding only the fixed keys, it stops with INVALID_ARGUMENT, and when inspecting the values itself throws an exception (a Proxy trap and so on), with INSPECTION_FAILED. It does not accept getters, class instances, sparse arrays, null or a __proto__ key, and checks the values after copying them into plain data.
- When the input is malformed (an unknown key, a missing key, an origin outside the vocabulary, the form of a name, a duplicate, the form of a JSON Pointer and so on), it stops with INVALID_INPUT; when a decimal string is not in the fixed form, has more than 34 significant digits, is outside the range (0 or 10^-100 ≤ |value| < 10^100) or is "-0", with INVALID_DECIMAL; and when a unit string is malformed or the combined exponents exceed 64, with INVALID_UNIT.
- When a name refers to an undeclared parameter or role, it stops with UNKNOWN_NAME; when the unit dimensions of an addition, subtraction, min or max differ, or the unit of an input or sweep has a different dimension from the declaration, with UNIT_MISMATCH; when an expression contains rand but there is no seed, with SEED_REQUIRED; and when an expression operator, a rounding mode, or the op of a condition or effect is outside the vocabulary, with UNSUPPORTED_CONSTRUCT.
- When a limit is exceeded, it stops with RESOURCE_LIMIT and puts the name of the limit in limit. The limits include expression depth 64, 10,000 nodes, 1000 sweep points, a state of 256 KiB and depth 64, strings of 4096 characters, effect values of 4 KiB, and max_steps 1000. The size of the state after a move is applied is also checked here.
- When even one parameter in use is missing (MISSING) or UNKNOWN, it does not guess: it sets state to need_input and leaves points empty (it does not output a partial calculation). Division by zero and out-of-range results are shown for that output only, as DIVISION_BY_ZERO or OUT_OF_RANGE with the location of the node where they occurred; the value is not made 0 or NaN.
- A move is checked in the order definition → role → end → conditions → effects; if it is not legal, it returns ILLEGAL_MOVE with the first condition that did not hold (or MOVE_NOT_DEFINED, ROLE_MISMATCH or ENDED). If an effect cannot be applied, it returns EFFECT_FAILED and discards the whole move (next_state is null). A condition or effect that reads beneath an UNKNOWN marker in the state is called neither legal nor a violation: it is made undetermined, and the locations of the markers are listed in need_input.
- Fuzzing that compares the results against an independent oracle written without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only calculation facts. The fields inference and advice are always null, and no string in the output contains wording of judgement. The result is not an approval (result_is_approval: false), and it creates no authority (derives_authority: false).
- The results are facts about evaluating the expressions and rules passed: they do not show that a model or rules are correct, they are not a forecast of actual demand, and they are not a judgement of how good a game is. It does not act on anyone's behalf in demand forecasting, game design or rule judgement. Whether the expressions, rules and input values are appropriate is checked by the user.
- It holds no specific expressions or game rules in code. Expressions, units, rules and state are all taken from the input.
- It does not evaluate the size of the error when 34-digit rounding accumulates node by node. The only signs of numerical instability it outputs are the precision_rounded and OUT_OF_RANGE markers.
- The state limits (256 KiB and depth 64) are checked after a move has been fully applied; the size partway through a move is not examined.
- It has no clock, no environment locale, no external boundary and no state. Side effect: NONE, and it uses no credentials.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call evaluateModel({ model, inputs, sweep?, seed? }), evaluateRules({ rules, state, role, move?, max_steps? }) and runRules({ rules, state, moves, max_steps }) in src/model-rule.js (they do not throw exceptions). Returns: { ok: true, errors: [], facts } (state is done or need_input, with inference: null and advice: null) or { ok: false, errors: [{ code, path, limit? }] } (path is a JSON Pointer). Side effect: NONE. There is no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-07PKernel
Evidence, claims and applicability conditions — Evidence, claims and applicability conditions part (Kernel)
The Evidence, claims and applicability conditions part (Kernel) is a standalone part that returns, for each claim (with a type of fact, testimony, inference or hypothesis, and a source with ref, version and locator), whether its conditions for holding apply to the current conditions, as one of three values: match, mismatch or unknown. A partial match is not made match. When the source's version differs from the current version, the claim is marked stale and its evaluation becomes unknown (SOURCE_CHANGED). Malformed input is stopped with a fixed code and location (fail-closed). The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When checking "can this evidence still be used?", it prevents treating a claim that matches only some of its conditions as fully applicable, continuing to use an earlier evaluation after the source material has been updated, mixing up "entered as unknown" with "not entered yet", and promoting an inference or a hypothesis to a fact.
When to use
- You want to compare the conditions (0 to 50, with op eq, ne, in, lt, le, gt or ge) of each claim (0 to 10,000 claims) against the current values in current.conditions and produce applies as match, mismatch or unknown. If any condition is false, the result is mismatch; if none is false but some are unknown, not entered or not comparable, unknown; if all are true, match.
- You want to compare the source's version (source.version) against current.source_versions and produce source_status (current, changed, not_entered or unknown) and stale for each claim. When the source is not current, applies is unknown.
- You want to distinguish a value entered as { "unknown": true } from a value whose key is absent (not entered). Only values not entered are listed in need_input (CONDITION_NOT_ENTERED, SOURCE_VERSION_NOT_ENTERED, SUPPORT_NOT_ENTERED), and state becomes need_input. 0, false and the empty string are treated as values.
- You want to keep each claim's type and origin as they are, and receive AI-derived claims (ai_candidate) separately in candidate_claims.
- You want the support relations between claims, written with supports, listed for each claim as the shortest typed chain (id, type, origin, depth, chain), and cycles reported through support_cycles and in_support_cycle. Cycles are output, not stopped.
- You want to receive, for each claim, why applies became unknown or mismatch, through reasons (in a fixed order) and failed, unknown_keys, missing_keys and not_comparable_keys.
When not to use
- You want it to determine whether a claim is true or whether to adopt it. match is the result of comparing the conditions passed against the current values and does not show that a claim is true. It also does not check whether the content of a claim or the source location (locator) is correct; locator is only returned as it is.
- You want to compare conditions using fractional numbers, dates, ranges, string ordering or regular expressions. It compares only equality of strings, integers and booleans, and the ordering of integers (dates are converted to integers by the caller before being passed).
- You want to see the ordering of source versions (which one is newer). Old and new are judged only by whether the version strings are equal. The caller checks the current version of a source and passes it; this part does not read the source material.
- You want the result of a supporting claim to change the applies of the claim it supports. applies is decided only by the claim's own conditions.
- You want every support chain enumerated. It outputs only one shortest chain.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the input is malformed (not plain data, an unknown key, a missing key, a value outside the vocabulary, a wrong type, null, an id that is not 1 to 128 characters, a lone surrogate, the same value twice in a set), it stops with INVALID_INPUT, and when inspecting the values itself throws an exception (a Proxy trap, a revoked Proxy and so on), with INSPECTION_FAILED.
- It does not accept getters, class instances, sparse arrays, extra properties on arrays, symbol keys, non-enumerable properties or null. It calculates only on a copy of the values it has read and does not modify the input.
- When ids are duplicated, it stops with DUPLICATE_ID, and when a condition operator is none of eq, ne, in, lt, le, gt or ge, with UNSUPPORTED_OPERATOR. It also stops when in has 0 values or 65 or more values, and when the value for lt, le, gt or ge is not a safe integer.
- When a limit is exceeded (10,000 claims, 50 conditions in one claim, 64 values for in, 64 supports, 10,000 entries each in current.conditions and source_versions, a total of 2,000,000 ids in the output support chains), it stops with RESOURCE_LIMIT and puts the name of the limit in limit.
- Equality is strict, including type ("5" and 5, and true and 1, are different). For lt, le, gt and ge, when the current value is not an integer, the condition is not made false but not_comparable, and applies becomes unknown.
- When it stops, it returns only the first problem found, as { code, path, limit? } (path is a JSON Pointer).
- Fuzzing that compares the results against an independent oracle written without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null. The result is not an approval (approval: false), and it creates no authority (derives_authority: false).
- The results are a presentation of facts about whether conditions apply and about source versions: they are not a judgement of whether a claim is correct, and they do not act on anyone's behalf in deciding whether to adopt it. The truth of a claim is not promised, and it does not recognize any rights. Final authority, blocking and application are the roles of the Host and the Runtime.
- Even when a support chain contains a fact, it does not promote an inference or a hypothesis. It does not merge claims either.
- It does not decide the meaning of condition keys and values (what counts as holding). The caller decides these and passes them.
- The input and output forms are defined by v1 of this part and have not yet been accepted by the user side. Inputs at the full limits were checked only once each in unit tests, and time and memory have not been measured.
- It has no clock, no random numbers, no environment locale, no external boundary, no state and no credentials, and it does not use files or network communication. Side effect: NONE.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call evaluateClaims({ claims, current }) in src/evidence-claim.js (it does not throw exceptions and does not modify the input). Input: the list of claims, claims, and the current conditions and source versions, current. Returns: { ok: true, errors: [], facts } (claims, candidate_claims, support_cycles and need_input, with inference: null and advice: null) or { ok: false, errors: [{ code, path, limit? }] } (path is a JSON Pointer). Side effect: NONE. There is no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-08PKernel
View model and operation conversion — View model and operation conversion part (Kernel)
The View model and operation conversion part (Kernel) is a standalone part that, following a registered view contract (view_id, version, entity_kind, fields and commands; declarations only), builds a per-field view model from a common entity (id, version, kind, data) (project), and converts a typed operation event into a typed command candidate (command). Fields absent from the data, unknown values and values of the wrong type are not made empty but output as markers, and violations and missing input are returned as facts. Malformed input is stopped with a fixed code and location (fail-closed). The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
In the conversion between screens and processing, it prevents mistaking a field absent from the data for an empty value, making a field editable through view settings when it is not editable, mixing scripts or functions into view definitions, and calling AI on every operation.
When to use
- You want to build a per-field view (value, value_state, editable) from entity.data, following the view contract's fields (path, label_key, kind (text, number, enum, list or bool), editable, range, enum, max_length).
- You want field values classified by value_state (present, missing, unknown, kind_mismatch or out_of_contract). A field absent from the data is output as { missing: true }, { unknown: true } stays unknown, and a value of the wrong type is output as { kind_mismatch: true }, so they can be told apart from the empty string, 0, false and the empty list.
- You want to hide fields, reorder them or make them non-editable with settings (hidden, order, editable). Hidden fields are removed from view and listed in hidden.
- You want to convert an operation event (event_type, event_id, path, value) into a typed command candidate (op, path, value, base_version, event_id, event_type), checked against the command definition (op set, increment, select or toggle) selected by the pair (event_type, path). The entity_version passed is copied into base_version.
- You want to receive operation violations (UNKNOWN_EVENT, NOT_EDITABLE, VALUE_KIND_MISMATCH, RANGE_VIOLATION) and the need_input of an event whose value is absent or unknown (VALUE_ABSENT, VALUE_UNKNOWN) as facts rather than as ok: false.
When not to use
- You want to render a screen, decide permissions, or apply an operation. This part only converts views and operations. Rendering is the role of the App; permissions, checking against the base version, and applying are the roles of the Host (Runtime).
- You want to check whether a view contract is registered, whether its version is correct, or whether the entity's version is the current one. It only copies the contract and versions passed and returns them.
- You handle fractional numbers or numbers as decimal strings, or lists of elements other than strings. number is safe integers only, and list is arrays of strings only.
- You need a dictionary of display wording or translation. label_key is checked only for its id form, and the part holds no wording (that is the role of the App).
- You want to recognize resends of the same event_id. It does not detect duplicates.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the argument is not a plain object holding only the fixed keys, it stops with INVALID_ARGUMENT, and when inspecting the values itself throws an exception (a Proxy trap and so on), with INSPECTION_FAILED. It does not accept getters, class instances, sparse arrays, a __proto__ key, symbol keys or null, and checks the values after copying them into plain data.
- When there is a function anywhere, or the view contract or settings contain a key that is not declared, it stops with SCRIPT_REFUSED. There is no path that evaluates expressions or scripts.
- When the view contract is malformed (a kind or op outside the vocabulary, a mismatched pair of op and kind, min > max, an empty or duplicated enum, max_length out of range, duplicated path or (event_type, path) pairs, a command path that is not a field and so on), it stops with INVALID_CONTRACT. A command constraint allowed that widens the field's enum is also stopped here.
- When the entity is malformed, it stops with INVALID_ENTITY; when entity.kind differs from the contract's entity_kind, with ENTITY_KIND_MISMATCH; when the settings are malformed, with INVALID_SETTINGS; when the settings name a path that is not in the contract, with SETTINGS_PATH_UNKNOWN; and when the event is malformed, with INVALID_EVENT.
- When the settings try to make editable a field that the contract makes non-editable, it stops with EDITABLE_WIDEN_REFUSED. Differences in view do not increase permissions. command looks only at the contract's editable.
- In command, it returns UNKNOWN_EVENT when there is no matching pair, NOT_EDITABLE when the field is non-editable, VALUE_KIND_MISMATCH when the value's type does not match or a toggle has a value, and RANGE_VIOLATION when the value is out of range. A value passes only when it satisfies both the field's constraints and the command's constraints.
- When a limit is exceeded (nesting depth 32, 200,000 values, strings of 65,536, arrays of 10,000 elements, 256 fields, 256 commands, 256 each for enum and allowed), it stops with RESOURCE_LIMIT and puts the name of the limit in limit.
- Fuzzing that compares the results against an independent oracle written without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only conversion facts. The fields inference and advice are always null. The result is not an approval (approval: false), and it creates no authority (derives_authority: false). It does not call AI, and it does not use the clock, random numbers, files or network communication.
- A command is a candidate, not an application. Applying, checking against the base version, the authenticity of permissions, and conflicts between concurrent updates are the roles of the Host (Runtime).
- It does not check whether the value after an increment is applied falls within the field's range. Because it does not hold the base version's value, it limits only the size of the step with the command's range.
- out_of_contract values are not hidden but copied and output as they are. kind_mismatch values are not output.
- It is not a screen, a rendering engine or a permission mechanism.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call project({ contract, entity, settings? }) and command({ contract, entity_version, event }) in src/view-model.js (neither throws exceptions or modifies the input). Returns: { ok: true, errors: [], facts } (view and hidden for project; command, violation and need_input for command; with inference: null and advice: null) or { ok: false, errors: [{ code, path, limit? }] } (path is a JSON Pointer). Side effect: NONE. There is no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-09PKernel
Document anchors and fragment editing — Document anchor and fragment editing part (Kernel)
The Document anchor and fragment editing part (Kernel) is a standalone part that relocates a position (an anchor) in a document divided into blocks within the revised document (exact, relocated, ambiguous or lost), and outputs the diff of a fragment edit and the new block text as candidates. Positions are counted in UTF-16 code units, and the point between the two halves of a surrogate pair is never used as a position. When there are 2 or more candidates, it lists them all and does not choose. An edit that overlaps a protected range is marked PROTECTED_RANGE and no diff is output. The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents the positions of comments and references from drifting when a document is revised. By fixed rules, it does not settle on one position on its own when a position has become ambiguous after a revision, does not create positions that split a surrogate pair such as an emoji, does not make a diff from an edit that touches protected characters, and does not lose the characters that were removed.
When to use
- You want to relocate an anchor made in one version (doc_version, block_id, start, end, quote, prefix, suffix) within the current document (version, blocks) and receive status (exact, relocated, ambiguous or lost) and location (operation: resolve).
- You want relocation done in a fixed order of stages: exact (same version, and the characters at the position equal quote) → with context (prefix+quote+suffix) in the same block → with context in all blocks → quote only in the same block → quote only in all blocks. It decides at the first stage that has at least one match.
- You do not want an ambiguous position settled. When there are 2 or more matches, it returns ambiguous, sets location to null and lists all candidates (the list holds up to 1,000; candidate_count is the total number, and candidates_truncated shows that the list was cut).
- From a fragment edit that replaces [start, end) of a block with replacement, you want to receive the diff (including the removed characters removed and the inserted characters inserted) and new_block_text as candidates (operation: edit). The origin of replacement (registered, human or ai_candidate) is returned as replacement_origin.
- You want to stop edits that overlap protected ranges (protected) decided by the caller. On overlap, status is refused, PROTECTED_RANGE is listed in violations, and diff and new_block_text are null.
When not to use
- You want it to determine whether the meaning of the surrounding text is kept after a replacement. It looks only at the protection of characters, and meaning_checked is always false.
- You want Unicode normalization (NFC, NFKC), or differences in letter case or whitespace, to be treated as the same, or you want fuzzy relocation of similar text. It compares only code units.
- You want grapheme boundaries (combining characters, emoji ZWJ sequences, variation selectors) kept intact. Only surrogate pairs are never split.
- You handle anchors or edits that span blocks, or want to follow blocks being split or merged.
- You need document saving, version numbering, handling of conflicts in collaborative editing, or keeping the source (which version of which material). This part only returns anchors and versions.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the argument is not a plain object holding only the fixed keys (an unknown key, a missing key, a getter, a non-enumerable property, a symbol key), it stops with INVALID_ARGUMENT; when inspecting the values itself throws an exception (a Proxy trap and so on), with INSPECTION_FAILED; and when operation is neither resolve nor edit, with UNSUPPORTED_OPERATION.
- When the document is malformed (including text containing a lone surrogate), it stops with INVALID_DOCUMENT; when two blocks have the same id, with DUPLICATE_BLOCK_ID; and when the anchor is malformed (start > end, the length of quote is not end − start, prefix or suffix is longer than 32 code units and so on), with INVALID_ANCHOR.
- When the edit is malformed, it stops with INVALID_EDIT; when origin is outside the vocabulary, with INVALID_ORIGIN; when a protected range is malformed, empty, in a block not in the document, longer than the block, or between the halves of a surrogate pair, with INVALID_PROTECTED; when the block_id to edit does not exist, with BLOCK_NOT_FOUND; when end exceeds the length of the text, with INVALID_RANGE; and when start or end is between the halves of a surrogate pair, with SURROGATE_SPLIT.
- When a limit is exceeded, it stops with RESOURCE_LIMIT and puts one of blocks (10,000), document_bytes (a total of 1,048,576 bytes of text in UTF-8, including the document after an edit) or protected (1,000) in limit.
- When the range to remove [start, end) overlaps a protected range by even one character, or an insertion (start = end) is inside a protected range (excluding both ends), it lists PROTECTED_RANGE in violations and outputs no diff (ok is true).
- Matches are counted including overlaps ("aa" occurs at 2 places in "aaa"), and there are no matches that span blocks. The point between the halves of a surrogate pair is used neither in output positions nor in edit or protected positions.
- Fuzzing that compares the results against an independent oracle written without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null, and resolve does not output the document's text. The result is not an approval (approval: false), and it creates no authority (derives_authority: false).
- edit only outputs candidates and does not commit or save the document (committed is always false). Which ambiguous candidate to choose and whether to adopt an edit are decided outside this part (by the user of the part or the Runtime).
- The results are a presentation of facts about position candidates and edit candidates: they do not settle a position, and keeping the meaning is not promised. It does not act on anyone's behalf in settling positions or editing documents.
- It does not create protected ranges. The caller decides what to protect and passes it.
- Its form is close to the W3C Web Annotation TextQuoteSelector and TextPositionSelector, but it does not claim to conform to them (positions are in UTF-16 code units, and it has block ids).
- It has no clock, no random numbers, no external boundary and no state. Side effect: NONE, and it uses no credentials.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call documentAnchor({ operation: "resolve" | "edit", ... }) in src/document-anchor.js (resolve and edit can also be called directly; none throws exceptions). resolve takes { document, anchor }, and edit takes { document, block_id, start, end, replacement, origin, protected }. Returns: { ok: true, errors: [], facts } (state is done; inference and advice are null) or { ok: false, errors: [{ code, path }] }. Side effect: NONE. There is no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-10PKernel
Event aggregation and state reduction — Event aggregation and state reduction part (Kernel)
The Event aggregation and state reduction part (Kernel) is a standalone part that, with a declaration-only reducer (rules of set, add, append and remove for each event type), applies events in array order to a versioned state, and outputs as facts a candidate new state (new_state) together with the events applied, duplicates, order violations, events outside the window, events that could not be applied, and the count per type. Resends with the same event_id are not applied. Malformed input is stopped with a fixed code and location (fail-closed). The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When the current state is built from history, it prevents counts from increasing twice because of resent events, building a state while missing gaps or reversals in seq, rules switching in the middle of a window, a half-finished state remaining where only some of one event's rules were applied, and adding a missing value as 0.
When to use
- You want to apply events (0 to 10,000 of { event_id, seq, at, type, payload }) in order to state with a reducer (version, and for each type 0 to 64 rules { op, path, from ("payload.<key>") or value }), and receive the new_state candidate and aggregates.by_type (the count of applied events per type).
- You do not want resends with the same event_id applied. If the id is in seen_event_ids, it is listed in duplicates (source: seen); if it appears a second time in the same batch, in duplicates (source: batch); the increase is 0.
- You want to check that seq is continuous. Only the previous seq + 1 is accepted; a gap is listed in need_input (SEQ_GAP) and a reversal in order_violations, and in both cases the events after it are not_applied. last_seq can set the first expected seq.
- You want to define a window with window (start, end, reducer_version) and mark events outside start ≤ at < end as outside_window (they are not applied, but seq advances).
- You want the rules of one event applied all or nothing. If they cannot be applied, everything that event changed is rolled back, event_id, rule_index and code are listed in apply_failures, and the events after it are not_applied.
When not to use
- You want to use it as an event ledger, a delivery platform or a database. Saving, committing and delivery, as well as the duplicate ledger, time, leases and persistence, are the roles of the Host (Runtime).
- You want to tell apart events with the same event_id but different content. Duplicates are determined only by event_id, and the content is not compared.
- You want events sorted by seq before they are applied. They are applied in array order. When sorting is needed, the caller does it before passing them.
- You want to add decimal fractions. add works only on safe integers.
- You need set on an array index or replacement within a list. set works only when the parent is an object.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the argument is not a plain object holding only the fixed keys, it stops with INVALID_ARGUMENT, and when inspecting the values itself throws an exception (a Proxy trap and so on), with INSPECTION_FAILED. It does not accept getters, class instances, sparse arrays, a __proto__ key, symbol keys, null or functions, and applies events after copying the values into plain data.
- When the reducer is malformed (an unknown key, an op outside the vocabulary, the form of a path, a __proto__ token, the number of from and value, the form of from, a value for add that is not an integer and so on), it stops with INVALID_REDUCER. The reducer is declarations only, and there is no path that evaluates expressions, scripts or functions.
- When state is malformed, it stops with INVALID_STATE; when events are malformed (seq or at is not an integer of 0 or more and so on), with INVALID_EVENTS; when seen_event_ids is not a list of ids, with INVALID_SEEN; and when window is malformed (start > end and so on), with INVALID_WINDOW.
- When the window's reducer_version differs from reducer.version, it stops with RULE_SWITCH_IN_WINDOW. Rules are not switched in the middle of a window.
- It returns, as a code, why a rule could not be applied: NO_RULE_FOR_TYPE when there is no rule for the type, PAYLOAD_KEY_MISSING when the payload has no such key, PARENT_NOT_OBJECT when the parent is not an object, TARGET_MISSING, TARGET_NOT_INTEGER or TARGET_NOT_LIST when the target is absent, not an integer or not a list, VALUE_NOT_INTEGER when the value is not an integer, and INTEGER_OVERFLOW when the sum overflows. A missing value, an unknown value or a string is not treated as 0.
- When a limit is exceeded (nesting depth 32, 400,000 values, strings of 65,536, arrays of 100,000 elements, 10,000 events, 256 types, 64 rules per type, a list of 10,000 after append), it stops with RESOURCE_LIMIT and puts the name of the limit in limit.
- Fuzzing that compares the results against an independent oracle written without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null. The result is not an approval (approval: false), and it creates no authority (derives_authority: false).
- new_state is a candidate, not a commitment. Saving, delivery and committing the application are the roles of the Host.
- It does not check whether seen_event_ids are events that were actually applied, whether the state's version is the current one, or whether at is a correct time. The Host passes all of these. It does not read the clock and uses only the input values of at and window.
- A different event with the same seq (a different event_id) is stopped as a reversal. It does not decide which one is correct.
- Equality for remove with a value is judged by the set of keys and matching values (the numbers -0 and 0 are equal).
- It has no clock, no random numbers, no files, no network communication, no external boundary and no state. Side effect: NONE, and it uses no credentials.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call reduceEvents({ state, state_version, reducer, events, seen_event_ids, window?, last_seq? }) in src/event-reduce.js (it does not throw exceptions). Returns: { ok: true, errors: [], facts } (new_state, applied, duplicates, order_violations, apply_failures, not_applied, aggregates, need_input and so on; inference and advice are null) or { ok: false, errors: [{ code, path, limit? }] }. Side effect: NONE. There is no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-11PKernel
Assembly, compatibility and test plan check — Assembly DAG, compatibility and test plan check part (Kernel)
The Assembly DAG, compatibility and test plan check part (Kernel) is a standalone part that, from a list of parts' ToolLocks (tool_id, contract_version, artifact_digest, ports, capabilities), the connecting edges, the allowed capabilities and draft fixtures, outputs as facts whether the assembly can be connected (connectable), the problems (duplicates, capabilities not allowed, absent ports, mismatched versions or directions, schema or unit mismatches, unconnected required ports, cycles), the edges that need an adapter, and a test plan. Expected values made by AI are not counted as confirmed. The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Inconsistencies in an assembly of parts are not visible until it runs. This part lists, before anything runs, mismatches in the direction, version, schema or unit of connecting edges, required ports that are not connected, cycles, and capabilities that are not allowed. It also prevents counting expected values made by AI as confirmed tests, and answering "connectable" when there are no locks.
When to use
- You want to receive connectable and problems from locks (0 to 1,000) and edges (0 to 10,000 of { from: { tool_id, port, contract_version? }, to: the same }). connectable is true only when problems has 0 entries.
- You want to check each lock's capabilities against allowed_capabilities and list capabilities that are not allowed as CAPABILITY_NOT_ALLOWED.
- You want to know where the schema_digest differs between the two ends of an edge, as the edges that need an adapter (adapters_needed). It does not create adapters.
- You want to receive the test plan in the topological order of tools (starting from the smallest tool_id among tools with in-degree 0). Tools in a cycle and beyond it, which cannot be ordered, are listed in unordered.
- You want fixtures (id, tool_id, input_digest, expected_digest, origin) divided per tool into confirmed (human, known_source), unconfirmed (ai_candidate) and conflicting. It also lists tools with no confirmed fixture and fixtures for tools that are not in the locks (orphan_fixtures).
When not to use
- You want to check whether a lock's digest matches the real artifact, signatures, the supplier's authority, or acceptance by the user side. It checks only the form and compares values. These are checked separately, by the Host, the Runtime and so on.
- You want to compare the content of schemas or the range of compatibility. If schema_digest is the same, it treats the types as the same; if it differs, it only says that an adapter is needed.
- You want it to determine whether units can be converted, or whether different notations mean the same thing. Units are compared only by exact string match.
- You want it to determine whether a fixture's expected value is correct, or whether the basis for known_source itself is sound. It only copies origin and sorts by it.
- You want 2 or more edges connected to one in port (fan-in), or duplicated identical edges, reported as problems. These are not reported as problems. It also does not determine the old-to-new order of contract_version.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the argument is not a plain object holding exactly the four keys locks, edges, allowed_capabilities and fixtures, it stops with INVALID_ARGUMENT, and when inspecting the values itself throws an exception (a Proxy trap and so on), with INSPECTION_FAILED. It does not accept getters, class instances, sparse arrays, a __proto__ key, symbol keys, null or functions, and checks the values after copying them into plain data.
- When a lock is malformed (an unknown key, a digest that is not sha256:<64 lowercase hexadecimal digits>, a direction outside the vocabulary, the form of unit, duplicated port names or capabilities and so on), it stops with INVALID_LOCKS. It does not accept keys such as authorized, and authorized=true does not substitute for a basis.
- When an edge is malformed, it stops with INVALID_EDGES; when allowed_capabilities is not a list of ids, with INVALID_CAPABILITIES; and when a fixture is malformed (an origin outside the vocabulary, duplicated ids, an extra key and so on), with INVALID_FIXTURES.
- When a limit is exceeded (nesting depth 16, 400,000 values, strings of 4,096, arrays of 100,000 elements, 1,000 locks, 64 ports and 64 capabilities per lock, 1,000 allowed_capabilities, 10,000 edges, 10,000 fixtures), it stops with RESOURCE_LIMIT and puts the name of the limit in limit.
- For each end of an edge, it reports PORT_UNKNOWN when the tool or port does not exist, VERSION_MISMATCH when the end's contract_version differs from the lock, and DIRECTION_MISMATCH when the direction is wrong (from is not out, or to is not in). When both ends connect, it reports a difference in schema_digest as SCHEMA_MISMATCH and a difference in units (including unknown units and a unit on only one side) as UNIT_MISMATCH.
- It reports locks with the same tool_id as DUPLICATE_TOOL, required ports with no connected edge as REQUIRED_PORT_UNCONNECTED, and strongly connected components of the arrows between tools (2 or more tools, or a self-loop) as CYCLE. When there are 0 locks, connectable is null and need_input (NO_LOCKS) is returned.
- Fuzzing that compares the results against an independent oracle written without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null. The result is not an approval (approval: false), and it creates no authority (derives_authority: false).
- The results are a check of facts about an assembly, not permission to adopt, sign, issue or run it. It is not a pack issuer, a signer or a runtime environment.
- It does not sign, issue or generate adapters. It does not use the clock, random numbers, files or network communication either.
- Expected values from AI (ai_candidate) are listed in unconfirmed and not counted as confirmed. It does not accept majority-vote or confidence fields. When confirmed fixtures have different expected values for the same input, all of those fixtures are placed in conflicting and not counted as confirmed.
- The ToolLock fields repository, source_revision, source_path, license, publisher_evidence_ref, consumer_acceptance_ref, input_schema_digest and output_schema_digest are only checked for form and not used.
- It has no external boundary and no state. Side effect: NONE, and it uses no credentials.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call checkAssembly({ locks, edges, allowed_capabilities, fixtures }) in src/assembly-check.js (it does not throw exceptions). Returns: { ok: true, errors: [], facts } (connectable, problems, adapters_needed, test_plan, expectation_conflicts, tools_without_confirmed_fixture, orphan_fixtures and need_input; inference and advice are null) or { ok: false, errors: [{ code, path, limit? }] }. Side effect: NONE. There is no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-12PKernel
Per-role context and items not to use — Per-role context and non-use set part (Kernel)
The Per-role context and non-use set part (Kernel) is a standalone part that outputs, for each role (with allowed_scopes), the set of items that may be passed to it (projection), the items excluded and the reasons, and the items that are required but not included (missing_required). Exclusion follows derived_from transitively. The transitive non-use set (non_use_closure) is output as candidates for stopping references, and it does not claim that anything was deleted (deleted: false). Malformed input is stopped with a fixed code and location (fail-closed). The fields inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When information that must not be passed to a role is removed, it prevents overlooking items made from items that were set not to be used (derived items), passing items whose origin cannot be checked, and mistaking candidates for stopping references for "deleted".
When to use
- You want to receive projection and excluded for each role from roles (0 to 50 of { role, allowed_scopes }), items (0 to 100,000 of { id, scope, derived_from, kind }) and non_use. For each role, projection and excluded divide the items with nothing left out and no overlap.
- You want to receive every reason for exclusion in a fixed order. The applicable reasons are listed in reasons in the order NON_USE → SCOPE → DERIVED_FROM_NON_USE → DERIVED_FROM_OUT_OF_SCOPE → DERIVED_FROM_NOT_ENTERED, and the first one is reason.
- You want to receive the transitive non-use set as candidates for stopping references. non_use_closure contains the ids in non_use and the items that have an id from non_use among their ancestors; meaning is reference_stop_candidates, and deleted is always false. Ids that are in non_use but not among the items are output in non_use_not_in_items.
- You want to pass required (the ids of the items each role needs) and learn, as missing_required, which items are not in projection and why. This is a fact about projection and is not made need_input.
- When derived_from names an id that is not in the input, you want that id listed in items_not_entered and need_input (ITEM_NOT_ENTERED). state becomes need_input, and the item stays excluded.
When not to use
- You want it to decide permissions, block information before input, or actually stop references or delete. Final authority is the role of the Runtime; blocking before input and actually stopping references or deleting are the roles of the Host. This part only calculates sets from the lists passed.
- You want to check whether allowed_scopes or scope are correct (the authenticity of permissions). The caller decides the vocabulary of scopes and passes it; this part does not check it.
- You want to handle the content of a policy or differences from an earlier policy. policy_version is only checked for form and copied.
- You want different handling for each item type (kind), or want to know which ancestor caused an exclusion. kind is not used in the calculation, and only the type of reason is output.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- When the input is malformed (not plain data, an unknown key, a missing key, a value outside the vocabulary, a wrong type, null, an id that is not 1 to 128 characters, a lone surrogate, the same value twice in a set), it stops with INVALID_INPUT, and when inspecting the values itself throws an exception (a Proxy trap, a revoked Proxy and so on), with INSPECTION_FAILED.
- It does not accept getters, class instances, sparse arrays, extra properties on arrays, symbol keys, non-enumerable properties or null. It calculates only on a copy of the values it has read and does not modify the input.
- When role names or item ids are duplicated, it stops with DUPLICATE_ID; when required names a role that is not in roles, with UNKNOWN_ROLE; and when derived_from forms a cycle (including an item that names itself), with DERIVATION_CYCLE. The location of a cycle is the item on the cycle with the smallest index (/items/<index>).
- When a limit is exceeded (50 roles, 1,000 allowed_scopes, 100,000 items, 1,000 derived_from entries in one item, a total of 1,000,000 derived_from entries, 100,000 non_use entries, 50 roles in required and 100,000 ids for one role), it stops with RESOURCE_LIMIT and puts the name of the limit in limit.
- Exclusion follows ancestors of ancestors. Items with an id among their ancestors that is not in the input (items that cannot be checked) are not put in projection.
- When it stops, it returns only the first problem found, as { code, path, limit? } (path is a JSON Pointer).
- Fuzzing that compares the results against an independent oracle written without using the helper functions in src confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only facts. The fields inference and advice are always null. The result is not an approval (approval: false), and it creates no authority (derives_authority: false).
- It deletes nothing. non_use_closure is a set of candidates for stopping references, not a record of deletion. Actually stopping references, deletion and deletion failures happen outside this part.
- It does not grant permissions, block, or delete on anyone's behalf. Final authority, blocking and application are the roles of the Host and the Runtime.
- It does not save non-use records or reference stops (it has no state).
- The input and output forms are defined by v1 of this part and have not yet been accepted by the user side. Inputs at the full limits were checked only once each in unit tests, and time and memory have not been measured.
- It has no clock, no random numbers, no external boundary and no credentials, and it does not use files or network communication. Side effect: NONE.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Call projectRoleContext({ roles, items, non_use, policy_version, required? }) in src/role-context.js (it does not throw exceptions). Returns: { ok: true, errors: [], facts } (projection, excluded and missing_required for each role, and non_use_closure, non_use_not_in_items, items_not_entered and need_input; inference and advice are null) or { ok: false, errors: [{ code, path, limit? }] }. Side effect: NONE. There is no state, no external connection and no credentials, and there is no port for the user to provide.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-13PTsugite
Content, revision and patch application — Content, Revision and Patch Application Tsugite
The Content, Revision and Patch Application Tsugite (継手, tsugite) carries a Kernel's typed patch (typed-patch/v1) to one piece of content in a Runtime store, either to apply it (APPLY) under an optimistic revision (expected_revision) or to check it without writing (CHECK), and checks that the returned receipt (ContentPatchApplyReceipt) follows the contract. The store belongs to the Runtime; this Tsugite does not create a store (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind saving, it prevents one person's change from overwriting someone else's. It carries the patch bound to the revision and to the user's confirmation, does not confuse received, applied and unknown, and does not apply a patch twice.
When to use
- You want to apply a change to content only when the base revision (expected_revision) is the same as the current revision. If the revision differs, the result is CONFLICT and nothing is written.
- You want to check only whether a patch can be applied, without writing it (op CHECK, effect NONE).
- You want to carry an AI proposal as adopted (ADOPTED_CANDIDATE) only after the user has personally checked that proposal and its patch. The confirmation is received through client.recordConfirmation.
- You want to resolve an apply whose result is unknown by resending the same request, so that the patch is not applied twice.
When not to use
- You want the store or revision management itself. Atomic application, revisions and permissions are the Runtime's role; this Tsugite does not create a store.
- You want to create a patch, or to check the correctness of a patch's content or of its inverse operation. That is the role of the Kernel and the Runtime.
- You want an automatic rebase or retry after CONFLICT. This Tsugite does not silently rebase or retry.
- You want a connection target you can use right away. It has applied to a real Runtime zero times, and the port is only a test stand-in. The Runtime-side commands needed to implement the port exist only as a design so far.
- You want to build business rules. This Tsugite is an application-platform Tsugite that connects Kernels with data and services; business rules are handled by parts (Kernels).
What it checks
- It checks the contract version. When a request's contract_pin differs from the configured contract_schema_digest, it does not send it. Without a configuration, it sends nothing.
- It sends only to workspaces allowed by the configured workspace_ids. Knowing a content hash is not treated as a permission.
- It checks the user's confirmation. It sends ADOPTED_CANDIDATE only when there is a confirmation by the user personally (origin HUMAN) bound to that proposal and its patch (the patch_digest value). A confirmation is used up by an APPLY answer that has been matched.
- It matches what was received. It accepts a receipt only when the received patch_digest is exactly the same as in the request and base_revision equals expected_revision. It accepts APPLIED only when new_revision is expected_revision + 1 and there is a digest of the new content, and CONFLICT only when nothing has been written.
- It accepts a receipt as an ordinary answer only when its contract_pin is the same as the request's. For a different version it accepts only an INCOMPATIBLE_VERSION refusal; an INCOMPATIBLE_VERSION with the same pin is treated as an anomaly and it stops.
- An APPLY whose result is unknown (a port failure, or an UNKNOWN receipt) is resolved by resending the same request (if port.lookup exists, it reads the record first). Until it is resolved, it sends no other request, and it never sends different content under the same request_id.
- It does not accept, and stops on, a receipt that claims to be an ERABAZU store, a receipt whose effect is escalated or unknown, or a receipt for a different request or a different workspace (HALTED after RECEIPT_REJECTED). The effect of APPLY is LOCAL_REVERSIBLE; the effect of CHECK is NONE.
What it does not do / limits
- It does not check that the patch bytes hash to the patch_digest value, that target.content_digest is the digest of the base revision's content, or that the new content digest is the digest of the content after application (that is the Runtime's role).
- It does not decide on permissions (REF_NOT_PERMITTED), a different workspace (WORKSPACE_MISMATCH) or protected ranges (PROTECTED_RANGE). The Runtime decides, and this Tsugite returns that answer.
- Not applying a patch twice depends on the Runtime's deduplication. The tests only checked that the stand-in behaves that way.
- The records of user confirmations and of matched receipts exist only inside this Tsugite and are lost on restart. It does not check whether a port that claims a different version really has that version.
- Being applied is not approval: it does not mean the content is correct, and it is not approval to adopt (every result carries approval: false).
- It does not change the contract. The schema authority for the contract has not been set yet (NOT_SPECIFIED).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
How to connect
Contract: erabazu.candidate/certified-tsugite/content-patch-apply/v1 (ContentPatchApplyRequest / ContentPatchApplyReceipt). submit(request) on createContentPatchApplyClient({ cfg, port }) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED (always approval: false). User provides: a port to the Runtime (submit, and optionally lookup) and cfg (contract_schema_digest and workspace_ids). Side effect: APPLY is LOCAL_REVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-14PTsugite
File input and output — File Input/Output Tsugite
The File Input/Output Tsugite (継手, tsugite) uses the handle of a file the user selected in the App to carry requests (FileIoRequest) for an import preview (IMPORT_PREVIEW) and an export (EXPORT) of CSV (RFC 4180, UTF-8) and JSON to the Host, and matches the returned receipt (FileIoReceipt) against the contract. It does not treat a preview as adoption, and it reads back the exported bytes itself to check them (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents reading or writing a path or URL the user did not select, mistaking an import preview for adoption, and exporting a CSV that still contains cells that look like formulas (cells starting with = + - @ TAB CR).
When to use
- You want to preview a CSV or JSON file the user selected before importing it. The preview returns counts, issues with only a location and a code, and digests.
- You want to export CSV or JSON as a new file and have the Tsugite read it back to check the written bytes' length, sha256, cell quoting and formula protection.
- You want to limit the ops, formats, limits and mappings handled to the range pinned in the configuration.
- You want an export whose result is unknown not to be resent automatically, and further exports to stop until the App has checked the output.
When not to use
- You want to adopt (apply) the import result as it is. A preview is not adoption (adopted: false); adoption is the role of the Content, Revision and Patch Application Tsugite.
- You want to check the correctness of the conversion, of the counts or of the issue locations. That is the role of the conversion and matching parts (Kernels) and of the App.
- You want to handle formats other than CSV (RFC 4180) and JSON, or character encodings other than UTF-8.
- You want to overwrite an existing file. Exports go to new files only.
- You want to read or write by specifying a path or URL directly. This Tsugite uses only handles the App passed through admitHandle.
- You want a Host you can use right away. It has never connected to a real Host or file; the port implementation, how handles are issued and the configuration values will be settled at a separate gate when connecting.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the configuration. It does not send to the Host a request not in allowed_ops or allowed_media_types, a request exceeding the limit ceiling (limits_ceiling; at most the contract's 50 MiB, 1,000,000 rows and 1024 columns), an unpinned mapping, or a request whose declared extension does not match the format (such as a CSV named .json). An empty list allows nothing.
- It checks the contract version. If a request's contract_pin differs from the configured contract_schema_digest, it does not send it. If the version a receipt claims differs from the request's, it accepts nothing except a REJECTED with INCOMPATIBLE_VERSION.
- It checks handles. It does not use a handle the App has not passed, a handle used in the wrong direction (such as an import source handle used for output), a URL or Windows path, or an output handle that has already been exported to.
- It checks the shape of requests. It does not send a CSV without a dialect, a JSON with a dialect or formula protection, a CSV export without formula protection, mixed op fields, an export with key_fields, or an export that allows overwriting. It also does not send a different request under the same request_id (REFUSED).
- It matches receipts. It does not accept, and stops on (HALTED), a spoofed request_digest, a receipt for a different op, a receipt claiming the source was changed, an escalated or unknown effect, a reason that does not fit the op, a receipt whose counts do not add up (rows_valid + rows_invalid is not rows_read), or issues that contain cell values or source text.
- It checks the location and order of issues. It does not accept a receipt that does not fit the row range (0 to rows_read for CSV, 1 to rows_read for JSON), the location field for each format (column for CSV, json_pointer for JSON), whether each code has a location, or the source order (within the same row, a whole-row issue comes first).
- After an export, the Tsugite itself reads the bytes the Host read back (readOutput). It does not accept the result when the length or sha256 differs from the receipt, the bytes are not UTF-8, a CSV cell is not quoted, a cell that looks like a formula remains, the header is not exactly one line, the JSON is not a single array of objects, the row or cell counts do not match, or the receipt overstates the number of escapes added (cells_escaped).
- After a Host failure, UNKNOWN, FAILED or an output that cannot be read back, it does not resend the export automatically. It only asks through lookup, and it does not export again until the App calls outputChecked (imports only read, so they can continue).
What it does not do / limits
- A preview is not adoption, and a successful export does not mean the content is correct (every result carries approval: false).
- It does not read the import bytes (the Host reads them). It checks only the shape and target of source_digest and preview_digest and does not recompute them. It does not check the correctness of the conversion, of the issue locations or of the counts.
- Detecting size, leading bytes and extension, UTF-8 and duplicate JSON keys is the Host's role. The Tsugite checks only the shape of the receipt and the combination of reasons.
- Checking the written bytes assumes the bytes the Host passed are the same as the real file. For cells_escaped it checks only the upper bound. It does not check whether CSV headers or JSON keys are the mapping's names, or the value types.
- Formula protection covers only the contract's triggers (= + - @ TAB CR). A formula after leading spaces, full-width symbols, and other interpretations specific to each spreadsheet application (such as DDE) are out of scope.
- Handle state and exports whose result is unknown exist only inside this Tsugite and are lost on restart. outputChecked only receives the App's statement; it does not check whether the App really checked. It also does not check whether a port that claims a different version really has that version.
- It is not a template for using a specific spreadsheet application or file-picker mechanism.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority. It also does not change the contract (the schema authority is NOT_SPECIFIED).
How to connect
Contract: erabazu.candidate/certified-tsugite/file-io/v1 (FileIoRequest / FileIoReceipt). On createFileIoClient({ cfg, port }), the App passes handles through admitHandle, and submit(request) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED (all with approval: false). User provides: a port to the Host (submit, optionally lookup, and readOutput for exports) and the pins in cfg (version, ops, formats, limits and mappings). Side effect: NONE for a preview, LOCAL_REVERSIBLE for an export.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-15PTsugite
AI execution connection — AI Execution Connection Tsugite
The AI Execution Connection Tsugite (継手, tsugite) carries one AI call fixed by the Runtime (operation, slot, permitted context, budget reservation and attempt number) to the AI service as a request (AiExecutionConnectRequest), and matches the returned receipt (AiExecutionConnectReceipt). It matches delivery state, usage and settlement separately, never turns an unknown into 0, and does not resend automatically (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
When an AI call ends in a timeout or a lost connection, it prevents releasing the reservation with usage recorded as 0 although delivery is unknown, and prevents calling twice by simply resending. By matching, it brings to light mismatches between the records of usage and of results, which are hard to see behind the call.
When to use
- You want to carry one AI call to the AI service using only a provider and model pair the deployment allows and only permitted context references (up to 32).
- You want a timeout or a lost connection to be received as: delivery unknown (UNKNOWN_DELIVERY), usage UNKNOWN, reservation still held, retries stopped.
- You want to issue a resend only as a new attempt after the previous attempt is known to be FAILED_CONFIRMED or NOT_EXECUTED_PROVEN. A new attempt uses a new request_id, operation_id and reservation, from the same budget, up to 3 attempts per slot.
- You want to bind the digest of the AI service's capability declaration and the transport limit (transport_limit_ms) to the request, and stop a call that does not match the declaration before it is sent.
- You want to accept a COMPLETED reply only after checking its byte size and sha256.
When not to use
- You want to call a provider directly. Calling the provider is the AI service's role; this Tsugite is the Runtime-side half.
- You want to call AI from the App or from a part (Kernel). Only the Runtime can call this Tsugite.
- You want an automatic resend after a failure. This Tsugite does not resend automatically; the Runtime decides on a new attempt.
- You want to delegate budget, planning or matching decisions. Those are the Runtime's role. The Runtime decides the result of matching (verdict); this Tsugite only records it.
- You want a connection to an AI service you can use right away. It has never connected to a real AI service or provider (zero calls); the port implementation and configuration values will be settled at a separate gate when connecting.
- You want a template for using a specific AI provider's SDK or billing API. This Tsugite is not such a template.
What it checks
- It checks the caller and the configuration. It does not send a call that does not come from the Runtime, a provider and model the deployment does not allow, a currency not in the configuration, or a request whose contract version differs from the one fixed for the deployment. An empty table allows nothing.
- It checks the shape of the request. It does not send a request with a raw key field, 33 or more or duplicated context references, a fractional amount of money, a max_output_tokens greater than the reserved tokens, a transport_limit_ms that is missing or outside the range (an integer from 1,000 to 600,000), a declaration digest that is missing or points to a different domain or a different provider, or an ai_service_capability_read_at later than the request's time.
- It checks the provider-side document (ai-execution-request/v1). It does not send a document whose sha256(canonical) differs from provider_request_digest, whose provider or model differs from the request, or that contains unpermitted references, raw body text, retry_profile, values that look like actual keys, secret field names, or an auth_ref that is not in reference form.
- It checks the order of attempts. It does not send a 4th attempt in a slot, a 2nd attempt without a previous attempt, a prior_verdict that does not match the previous attempt's result, a retry before matching or after RESPONSE_DURABLE, or an attempt with a different budget, a used reservation, a used operation_id, a different slot, or a digest that does not match the previous receipt.
- When the declaration document (the document from declareCapabilities()) is attached, it compares whether its sha256(canonical) equals the request's digest and whether its transport_limit_ms field equals the request's limit. A declaration that does not fit the canonical form (fractions, integers outside the safe integer range, arrays with holes, getters and so on) is refused without comparison.
- It matches receipts. It does not accept, and stops on (HALTED), a spoofed request_digest, a different operation or reservation, a receipt that sets usage to 0, releases the reservation, allows a retry or claims delivery despite a timeout, a COMPLETED without the provider's receipt or a reply, a SETTLED without grounds, or a receipt claiming that prompts or keys were written to logs.
- It matches the version and origin of receipts. It does not accept a receipt with no pin or a different version, an INCOMPATIBLE_VERSION with the same pin as the request, a receipt in which the AI service claims to be the Runtime's record, or a receipt that uses the same provider receipt for a different operation (RECEIPT_CONFLICT). After CAPABILITY_MISMATCH, it sends an attempt only when its digest equals that receipt's current_capability_digest.
- For COMPLETED, it reads the reply bytes with readReply and accepts them only when the length is byte_size (up to 16,384) and the sha256 equals reply_digest. If they cannot be read, the result is UNKNOWN; if they do not match, it does not accept them and stops.
What it does not do / limits
- Delivery does not mean the content is correct or adopted. Every result carries approval: false and automatic_re_request: false, and the Runtime decides on a re-request as a new attempt.
- It does not check the meaning, correctness or adoption of the reply's content (only delivery and the sha256 of the bytes). It also does not check the correctness of the matching result (verdict).
- It does not check the usage values, unit rates or the correctness of settlement, or whether the reserved amount is enough. Usage that exceeds the reservation is also returned as a fact, as it is.
- It does not handle keys. Keys are held by the AI service and referred to by reference (HELD_BY_PORT). It also does not copy the message of a port exception into the result, because it may contain an actual key.
- Attempt records, reservation IDs, operation_ids and the provider receipts it has seen exist only inside this Tsugite and are lost on restart. After a restart, it does not send attempts from the 2nd onward.
- It does not decide when to give up on an attempt with no answer and turn it into a Runtime record (recordNoAnswer). This Tsugite has no clock; it only checks that the record's time is at least the request's time + transport_limit_ms.
- It does not check whether an attached declaration document or current_capability_digest really belongs to the AI service's current declaration, or whether the Runtime really re-read the declaration. Whether a real AI service refuses with CAPABILITY_MISMATCH was tried only with a stand-in.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority. It also does not change the contract (the schema authority is NOT_SPECIFIED).
How to connect
Contract: erabazu.candidate/certified-tsugite/ai-execution-connect/v1. submit(request, providerRequest, declaration?) on createAiExecutionConnectClient({ cfg, port }) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED (all with approval: false). The Runtime records through recordNoAnswer and recordReconciliation. User provides: a port to the AI service (submit and readReply, and optionally lookup) and the pins in cfg. Side effect: a request is EXTERNAL_IRREVERSIBLE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-16PTsugite
Display and playback connection — Display and Playback Connection Tsugite
The Display and Playback Connection Tsugite (継手, tsugite) passes a view model produced by a part (Kernel) to the App's screen, together with assets and playback settings fixed by digest (ViewPlaybackRequest), and matches the receipt the App returns (ViewPlaybackReceipt). The App draws; this Tsugite does not draw. Whether something was shown or played is checked against the App's build and the output digest (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents believing "displayed" or "played" without evidence, turning the same user action into a command twice, and mistaking showing something for a change of permissions.
When to use
- You want to pass a view model, assets and playback settings fixed by digest to the App's screen, and check what was shown against the App's build and the output digest.
- You want to check that the same input (op, view model, view contract, assets, playback settings and App build) gives the same output digest (reproducibility).
- You want to receive user actions as typed commands only once per view (view_session_id) and per operation_id.
- You want missing assets reported with a reason (NOT_FOUND when absent from the asset storage, DIGEST_MISMATCH when the digest differs) and the observed digest.
When not to use
- You want the drawing itself. The App draws; this Tsugite does not draw.
- You want to generate images. Image generation is outside this Tsugite's scope.
- You want to apply typed commands. This Tsugite does not send commands. Applying them is the role of the Content, Revision and Patch Application Tsugite or of the Runtime.
- You want to use media types other than image, audio, video, text and font, such as scripts, as assets.
- You want a screen or asset storage you can use right away. It has never connected to a real App, screen or asset storage (zero drawings and playbacks); the port implementation and configuration values will be settled at a separate gate when connecting.
- You want a template for a specific UI framework, player or rendering engine. This Tsugite is not such a template.
What it checks
- It checks the configuration and the request. It does not send a contract version different from the one fixed for the deployment, a view contract, version or media type not in the configuration (view_contracts, media_types), a script media type, 257 or more assets, a request that lists the same asset_ref twice, or a request that declares a side effect. An empty table allows nothing.
- It matches what was shown. It does not accept a receipt that answers RENDER with PLAY (or the reverse), a receipt with no evidence of showing, a view model or settings digest different from the request, or a receipt whose output digest changed for the same input.
- It matches missing assets. It does not accept an ASSET_MISSING without a list, assets or media types not in the request, a missing asset without a reason, a DIGEST_MISMATCH with no observed digest or with the same value as declared, a NOT_FOUND with a digest, or a receipt that reports the same asset as missing twice.
- It matches actions. It does not accept a receipt that fails to list a duplicate operation_id, calls a new action a duplicate, has actions for a view that was not shown, has playback actions for RENDER, or has 1,001 or more events or events of an unknown kind. Only events with a new operation_id become commands.
- It matches the receipt's declarations. It does not accept a receipt claiming that it generated an image, added UI to the Runtime, granted permissions or called AI.
- It matches the version and correlation. It does not accept, and stops on (HALTED), a spoofed request_digest, a receipt with no pin or a different version, an INCOMPATIBLE_VERSION with the same pin as the request, a receipt for a different view (view_session_id), or a digest with a different target.
What it does not do / limits
- Showing something allows nothing. Every result carries approval: false and permission_change: NONE.
- It does not check what was actually visible on the screen (pixels and sound). The output digest is the digest of the App's deterministic description of the drawing, and its correctness is the App's responsibility.
- It does not hold the asset bytes. Checking the bytes (NOT_FOUND, DIGEST_MISMATCH) is the App's role, and it cannot check whether the App listed every missing asset or whether an observed digest is really the observed value (it only offers checkAssetBytes for the App's own check).
- It does not check the correctness of the view model itself, the content of the view contract or the content of typed commands (only digests and refs).
- View records (operation_ids seen and reproducibility keys) exist only inside this Tsugite and are lost on restart. Duplicates after a restart cannot be counted.
- It does not accept receipts pushed from the App. It matches only the receipt that answers submit, and assumes the App answers a resend with the same request_id with the same receipt.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority. It also does not change the contract (the schema authority is NOT_SPECIFIED).
How to connect
Contract: erabazu.candidate/certified-tsugite/view-playback/v1. submit(request) on createViewPlaybackClient({ cfg, port }) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED (all with approval: false and permission_change: NONE). DONE carries output_digest, missing_assets, commands (typed commands) and more. User provides: a port to the App's screen (submit, and optionally lookup) and cfg (version, view contracts and media types). Side effect: NONE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-17PTsugite
Shared revisions with multiple participants — Multi-Participant Shared Revision Tsugite
The Multi-Participant Shared Revision Tsugite (継手, tsugite) carries participant events (proposing a change PROPOSE_CHANGE, consent CONSENT, withdrawing consent WITHDRAW_CONSENT, leaving LEAVE, synchronizing SYNC) one at a time to the shared Host (SharedRevisionRequest), and matches the receipt (SharedRevisionReceipt). It binds consent to (who, which revision, that revision's digest) and does not let it carry over to the next revision (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
In collaborative editing, it prevents counting consent to an old revision as consent to a new one, counting an AI role as a person participant, and reissuing an event whose result is unknown after a disconnection so that it is applied twice.
When to use
- You want to carry a flow in which several participants propose changes in a shared space, consent, withdraw consent and leave, one event at a time as the contract requires.
- You want to bind consent to (who, revision, digest) and check that, once a change goes through, consent to the old revision is reported as invalidated.
- You want an AI role to take part only as something operated by a person whose authentication has been recorded, without letting it consent, withdraw or leave, and without counting it as a person.
- You want to resolve an event whose result is unknown after a disconnection by resending it with the same event_id.
When not to use
- You want to run a shared space right now. Who will operate the shared Host has not been decided, and the Host itself does not exist. Until it is decided, every event is refused with HOST_NOT_ASSIGNED and nothing is applied.
- You want a shared Host, a collaborative editing service or a consent management service itself. This Tsugite does not create a Host.
- You want to apply patches to the shared content. That is the role of the Content, Revision and Patch Application Tsugite (P-13). Ordering and applying events, and holding revisions, consents and deduplication, is the shared Host's role.
- You want participants' identities verified. This Tsugite only carries refs to authentication evidence; identity verification is the Host's role.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the configuration and references. It does not send a request when there is no configuration, the contract pin differs from contract_schema_digest, the host_ref differs from the configuration, the workspace is not allowed (workspace_ids), a reference contains .., a drive, control characters or U+2028, the op is unknown, there are extra fields, or the body or intent does not fit the op.
- It checks participants. It does not send events from a person whose authentication evidence has not been recorded, an unregistered AI role, an AI role with a different operator or one that operates itself, a request that uses the same reference both as a person and as an AI role, or a participant who has left (and the AI roles that person operates).
- It does not send consent, withdrawal or leaving by an AI role. It does not accept a receipt claiming that an AI's consent was accepted, or an invalidation or leave notice that names an AI role.
- It matches revisions and consent. It does not accept a change whose new revision is not the base revision + 1, an event that advances the revision without being a change, a revision that goes back before one already known, a CONFLICT at the base revision, a consent record whose (who, revision, digest) differs from the request, consent carried over, or an invalidation without a notice. Consent is sent only to the base revision itself.
- It matches answers to withdrawal and leaving. It does not accept a withdrawal that does not list the ended consent, an answer that lists consent by another person or to a revision that is not current, a leave that omits that person's known consents, a leave without the invalidated_consents field, or CONSENT_NOT_FOUND for an op that is not a withdrawal.
- It checks resends. It does not send a request with different content under the same event_id or the same request_id, or a request that reissues an event under a new event_id while an event with an unknown result exists. It does not accept DUPLICATE_EVENT for a new event or EVENT_ID_REUSE for a resend.
- It matches receipts. It does not accept, and stops on (HALTED), a spoofed request_digest, a different op or event, a state outside the op's table, a receipt that escalates the effect, a receipt with no pin or a different version, or a receipt in which a database in a shared folder or an encrypted blob claims to be the shared Host.
What it does not do / limits
- Being accepted or having consent recorded does not mean the content is correct, nor is it the consent of other participants (every result carries approval: false).
- It does not check whether the shared content (the content of change_ref) and the state digest (target_digest) are correct.
- It only carries refs to authentication evidence. It does not perform identity verification itself, and does not check whether the person operating an AI role really is that person.
- Participant, consent and revision records exist only inside this Tsugite and are lost on restart. It learns about consent, withdrawal or leaving done on other devices only through notices or the Host's answers. It does not check whether the Host delivered notices to all other participants.
- It has never connected to a real shared Host. The tests used only a local stand-in and the HOST_NOT_ASSIGNED port, and the order of checks (pin, then Host assignment, then participants and events) was checked only with the stand-in.
- It checks only the shape of observed_timestamp and does not check whether the clock is correct. It also does not check whether the answering port really has that version.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority. It also does not change the contract (the schema authority is NOT_SPECIFIED).
How to connect
Contract: erabazu.candidate/certified-tsugite/shared-revision/v1. On createSharedRevisionClient({ cfg, port }), recordAuthentication and registerAiRole record people and AI roles, and submit(request) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED (all with approval: false). User provides: a port to the shared Host (createHostNotAssignedPort while the operator is undecided) and cfg (version, host_ref and workspace_ids). Side effect: NONE for SYNC, EXTERNAL_REVERSIBLE for the others.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-18PTsugite
Limited operation and observation connection — Limited Operation and Observation Connection Tsugite
The Limited Operation and Observation Connection Tsugite (継手, tsugite) carries one step of a trial (observing OBSERVE, one permitted action ACT, or resetting to the initial snapshot RESET) to the operation Host of the App's sandbox (SandboxOperationRequest), and matches the receipt (SandboxOperationReceipt). Its scope is only inside the sandbox; it lets through no request or receipt that claims sending outside, purchasing or broad desktop permissions (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents trial operations from leaking outside the sandbox, filling in an action whose result is unknown by clicking again and so acting twice, and continuing to the next trial in an environment that could not be restored.
When to use
- You want to carry observation, permitted actions and resets to the initial state in a trial environment (sandbox), one step at a time, as the contract requires.
- You want to send an action only when it is within the pinned action set and based on the latest observation in that trial (one not yet used as the basis of an action).
- You want each trial to start with a reset whose read-back snapshot equals the initial one (RESET_VERIFIED), and the sequence to advance from 0 by 1.
- You want nothing to be sent to a sandbox after a failed reset until it is recorded that a person has restored it (recordHumanRestore).
When not to use
- You want to send to real sites, make purchases or operate a broad desktop. This Tsugite's scope is only inside the sandbox.
- You want to delegate observation, actions and resets themselves, time measurement, or sandbox isolation. Those are the role of the App's sandbox and the operation Host; this Tsugite does not operate anything.
- You want to delegate deciding whether the goal was reached (the goal check). The goal check is the operation Host's role.
- You want a template for a screen-automation tool or for browser automation. This Tsugite is not such a template.
- You want a sandbox you can use right away. It has never connected to a real App sandbox or operation Host; the tests used only a local stand-in (a three-page practice app).
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks the configuration and scope. It does not send a request when there is no configuration, the contract pin differs from contract_schema_digest, the sandbox is not allowed, the action set is unpinned or has a different digest, the target, action type or selector is outside the set, the request claims sending outside the sandbox, purchasing or a broad desktop, or the limits are out of range (max_actions 1 to 200, max_seconds 1 to 3600).
- It checks the shape of requests. It does not send a request with a reference containing .., a drive or control characters, an unknown op, extra fields, an intent that does not fit the op, an unknown action type, a TYPE_TEXT without input, or a digest with a different target.
- It checks the order of trials. It does not send a trial that does not start with RESET, a trial_id already used, a stopped sandbox or an ended trial, a sequence that skips or repeats, or a trial whose conditions change midway. When max_actions is reached, it does not send ACT.
- It checks actions. It does not send an ACT without an observation or based on an old observation, a second ACT on one observation, a resend of the same action_id (clicking again), or an ACT or RESET after ACTION_UNKNOWN. After ACTION_UNKNOWN, it sends only OBSERVE.
- It matches resets. It does not accept a RESET_VERIFIED whose snapshot differs from the initial one, a RESET_FAILED that allows the next trial, or a RESET_FAILED whose snapshot equals the initial one.
- It matches receipts. It does not accept, and stops on (HALTED), a spoofed request_digest, a different op, trial or sequence, a receipt claiming that something was sent outside or purchased, a receipt that escalates the effect, a reach result without a goal, an ACTION_PERFORMED without a digest of the action record, an actions_used that is miscounted or over the limit, a refusal reason not in the op's table, or a receipt with no pin or a different version.
- It does not resend a request whose result is unknown because of a port failure. It only reads the record through lookup; if nothing is found, OBSERVE and ACT end that trial, and RESET is treated as a failure, stopping the sandbox until a person restores it.
What it does not do / limits
- Having acted or reached a goal does not mean the action was correct or that the user adopted it (every result carries approval: false).
- It does not check sandbox isolation, purchase prevention or screen permissions. It only matches the receipt's zero outside sends and zero purchases; whether they were actually prevented is the operation Host's role.
- It does not check whether the content and digests of observations and snapshots match the screen, whether the goal check is correct, or whether an action record really belongs to that action.
- It has no clock and does not time max_seconds. It learns of a timeout from the operation Host's LIMIT_REACHED. It also does not read the action set document itself; it only matches against the configured set and the digest value.
- Trial records exist only inside this Tsugite and are lost on restart. It learns that a person restored a sandbox only through an evidence ref, and checks whether it was restored by the read-back at the next reset.
- For SEQUENCE_GAP refusals and TARGET_NOT_PERMITTED refusals of OBSERVE, it takes the Host's statement as it is and does not check the cause of the discrepancy. It also does not check whether the answering port really has that version.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority. It also does not change the contract (the schema authority is NOT_SPECIFIED).
How to connect
Contract: erabazu.candidate/certified-tsugite/sandbox-operation/v1. submit(request) on createSandboxOperationClient({ cfg, port }) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED (all with approval: false). For a stopped sandbox, recordHumanRestore records that a person restored it. User provides: a port to the operation Host (submit, and optionally lookup) and cfg (version, sandbox_refs and action_sets). Side effect: NONE for OBSERVE, LOCAL_REVERSIBLE for ACT and RESET.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-19PTsugite
Material and asset reference — Material and Asset Reference Tsugite
The Material and Asset Reference Tsugite (継手, tsugite) fixes one location in a local material the user designated, by revision (exact_revision) or file digest, asks the material Host for a reference with terms of use and a role scope attached (MaterialReferenceRequest), and matches the returned receipt (MaterialReferenceReceipt) against the contract. It either references the fixed material or returns nothing (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
It prevents a referenced material from being replaced midway and the new revision being used without anyone noticing. It also prevents treating instruction-like text inside a material as a command to execute, and filling in an unknown source by guessing.
When to use
- You want to reference a local material the user designated, fixed by revision or digest, with terms of use.
- When the original has changed, you want to be told by REVISION_CHANGED with the observed revision and digest, instead of moving silently to the new revision.
- You want to receive only a set location in a material (a range of pages, lines, characters, time and so on) as bytes whose sha256 and size have been checked.
- You want permission revoked (PERMISSION_REVOKED), a broken reference (NOT_FOUND) and a missing location (LOCATOR_UNRESOLVED) received as separate states.
When not to use
- You want to reference external material such as a URL or another service. External material is out of scope and needs a separate capability declaration and permission.
- You want to check whether a material's content is correct or factual. This Tsugite does not interpret or check the content (NOT_VERIFIED).
- When the original changes, you want it re-referenced automatically at the new revision. It does not create a request with a new fix; the user or the Runtime decides.
- You want a template for using a specific document management or search service. This Tsugite is not such a template.
- You want a material Host you can use right away. It has never connected to a real material or material Host (zero real references); the port implementation, the mechanism for designating materials and the configuration values will be settled at a separate gate when connecting.
- You want to build front-end business logic. This Tsugite is only for backstage and infrastructure APIs; business logic is handled by parts (Kernels).
What it checks
- It checks materials and versions. It does not send a request for a material not in the configured allowed_source_handles, a handle that is a URL or Windows path, a source_locality other than LOCAL_DESIGNATED, or a contract pin that differs from contract_schema_digest. An empty list allows nothing.
- It checks fixes, locations and terms of use. It does not send a request with no fix or two fixes, a digest of a different material, an empty revision, a range without an end, a start after the end, pages or lines numbered from 0, a whole-material location or anchor with a range, an unknown location kind, or no terms of use.
- It matches the fix. It does not accept a receipt that says RESOLVED although it does not match the fix, a receipt that silently returns a new revision, a REVISION_CHANGED that returns content or has no observation, a pinned_matches that contradicts the observation, a LOCATOR_UNRESOLVED whose pin does not match, or a receipt that does not return the pin.
- It treats materials as data. It does not accept a receipt claiming the material is executable, a receipt that allows embedded instructions (embedded_instructions_authorized is not false), or a receipt that presents the material as a checked fact.
- It matches sources. It does not accept KNOWN without source_identity, a guessed source state, or an UNKNOWN that gives a name.
- It matches receipts. It does not accept, and stops on (HALTED), a spoofed request_digest, a receipt that does not return the terms of use or the location, a receipt claiming that an effect was carried out, extra fields, a receipt with no pin or a different version, or an INCOMPATIBLE_VERSION with the same pin as the request.
- It matches content. readContent reads only RESOLVED references, and it does not accept, and stops on, a receipt whose byte_size exceeds 1 MiB. Content exceeding the configured max_content_bytes is refused without being read (REFUSED). Read bytes are returned as bytes only when their length and sha256 equal the receipt's; otherwise it stops.
What it does not do / limits
- Being able to reference something is neither a checked fact nor a judgment that it may be used (every result carries approval: false).
- Because it does not interpret the content bytes, it does not judge the meaning of text in the material (whether it is instruction-like). Treating it as data is the receiving side's role.
- Comparing the fix with the original, extracting the location, and judging permissions, role scope and terms of use are the roles of the material Host and the Runtime. This Tsugite checks only the shape and combinations of the receipt, and cannot detect a Host that fakes an observation with the same value as the fix.
- It does not check whether the location really lies within the fixed revision or whether the extraction follows the unit (how pages are divided, how code points are counted, time positions). It compares only byte_size with the length and sha256 of the bytes read.
- It does not check whether a KNOWN source_identity was really recorded, or whether the material behind a handle really is a local material the user designated.
- Records exist only inside this Tsugite and are lost on restart. Reporting that an earlier reference has lapsed is the Runtime's role. It also does not check whether the answering port really has that version.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority. It also does not change the contract (the schema authority is NOT_SPECIFIED).
How to connect
Contract: erabazu.candidate/certified-tsugite/material-reference/v1. On createMaterialReferenceClient({ cfg, port }), submit(request) asks for a reference, and readContent(request_id) receives the content of a RESOLVED reference as bytes. The result is one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED (all with approval: false). User provides: a port to the material Host (submit, optionally lookup, and readScoped for readContent) and cfg (version, list of materials and content size limit). Side effect: NONE.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-20PTsugite
Export and import round trip — Studio Round-trip and Migration Tsugite
The Studio Round-trip and Migration Tsugite (継手, tsugite) carries requests (StudioRoundtripRequest) to the Runtime for exporting a candidate (EXPORT_RETURN), reading the export destination (INSPECT_EXPORT), verifying a Studio receipt (VERIFY_STUDIO_RECEIPT), a data migration preview (MIGRATION_PREVIEW), a migration decision (MIGRATION_DECIDE) and reading the migration status (MIGRATION_STATUS), and matches the receipt (StudioRoundtripReceipt). It neither overwrites nor retries automatically (fail-closed).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
In the round trip that returns work results to their original place, it prevents overwriting a file at the export destination, deciding a migration with an unverified Studio receipt or a diff the user has not checked, and resending an export or decision whose result is unknown so that something is written or decided twice.
When to use
- You want to export a candidate as a new file (create-new) and resolve an export with an unknown effect by reading the export destination.
- You want to send a data migration preview only when there is a Studio receipt for the same base package that has been verified and accepted (VERIFIED, ACCEPTED).
- You want to send a migration decision only when it has the same digest as the matched preview's diff and the user's own confirmation (recordConfirmation).
- You want to accept adoption (COMMITTED) only with a new generation (old generation + 1) and a new session, keeping the old generation.
- You want to resolve a decision with an unknown result by reading the migration status (MIGRATION_STATUS) and using the table of which decision took effect.
When not to use
- You want to delegate verifying a Studio receipt's signature, computing the diff, or the migration itself. That is the Runtime's role; this Tsugite only checks before sending and after receiving.
- You want to replace a file at the export destination. Exports create new files only; if anything is at the destination, the Runtime writes nothing and answers DESTINATION_EXISTS.
- You want to use a Studio receipt verification or a migration result as permission to run a package or as approval of adoption.
- You want a round trip that works right away. The counterpart entry points on the Runtime side (import, migration, reading the export destination and so on) exist only as a design or not at all, and the port is only a test stand-in. Real exports, reads, verifications and migrations: zero.
- You want to build business rules. This Tsugite is an application-platform Tsugite; business rules are handled by parts (Kernels) and by the composition.
What it checks
- It checks the configuration and the shape of requests. It does not send a request when there is no configuration, the contract pin differs from contract_schema_digest, the export destination is not in destination_handle_refs (for reads too), a reference contains .., a drive, control characters or U+2028, the op is unknown, there are extra fields or fields of another op, the export applies itself automatically (automatic_apply), or the candidates are empty or repeated.
- It checks previews and decisions. It does not send a preview without a verified and accepted Studio receipt, for a different base package, or that failed re-verification; a decision for an unknown migration, with a different diff digest or without a confirmation; a confirmation that is not HUMAN, is bound to another decision, migration or diff, or has already been used; or a second decision on the same preview.
- It matches answers to Studio receipt verification. It does not accept an answer that lets an UNVERIFIED, MISMATCH or unaccepted Studio receipt proceed to preview, an ACCEPTED without the issued package's digest, or a VERIFIED without studio_receipt_id.
- It matches answers to decisions and migrations. It does not accept a receipt that answers ADOPT with DECLINED or DECLINE with COMMITTED, a receipt that does not return or swaps the decision, a COMMITTED that drops the old generation, keeps the same generation or skips a generation, or a receipt for a different migration.
- It matches answers about the export destination. It does not accept an EXPORT_PRESENT without found_bundle, with a different shape or that applies automatically, other states with found_bundle attached, an EXPORT_UNREADABLE without a digest or with a different target, DESTINATION_EXISTS for an op other than EXPORT_RETURN, or reads or refusals that claim an effect.
- It does not resend an export or decision whose result is unknown. An export is resolved by the record (lookup) or by reading the export destination, and a decision by the record or the migration status; until resolved, it sends nothing else. It exports again only on EXPORT_ABSENT, and writes nothing to a destination that holds a different bundle or an unreadable file.
- It matches receipts. It does not accept, and stops on (HALTED), a spoofed request_digest, a receipt for a different op, a state outside the op's table, a receipt claiming the original was edited, issued or re-signed, a receipt claiming a Studio receipt alone permits execution, a receipt with an escalated effect, a refusal reason that does not fit the op, or a receipt with no pin or a different version.
- If the migration status shows that a decision other than the one sent took effect, it does not treat this as an anomaly but returns it as a fact with other_decision_took_effect: true. It makes no further decisions on that migration.
What it does not do / limits
- Having verified a Studio receipt, being able to proceed to preview, or having migrated is neither permission to run a package nor approval of adoption (every result carries approval: false).
- It does not check a Studio receipt's signature, the trusted keys, the sha256 of the bundle or diff bytes, or the migration itself. It only matches the receipt's values.
- It does not check whether found_bundle really is content read from the file at the export destination, whether the read wrote nothing, or whether create-new really replaces nothing. Against a Runtime that breaks create-new, it can protect only destinations it has read and found to hold a different bundle or an unreadable file, and only until restart.
- After an answer that needs a person's decision (needs_human_decision: true), it does not know what the person did. Stopping and asking a person is the caller's job.
- Matched Studio receipts, previews, user confirmations and export destination records exist only inside this Tsugite and are lost on restart.
- It cannot tell whether a state such as COMMITTED is the effect of the decision this Tsugite sent or of the same decision sent by another sender.
- When an unknown export or decision result cannot be resolved (no record, the status stays CREATED or PREVIEWING, or the read is refused), it stays UNKNOWN and stops, waiting for a person's check.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority. It also does not change the contract (the schema authority is NOT_SPECIFIED).
How to connect
Contract: erabazu.candidate/certified-tsugite/studio-roundtrip/v1. On createStudioRoundtripClient({ cfg, port }), recordConfirmation receives the user's confirmation, and submit(request) returns one of DONE, ALREADY_DONE, REFUSED, UNKNOWN, RECEIPT_REJECTED or HALTED (all with approval: false). User provides: a port to the Runtime (submit, and optionally lookup) and cfg (version and destination_handle_refs). Side effect: LOCAL_REVERSIBLE for export, preview and adoption; NONE for reads, verification, declining and status.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-21PComponent
Typed answer intake — Typed answer intake part (Component)
The Typed answer intake part (Component) is a part that checks an AI answer string against a fixed envelope (status, binding, and the content of each branch) and the decision schema passed by the caller, and sorts it into a draft adoption candidate (PROPOSAL_CANDIDATE), HOLD or OUT_OF_SCOPE. It asks the part (Kernel) P-01, through a port, to read the answer and check its types; this part checks the branch rules and the eight binding keys. Even a PROPOSE answer is not adopted (NOT_ADOPTED).
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind an AI "proposal", whether the answer is for stale input is not visible. This part does not make a candidate of an answer with the wrong types, an answer to stale input, or an answer from a port that cannot be read (fail-closed), and stops an answer whose eight binding keys differ from those expected with STALE_OR_UNBOUND. Even if the model declares "valid" or the like in the answer, it does not take that as a basis.
When to use
- You want to check an AI answer string (answer_text, up to 4,194,304 code units) against the envelope { status, binding, proposal | questions | reason } and the caller's decision schema (proposal_schema), and receive it as one of PROPOSAL_CANDIDATE, HOLD or OUT_OF_SCOPE (intake).
- You want to check whether the answer is for the conditions the caller expects (the eight keys product_id, case_id, role, input_revision, input_digest, selection_digest, profile_revision and request_id) by exact match of UTF-16 code units. If they differ, it is STALE_OR_UNBOUND, and the differing keys are returned in a fixed order in detail.mismatched.
- You want to check the content rules of each status (PROPOSE has only proposal, HOLD only questions, OUT_OF_SCOPE only reason) after P-01 has answered that it matches. If they are broken, it is ANSWER_NOT_TYPED (stage: branch).
- You want to receive a type mismatch with the stage at which it failed (detail.stage: json, envelope, proposal, branch) and the P-01 issues (location and code, up to 20).
- You want to attach to the result which decision schema was used, by the digest (schema_digest) of the schema combining the envelope and the decision schema.
- You want the same result from the same answer and the same P-01 answer, from processing that does not throw, does not modify the input, and has no clock, random numbers or state.
When not to use
- You want to use PROPOSAL_CANDIDATE as adoption, storage or application to the outside. PROPOSAL_CANDIDATE is "a draft whose types and binding match"; it is not a statement that the content is correct, and not permission to adopt or store. Recording adoption is the caller's role.
- You want to check whether the content of proposal is correct or whether the basis of the answer holds. This part looks only at types and binding. Checking the basis is the caller's role.
- You want to receive numbers beyond the safe integer range exactly as numbers. proposal is returned as the value read with JSON.parse, so such numbers can be rounded.
- You want to connect an asynchronous port that returns a Promise in place of P-01. Asynchronous ports are not supported; it stops with PORT_FAILED.
- You want to move answers in this envelope into the candidate form of an existing decision schema. That conversion is the role of a separate adapter outside this part, and existing schemas are not changed.
- You want it to also handle confirming the person's identity or keeping the answer. This part does not keep answers and does not confirm identity.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with INVALID_OPTIONS when options is not a plain object or has unknown names or getters; with PORT_MISSING when contract_check or schema_digest is not a function; with INVALID_SCHEMA_ID when schema_id is not a string of 1 to 256 characters; and with INVALID_PROPOSAL_SCHEMA when proposal_schema is not a plain JSON object (a class instance, getters, holes, non-finite numbers, more than 64 levels of nesting).
- It stops with PROPOSAL_SCHEMA_NOT_TRUSTED when P-01 cannot read, or does not trust (keywords outside the subset and so on), the schema combining the envelope and the decision schema.
- It does not let the answer through and stops with PORT_FAILED when P-01 throws, when the form of its answer cannot be read, when state is not done, when it has not looked for duplicate keys, or when an answer it said matches cannot be read as JSON or as the envelope (a false answer).
- It stops with INVALID_ANSWER when answer_text is not a string (including a boxed string) or exceeds 4,194,304 code units, and with INVALID_EXPECTED_BINDING (detail.path) when expected_binding is not a plain object holding exactly the eight keys as strings of 1 to 256 characters.
- Natural-language text, broken JSON, lone surrogates, answers over 1 MiB in UTF-8, duplicate keys, keys outside the envelope, an unknown status, a proposal outside the decision schema, and missing or mixed branch content stop with ANSWER_NOT_TYPED.
- After the type check passes, if even one binding key differs from what is expected (including a difference in case), it stops with STALE_OR_UNBOUND. When inspecting the values itself throws an exception (a Proxy trap and so on), it is INSPECTION_FAILED. Getters are not called.
- Fuzzing of 1,000,000 runs that compares the results against a separately written reference judgement (including random failures of a stand-in for P-01) confirmed that mismatches and fail-open cases were 0. The wiring with the real P-01 was also checked.
What it does not do / limits
- Every result carries adoption: NOT_ADOPTED, approval: false, authority: NOT_DERIVED and claim: NOT_DERIVED. Even for a PROPOSE answer it does not adopt, store or apply anything to the outside; the result is not an approval (result_is_approval: false) and creates no authority (derives_authority: false).
- The judgement of match or mismatch stays with P-01; this part only reads P-01's answer. It does not raise the standing of claims or their basis, and returns the content of proposal as it is.
- That P-01 works correctly is left to P-01's own inspection. What was checked here goes as far as the wiring with the real P-01.
- binding does not include an attempt ID. The AI does not know the executor's attempts, so the mapping between requests and attempts is handled outside this part.
- The content of the decision schema, adoption, storage and identity confirmation are decided by the caller outside this part. It does not keep answers either (its only state is the settings, which are frozen).
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use the output parser of a particular AI.
How to connect
Create an instance with createTypedAnswerIntake({ contract_check, schema_digest, schema_id, proposal_schema }) in src/intake.js and call instance.intake(answer_text, expected_binding). For the ports, pass P-01's checkContract and schemaDigest (synchronous). It returns { ok: true, errors: [], status, binding, proposal, questions, reason, schema_digest } or { ok: false, errors: [{ code, detail? }] }, and does not throw. There are no side effects, no state and no credentials, and its only dependencies are built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
P-22PKernel
Adoption revision ledger — Adoption revision ledger part (Kernel)
The Adoption revision ledger part (Kernel) is a standalone part that applies, one operation at a time on a ledger passed to it, the transitions of revisioned items (candidate, confirmation by a person, adoption, then superseded or revoked) and the confirmation records (applyAdoption), and returns per-item facts from the current conditions and the impact input (reviewAdoption). A new revision (including a correction) always enters as a candidate, and adoption happens only when there is a matching confirmation by a person for that revision. Old revisions, revoked entries and history are not deleted. inference and advice are always null.
Workshop technical inspection PASS
1,000,000 fuzz runs; mismatches 0; fail-open 0
Manual: use cases, inspection scope and limits
Problem addressed
Behind "adopted", who checked which revision is not visible. This part prevents an AI from confirming, adopting or revoking; corrections from being adopted automatically; a resent old revision from being adopted over a newer adoption; superseded adoptions or history from being deleted; and an inconsistent ledger from being used.
When to use
- You want to add a new revision to the ledger as a candidate (CANDIDATE), as a revision larger than any earlier revision of that item (PROPOSE). source is PERSON or AI; an AI proposal goes into the candidate fields, kept apart from the confirmation fields.
- You want to record a confirmation by a person (actor_kind: PERSON) only when the content digest and every binding field are the same as the candidate's (CONFIRM). Even with the same digest, a difference in a binding field makes it a different thing.
- You want to record as adopted only a candidate that has a confirmation by a person, and make the previous adoption superseded (SUPERSEDED) (ADOPT). You want to make a candidate or an adoption revoked (REVOKED) (REVOKE, with reason_ref).
- You want to apply an operation together with the ledger revision (expected_ledger_revision) and, if the ledger has already moved on, apply nothing and receive LEDGER_CONFLICT.
- You want to compare each binding field of the adopted revision with the current conditions (context), and receive per item the differing fields (changed), the fields with no current value (unknown) and the reconfirmation state (CURRENT, RECONFIRM_REQUIRED, RECONFIRM_UNKNOWN, NOT_ADOPTED) (reviewAdoption).
- You want to pass impacts computed separately by the caller (AFFECTED, NOT_AFFECTED, UNKNOWN) as impacts, and receive them mapped to a per-item impact (NOT_AFFECTED, REVOCATION_CANDIDATE, IMPACT_UNKNOWN, IMPACT_NOT_PROVIDED, NOT_ADOPTED).
- You need the same facts from the same input without the clock, random numbers or the environment's locale (the time is passed by the caller as at), without throwing exceptions and without modifying the input.
When not to use
- You want to store the ledger. The new ledger in the result is a candidate before storage (committed: false); storage and atomic comparison are the caller's role.
- You want to check that actor_ref and actor_kind are really that person. It does not confirm identity (authentication) and records them as the caller's declaration.
- You want it to compute impacts, or to check that the impact input is correct. The impact input is only copied, and an impact that was not passed is not made "no impact" but IMPACT_NOT_PROVIDED.
- You want it to carry out revocation. REVOCATION_CANDIDATE is a copy of the impact input, not the execution of a revocation. A REVOKE operation is applied only when it is passed as an operation by a person.
- You want the part to decide the meaning of binding fields (which field is the revision of a material). The names of the fields are decided by the caller.
- You need ledgers beyond the limits (10,000 entries, confirmations and impacts, 100,000 events) or speed on large ledgers. Matching confirmations is quadratic in the count, and speed on large ledgers has not been checked.
- You use it on a version other than Node.js v24.21.0. Other versions have not been checked (NOT_VERIFIED).
What it checks
- It stops with INVALID_INPUT when the argument is not a plain object, has unknown names or getters, or lacks a field; with INVALID_LEDGER when the ledger is malformed (schema, revision numbers, digests, binding, source, state, a confirmation whose actor_kind is not PERSON, the op of an event and so on); and with TOO_MANY_ENTRIES when a count limit is exceeded.
- When the internal consistency of the ledger does not hold (ledger_revision against the number of events, gaps in seq, the same item and revision pair twice, two adoptions, confirmations that do not match an existing candidate, an adoption or superseded adoption without confirmation), it stops with LEDGER_INCONSISTENT and does not use that ledger.
- When an operation is malformed (an unknown op, names or getters not belonging to that op, a missing field, an empty item_id, a revision that is 0, a fraction, a string or an unsafe integer, an uppercase digest, binding fields with differing names, values or count, an unknown source, a missing actor_ref or at), it stops with INVALID_OPERATION.
- It stops with CONFIRMER_NOT_PERSON when the actor_kind of CONFIRM, ADOPT or REVOKE is not PERSON; with CONFIRMATION_MISMATCH (path is the first difference) when the digest or a binding field of CONFIRM differs from the candidate; and with NOT_CONFIRMED when the revision for ADOPT has no confirmation by a person.
- It stops with REVISION_NOT_NEWER when the PROPOSE revision is not larger than earlier revisions; with UNKNOWN_ENTRY when the item and revision pair does not exist; with NOT_A_CANDIDATE when the target of CONFIRM or ADOPT is not a candidate; with STALE_REVISION when there is a newer adoption; and with NOT_REVOCABLE when the target of REVOKE is superseded or already revoked.
- It stops with INVALID_CONTEXT when context is malformed; with INVALID_IMPACTS when impacts is malformed or has the same revision twice; and with INSPECTION_FAILED when inspecting the values itself throws an exception (a Proxy trap and so on). When it stops, it returns the code and location (path, a JSON Pointer) of the first problem.
- Fuzzing of 1,000,000 runs that compares the results, one operation at a time, against a state model written only with linear search confirmed that mismatches and fail-open cases were 0.
What it does not do / limits
- It returns only the ledger transitions and per-item facts. inference and advice are always null, and it makes no judgements. ADOPTED is the fact that "a revision confirmed by a person was recorded as adopted"; it is not a statement that the content is correct, nor an approval (result_is_approval: false). It creates no authority either (derives_authority: false).
- It does not adopt corrections (new revisions) automatically. No operation deletes items, confirmations or events.
- After a newer adoption is revoked, a person can adopt an older confirmed candidate again. STALE_REVISION occurs only when there is a newer ADOPTED.
- Storing the ledger, atomic comparison, confirming identity, carrying out revocation and computing impacts are done by the caller outside this part. The result of the impact computation is received as impacts, and its meaning is not reworked.
- It has no external boundary, no state, no clock and no random numbers. Side effect: NONE, and it uses no credentials.
- Passing the workshop inspection is not certification. Shipment eligibility has not been evaluated, it has not been published, and it grants no authority.
- It is not a template for how to use a particular workflow engine.
How to connect
Call applyAdoption({ ledger, operation }) and reviewAdoption({ ledger, context, impacts }) in src/adoption-ledger.js (neither throws). An empty ledger is schema "erabazu.kernel/adoption-ledger/v1", ledger_revision 0 and three empty arrays. It returns { ok: true, errors: [], facts } (the new ledger with committed: false and transition, or items; inference and advice are null) or { ok: false, errors: [{ code, path }] }. There is no port, and its only dependencies are the Node.js built-ins.
Results from preserved inspection records; live service connection and shipment eligibility are separate. v24.21.0
No matching entries. Shorten your search or change category.