Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ The SDK's organizational architecture strictly mirrors the Rust version:
mutation. Exact policy identity and approval state are retained with audit
evidence. Missing customer policy or approval emits stable warnings without
changing persistence semantics; an explicit denial fails closed.
* **Portable Business ID Encoding**: Core scope/key types and the runtime
`daily-permuted-v1` encoder execute the canonical cross-language golden
vectors without exposing the internal sequence.
* **TeaQL Federal Protocol Client**: `TeaQLFederalClient` and `TfpHttpProvider`
execute governed canonical TFP v1 queries and audited mutations against a
remote TeaQL endpoint such as Rust. Direct query execution returns
Expand Down Expand Up @@ -131,6 +134,26 @@ The repeatable [`examples/opaque-entity-reference`](examples/opaque-entity-refer
example proves the exact cross-language golden vector and purpose-substitution
rejection.

## Business ID V1 Foundation

The pure runtime encoder maps a durable internal sequence into the canonical
six-character, scope-specific Base36 code:

```python
from teaql.core import BusinessIdEncodingKey, BusinessIdScope
from teaql.runtime import encode_business_id_permutation_v1

scope = BusinessIdScope(
"tenant-a", "commerce_order", "order_number", "20260925"
)
key = BusinessIdEncodingKey(1, key_from_secret_manager)
code = encode_business_id_permutation_v1(0, scope, key)
```

Durable concurrent allocation, aggregate retry reuse, and typed lookup are
separate lifecycle capabilities. Secret key material is application-owned and
must not be placed in KSML or generated source.

### Mutation Policy installation

Policy implementations are installed from trusted application startup through
Expand Down
8 changes: 8 additions & 0 deletions src/teaql/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
from .eval import LoadState, EvalResult
from .safe_expression import SafeExpression
from .xls import XlsWorkbook, XlsPage, XlsBlock, XlsBlockBuildContext
from .business_id import (
BusinessIdEncodingKey,
BusinessIdError,
BusinessIdErrorCode,
BusinessIdScope,
)

__all__ = [
"Value", "DataType", "Timestamp",
Expand All @@ -39,6 +45,8 @@
"GraphNode",
"EntityDescriptor", "PropertyDescriptor", "SmartList", "TeaQLPage",
"LoadState", "EvalResult", "SafeExpression",
"BusinessIdEncodingKey", "BusinessIdError", "BusinessIdErrorCode",
"BusinessIdScope",
"XlsWorkbook", "XlsPage", "XlsBlock", "XlsBlockBuildContext"
]
import builtins
Expand Down
65 changes: 65 additions & 0 deletions src/teaql/core/business_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Portable core types for externally visible Aggregate-root Business IDs."""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum


class BusinessIdErrorCode(str, Enum):
DEFINITION_INVALID = "BUSINESS_ID_DEFINITION_INVALID"
RANGE_EXHAUSTED = "BUSINESS_ID_RANGE_EXHAUSTED"
ENCODING_FAILED = "BUSINESS_ID_ENCODING_FAILED"


class BusinessIdError(ValueError):
def __init__(self, code: BusinessIdErrorCode, message: str):
self.code = code
super().__init__(message)


@dataclass(frozen=True)
class BusinessIdScope:
domain_root_key: str
aggregate_type: str
namespace: str
period_key: str

def __post_init__(self) -> None:
for name, value in (
("domain_root_key", self.domain_root_key),
("aggregate_type", self.aggregate_type),
("namespace", self.namespace),
("period_key", self.period_key),
):
if not isinstance(value, str) or not value.strip():
raise BusinessIdError(
BusinessIdErrorCode.DEFINITION_INVALID,
f"{name} must not be blank",
)


@dataclass(frozen=True)
class BusinessIdEncodingKey:
version: int
key: bytes

def __post_init__(self) -> None:
if (
not isinstance(self.version, int)
or isinstance(self.version, bool)
or not (1 <= self.version <= 0xFFFFFFFF)
):
raise BusinessIdError(
BusinessIdErrorCode.DEFINITION_INVALID,
"Business ID key version must be a positive u32",
)
if (
not isinstance(self.key, (bytes, bytearray, memoryview))
or len(self.key) != 32
):
raise BusinessIdError(
BusinessIdErrorCode.DEFINITION_INVALID,
"Business ID V1 key must contain exactly 32 bytes",
)
object.__setattr__(self, "key", bytes(self.key))
9 changes: 8 additions & 1 deletion src/teaql/runtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
UNSAFE_RAW_ENTITY_REFERENCES_ACKNOWLEDGEMENT,
UNSAFE_RAW_ENTITY_REFERENCES_ENVIRONMENT,
)
from .business_id import (
BUSINESS_ID_PERMUTATION_V1_ALPHABET,
BUSINESS_ID_PERMUTATION_V1_DOMAIN_SIZE,
BUSINESS_ID_PERMUTATION_V1_MAX_SEQUENCE,
BUSINESS_ID_PERMUTATION_V1_WIDTH,
encode_business_id_permutation_v1,
)
from .mutation_policy import (
MISSING_APPROVAL,
MISSING_POLICY,
Expand All @@ -44,7 +51,7 @@
from .wire_fields import NormalizedWireInput, WireEntityMetadata, WireFieldMetadata, WireInputError, create_wire_entity_metadata, encode_wire_output, normalize_wire_input, retain_submitted_paths
from teaql.core.entity import EntityKey, EntityChangeSet, EntityRoot

__all__ = ["WireFieldMetadata", "WireEntityMetadata", "NormalizedWireInput", "WireInputError", "create_wire_entity_metadata", "normalize_wire_input", "encode_wire_output", "retain_submitted_paths", "EntityKey", "EntityChangeSet", "EntityRoot", "ContextEntityRef", "ContextRootError", "CheckException", "CheckResult", "I18nCatalog", "JsonFieldNamingProfile", "Locale", "ObjectLocation", "UnsupportedLocaleError", "UserContext", "TeaqlRuntime", "SqlLogEntry", "SqlLogOperation", "DiagnosticSqlLogSink", "TextDiagnosticSqlLogSink", "ServiceRuntimeFromEnv", "RuntimeModule", "DataStore", "RawAuditEvent", "SafeAuditEvent", "MutationAuditKind", "BusinessClock", "FixedBusinessClock", "SystemBusinessClock", "AeadEntityReferenceCodec", "EntityReferenceClaims", "EntityReferenceCodec", "EntityReferenceTokenError", "ENTITY_REFERENCE_AAD", "UNSAFE_RAW_ENTITY_REFERENCES_ACKNOWLEDGEMENT", "UNSAFE_RAW_ENTITY_REFERENCES_ENVIRONMENT", "ContextTools", "ExecutableHttpTool", "HTTP_TOOL", "HttpIntentPhase", "HttpTool", "HttpToolProvider", "ToolDeniedError", "ToolError", "ToolPolicy", "ToolRisk", "Tools", "ToolToken", "ToolUnavailableError"]
__all__ = ["WireFieldMetadata", "WireEntityMetadata", "NormalizedWireInput", "WireInputError", "create_wire_entity_metadata", "normalize_wire_input", "encode_wire_output", "retain_submitted_paths", "EntityKey", "EntityChangeSet", "EntityRoot", "ContextEntityRef", "ContextRootError", "CheckException", "CheckResult", "I18nCatalog", "JsonFieldNamingProfile", "Locale", "ObjectLocation", "UnsupportedLocaleError", "UserContext", "TeaqlRuntime", "SqlLogEntry", "SqlLogOperation", "DiagnosticSqlLogSink", "TextDiagnosticSqlLogSink", "ServiceRuntimeFromEnv", "RuntimeModule", "DataStore", "RawAuditEvent", "SafeAuditEvent", "MutationAuditKind", "BusinessClock", "FixedBusinessClock", "SystemBusinessClock", "AeadEntityReferenceCodec", "EntityReferenceClaims", "EntityReferenceCodec", "EntityReferenceTokenError", "ENTITY_REFERENCE_AAD", "UNSAFE_RAW_ENTITY_REFERENCES_ACKNOWLEDGEMENT", "UNSAFE_RAW_ENTITY_REFERENCES_ENVIRONMENT", "BUSINESS_ID_PERMUTATION_V1_ALPHABET", "BUSINESS_ID_PERMUTATION_V1_DOMAIN_SIZE", "BUSINESS_ID_PERMUTATION_V1_MAX_SEQUENCE", "BUSINESS_ID_PERMUTATION_V1_WIDTH", "encode_business_id_permutation_v1", "ContextTools", "ExecutableHttpTool", "HTTP_TOOL", "HttpIntentPhase", "HttpTool", "HttpToolProvider", "ToolDeniedError", "ToolError", "ToolPolicy", "ToolRisk", "Tools", "ToolToken", "ToolUnavailableError"]

__all__ += [
"MISSING_APPROVAL", "MISSING_POLICY",
Expand Down
79 changes: 79 additions & 0 deletions src/teaql/runtime/business_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Canonical TeaQL Business ID fixed-domain permutation profile V1."""

from __future__ import annotations

import hashlib
import hmac
import struct

from teaql.core.business_id import (
BusinessIdEncodingKey,
BusinessIdError,
BusinessIdErrorCode,
BusinessIdScope,
)


BUSINESS_ID_PERMUTATION_V1_WIDTH = 6
BUSINESS_ID_PERMUTATION_V1_DOMAIN_SIZE = 2_176_782_336
BUSINESS_ID_PERMUTATION_V1_MAX_SEQUENCE = BUSINESS_ID_PERMUTATION_V1_DOMAIN_SIZE - 1
BUSINESS_ID_PERMUTATION_V1_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
_MAGIC = b"teaql-business-id-fp-v1\0"


def encode_business_id_permutation_v1(
sequence: int, scope: BusinessIdScope, key: BusinessIdEncodingKey
) -> str:
if (
not isinstance(sequence, int)
or isinstance(sequence, bool)
or sequence < 0
or sequence >= BUSINESS_ID_PERMUTATION_V1_DOMAIN_SIZE
):
raise BusinessIdError(
BusinessIdErrorCode.RANGE_EXHAUSTED,
f"Business ID V1 sequence must be in 0..{BUSINESS_ID_PERMUTATION_V1_MAX_SEQUENCE}",
)
if not isinstance(scope, BusinessIdScope) or not isinstance(key, BusinessIdEncodingKey):
raise BusinessIdError(
BusinessIdErrorCode.DEFINITION_INVALID,
"Business ID V1 requires a BusinessIdScope and BusinessIdEncodingKey",
)

tweak = _canonical_tweak(scope, key.version)
candidate = sequence
while True:
candidate = _permute32(candidate, tweak, key.key)
if candidate < BUSINESS_ID_PERMUTATION_V1_DOMAIN_SIZE:
break
encoded = ["0"] * BUSINESS_ID_PERMUTATION_V1_WIDTH
for index in range(BUSINESS_ID_PERMUTATION_V1_WIDTH - 1, -1, -1):
encoded[index] = BUSINESS_ID_PERMUTATION_V1_ALPHABET[candidate % 36]
candidate //= 36
return "".join(encoded)


def _permute32(value: int, tweak: bytes, key: bytes) -> int:
left = (value >> 16) & 0xFFFF
right = value & 0xFFFF
for round_number in range(8):
digest = hmac.new(
key, tweak + bytes((round_number,)) + struct.pack(">H", right), hashlib.sha256
).digest()
output = struct.unpack_from(">H", digest)[0]
left, right = right, (left ^ output) & 0xFFFF
return (left << 16) | right


def _canonical_tweak(scope: BusinessIdScope, key_version: int) -> bytes:
fields = (
scope.domain_root_key,
scope.aggregate_type,
scope.namespace,
scope.period_key,
)
framed = [_MAGIC, bytes((1,)), struct.pack(">I", key_version)]
for value in fields:
encoded = value.encode("utf-8")
framed.extend((struct.pack(">I", len(encoded)), encoded))
return b"".join(framed)
11 changes: 11 additions & 0 deletions test-vectors/business-id-permutation-v1.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
case_id,key_hex,key_version,domain_root_key,aggregate_type,namespace,period_key,sequence,expected_code,expected_business_id
first,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,1,tenant-a,commerce_order,order_number,20260925,0,S1Z2CG,ORD-20260925-S1Z2CG
middle,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,1,tenant-a,commerce_order,order_number,20260925,1088391168,5ZFLQT,ORD-20260925-5ZFLQT
last,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,1,tenant-a,commerce_order,order_number,20260925,2176782335,MDX0RC,ORD-20260925-MDX0RC
next-date,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,1,tenant-a,commerce_order,order_number,20260926,0,62RSAF,ORD-20260926-62RSAF
other-domain-root,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,1,tenant-b,commerce_order,order_number,20260925,0,Z70J3I,ORD-20260925-Z70J3I
other-aggregate,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,1,tenant-a,maintenance_order,order_number,20260925,0,DMVSGA,ORD-20260925-DMVSGA
other-namespace,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,1,tenant-a,commerce_order,receipt_number,20260925,0,642SAP,ORD-20260925-642SAP
same-key-new-version,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,2,tenant-a,commerce_order,order_number,20260925,0,15Q9BU,ORD-20260925-15Q9BU
rotated-key,f0e0d0c0b0a090807060504030201000112233445566778899aabbccddeeff00,2,tenant-a,commerce_order,order_number,20260925,0,V71L2B,ORD-20260925-V71L2B
leading-zero,000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f,1,tenant-a,commerce_order,order_number,20260925,16,0H5ZEP,ORD-20260925-0H5ZEP
78 changes: 78 additions & 0 deletions tests/runtime/test_business_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import csv
import re
from pathlib import Path

import pytest

from teaql.core import (
BusinessIdEncodingKey,
BusinessIdError,
BusinessIdErrorCode,
BusinessIdScope,
)
from teaql.runtime import (
BUSINESS_ID_PERMUTATION_V1_DOMAIN_SIZE,
encode_business_id_permutation_v1,
)


VECTOR = Path(__file__).parents[2] / "test-vectors" / "business-id-permutation-v1.csv"


def test_matches_every_cross_language_golden_vector() -> None:
with VECTOR.open(newline="", encoding="utf-8") as input_file:
rows = list(csv.DictReader(input_file))
assert len(rows) == 10
for row in rows:
scope = BusinessIdScope(
row["domain_root_key"],
row["aggregate_type"],
row["namespace"],
row["period_key"],
)
key = BusinessIdEncodingKey(
int(row["key_version"]), bytes.fromhex(row["key_hex"])
)
actual = encode_business_id_permutation_v1(int(row["sequence"]), scope, key)
assert actual == row["expected_code"], row["case_id"]
assert f"ORD-{row['period_key']}-{actual}" == row["expected_business_id"]


def test_is_deterministic_unique_and_canonical_for_retained_range() -> None:
scope = BusinessIdScope("tenant-a", "commerce_order", "order_number", "20260925")
key = BusinessIdEncodingKey(
1,
bytes.fromhex(
"000102030405060708090a0b0c0d0e0f"
"101112131415161718191a1b1c1d1e1f"
),
)
values: set[str] = set()
for sequence in range(20_000):
first = encode_business_id_permutation_v1(sequence, scope, key)
after_restart = encode_business_id_permutation_v1(
sequence,
BusinessIdScope(
"tenant-a", "commerce_order", "order_number", "20260925"
),
BusinessIdEncodingKey(1, key.key),
)
assert first == after_restart
assert re.fullmatch(r"[0-9A-Z]{6}", first)
assert first not in values
values.add(first)


def test_rejects_out_of_domain_sequence_and_malformed_definitions() -> None:
scope = BusinessIdScope("tenant-a", "commerce_order", "order_number", "20260925")
key = BusinessIdEncodingKey(1, bytes(32))
for sequence in (-1, BUSINESS_ID_PERMUTATION_V1_DOMAIN_SIZE, True):
with pytest.raises(BusinessIdError) as failure:
encode_business_id_permutation_v1(sequence, scope, key)
assert failure.value.code == BusinessIdErrorCode.RANGE_EXHAUSTED
with pytest.raises(BusinessIdError):
BusinessIdEncodingKey(0, bytes(32))
with pytest.raises(BusinessIdError):
BusinessIdEncodingKey(1, bytes(31))
with pytest.raises(BusinessIdError):
BusinessIdScope(" ", "commerce_order", "order_number", "20260925")
Loading