teaql 0.2.6__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {teaql-0.2.6 → teaql-0.2.7}/PKG-INFO +44 -1
  2. {teaql-0.2.6 → teaql-0.2.7}/README.md +43 -0
  3. {teaql-0.2.6 → teaql-0.2.7}/pyproject.toml +1 -1
  4. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/audit.py +13 -4
  5. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/context.py +8 -2
  6. teaql-0.2.7/src/teaql/runtime/log_privacy.py +93 -0
  7. {teaql-0.2.6 → teaql-0.2.7}/src/teaql.egg-info/PKG-INFO +44 -1
  8. {teaql-0.2.6 → teaql-0.2.7}/src/teaql.egg-info/SOURCES.txt +1 -0
  9. {teaql-0.2.6 → teaql-0.2.7}/LICENSE +0 -0
  10. {teaql-0.2.6 → teaql-0.2.7}/setup.cfg +0 -0
  11. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/__init__.py +0 -0
  12. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/__init__.py +0 -0
  13. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/dynamic_search.py +0 -0
  14. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/entity.py +0 -0
  15. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/eval.py +0 -0
  16. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/expr.py +0 -0
  17. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/graph.py +0 -0
  18. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/list.py +0 -0
  19. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/meta.py +0 -0
  20. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/mutation.py +0 -0
  21. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/query.py +0 -0
  22. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/safe_expression.py +0 -0
  23. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/value.py +0 -0
  24. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/core/xls.py +0 -0
  25. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/data_service/__init__.py +0 -0
  26. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/__init__.py +0 -0
  27. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/mysql/__init__.py +0 -0
  28. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/mysql/dialect.py +0 -0
  29. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/mysql/transport.py +0 -0
  30. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/postgres/__init__.py +0 -0
  31. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/postgres/dialect.py +0 -0
  32. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/postgres/transport.py +0 -0
  33. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/redis/__init__.py +0 -0
  34. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/redis/remote.py +0 -0
  35. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/redis/store.py +0 -0
  36. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/sqlite/__init__.py +0 -0
  37. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/sqlite/dialect.py +0 -0
  38. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/sqlite/transport.py +0 -0
  39. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/provider/tfp_client/__init__.py +0 -0
  40. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/py.typed +0 -0
  41. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/__init__.py +0 -0
  42. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/_schema_capability.py +0 -0
  43. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/builtin-messages-v1.json +0 -0
  44. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/env.py +0 -0
  45. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/i18n.py +0 -0
  46. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/module.py +0 -0
  47. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/opentelemetry.py +0 -0
  48. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/store.py +0 -0
  49. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/telemetry.py +0 -0
  50. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/tools.py +0 -0
  51. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/runtime/wire_fields.py +0 -0
  52. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/sql/__init__.py +0 -0
  53. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/sql/dialect.py +0 -0
  54. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/sql/executor.py +0 -0
  55. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/sql/types.py +0 -0
  56. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/web_integration/__init__.py +0 -0
  57. {teaql-0.2.6 → teaql-0.2.7}/src/teaql/web_integration/fastapi/__init__.py +0 -0
  58. {teaql-0.2.6 → teaql-0.2.7}/src/teaql.egg-info/dependency_links.txt +0 -0
  59. {teaql-0.2.6 → teaql-0.2.7}/src/teaql.egg-info/requires.txt +0 -0
  60. {teaql-0.2.6 → teaql-0.2.7}/src/teaql.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teaql
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: TeaQL Runtime and Data Service for Python
5
5
  Author: TeaQL
6
6
  License-Expression: Apache-2.0
@@ -39,6 +39,28 @@ Dynamic: license-file
39
39
 
40
40
  # TeaQL Python SDK
41
41
 
42
+ ## Sensitive log data
43
+
44
+ Runtime diagnostic logs redact payload values by default, before delivery to
45
+ file, console, buffers, or custom logging sinks. Selecting a diagnostic sink
46
+ alone does not authorize plaintext. For controlled troubleshooting only:
47
+
48
+ ```bash
49
+ export TEAQL_ALLOW_SENSITIVE_PLAINTEXT_LOGS=I_UNDERSTAND_SENSITIVE_DATA_MAY_BE_WRITTEN_TO_DISK
50
+ ```
51
+
52
+ Only this exact value enables plaintext permission; empty values, `true`, and
53
+ whitespace variants do not. Enabling it emits a warning. Credential-classified
54
+ fields remain redacted. The flag does not force every sink to expose values.
55
+ SQL without reliable field/literal provenance may be suppressed and marked
56
+ `NOT REPLAYABLE`. Execution parameters and persisted business data are unchanged.
57
+
58
+ Do not put sensitive data in free-text comments or purpose declarations.
59
+ TeaQL cannot govern arbitrary application prints or independent driver loggers;
60
+ configure those separately. This setting does not erase older plaintext files.
61
+ Restrict access and retention when using plaintext diagnostics, then unset the
62
+ variable and restart processes when troubleshooting is complete.
63
+
42
64
  TeaQL Python SDK is a runtime engine and toolkit for building data-driven business applications. It provides seamless integration with the TeaQL ecosystem, fully aligned with the `teaql-rs` baseline.
43
65
 
44
66
  ## Recommended Agent Harness
@@ -100,5 +122,26 @@ orders = await client.execute_query(FederalQuery(
100
122
  await client.aclose()
101
123
  ```
102
124
 
125
+ ## Security Foundation Status
126
+
127
+ Python currently provides governed local SQL execution and a **TFP client**;
128
+ it does not claim a public TFP server endpoint. Application queries retain
129
+ non-empty comment/purpose and audited mutations retain their audit reason.
130
+ Runtime logging should keep parameterized SQL and intent separate from any
131
+ restricted value-bearing diagnostic output.
132
+
133
+ Portable `UserContext` opaque entity references are not implemented in the
134
+ Python runtime yet. Until that capability is added, applications must not
135
+ invent a Python-specific token format or serialize internal ID/version pairs as
136
+ if they were the TeaQL portable contract. A Python TFP client may carry an
137
+ opaque token issued by a trusted Java, Rust, Go, or .NET backend, but it must
138
+ not decode, rewrite, or mint that token.
139
+
140
+ The planned wire format, fail-closed behavior, shared golden vector, and exact
141
+ development-only acknowledgement are maintained in the canonical
142
+ [opaque entity reference contract](https://github.com/teaql/teaql-conformance/blob/main/design/opaque-entity-references.md).
143
+ Opaque tokens never replace the backend's authorization, tenant, ownership,
144
+ role, or optimistic-version checks.
145
+
103
146
  ---
104
147
  To run test validations and business logic simulations locally, simply run `pytest` in the project root.
@@ -1,5 +1,27 @@
1
1
  # TeaQL Python SDK
2
2
 
3
+ ## Sensitive log data
4
+
5
+ Runtime diagnostic logs redact payload values by default, before delivery to
6
+ file, console, buffers, or custom logging sinks. Selecting a diagnostic sink
7
+ alone does not authorize plaintext. For controlled troubleshooting only:
8
+
9
+ ```bash
10
+ export TEAQL_ALLOW_SENSITIVE_PLAINTEXT_LOGS=I_UNDERSTAND_SENSITIVE_DATA_MAY_BE_WRITTEN_TO_DISK
11
+ ```
12
+
13
+ Only this exact value enables plaintext permission; empty values, `true`, and
14
+ whitespace variants do not. Enabling it emits a warning. Credential-classified
15
+ fields remain redacted. The flag does not force every sink to expose values.
16
+ SQL without reliable field/literal provenance may be suppressed and marked
17
+ `NOT REPLAYABLE`. Execution parameters and persisted business data are unchanged.
18
+
19
+ Do not put sensitive data in free-text comments or purpose declarations.
20
+ TeaQL cannot govern arbitrary application prints or independent driver loggers;
21
+ configure those separately. This setting does not erase older plaintext files.
22
+ Restrict access and retention when using plaintext diagnostics, then unset the
23
+ variable and restart processes when troubleshooting is complete.
24
+
3
25
  TeaQL Python SDK is a runtime engine and toolkit for building data-driven business applications. It provides seamless integration with the TeaQL ecosystem, fully aligned with the `teaql-rs` baseline.
4
26
 
5
27
  ## Recommended Agent Harness
@@ -61,5 +83,26 @@ orders = await client.execute_query(FederalQuery(
61
83
  await client.aclose()
62
84
  ```
63
85
 
86
+ ## Security Foundation Status
87
+
88
+ Python currently provides governed local SQL execution and a **TFP client**;
89
+ it does not claim a public TFP server endpoint. Application queries retain
90
+ non-empty comment/purpose and audited mutations retain their audit reason.
91
+ Runtime logging should keep parameterized SQL and intent separate from any
92
+ restricted value-bearing diagnostic output.
93
+
94
+ Portable `UserContext` opaque entity references are not implemented in the
95
+ Python runtime yet. Until that capability is added, applications must not
96
+ invent a Python-specific token format or serialize internal ID/version pairs as
97
+ if they were the TeaQL portable contract. A Python TFP client may carry an
98
+ opaque token issued by a trusted Java, Rust, Go, or .NET backend, but it must
99
+ not decode, rewrite, or mint that token.
100
+
101
+ The planned wire format, fail-closed behavior, shared golden vector, and exact
102
+ development-only acknowledgement are maintained in the canonical
103
+ [opaque entity reference contract](https://github.com/teaql/teaql-conformance/blob/main/design/opaque-entity-references.md).
104
+ Opaque tokens never replace the backend's authorization, tenant, ownership,
105
+ role, or optimistic-version checks.
106
+
64
107
  ---
65
108
  To run test validations and business logic simulations locally, simply run `pytest` in the project root.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "teaql"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  description = "TeaQL Runtime and Data Service for Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -29,19 +29,28 @@ class RawAuditEvent:
29
29
  category: Optional[str] = None
30
30
 
31
31
  def safe(self, mask_fields: List[str], max_length: Optional[int]) -> "SafeAuditEvent":
32
+ from .log_privacy import REDACTED, credential_name, payload_has_credentials, plaintext_enabled, scrub, value_strings
33
+ allow = plaintext_enabled()
34
+ secrets = []
32
35
  fields = []
33
36
  for change in self.changes:
34
37
  value = None if change.new_value is None else str(getattr(change.new_value, "val", change.new_value))
35
- masked = change.field in mask_fields
38
+ masked = (credential_name(change.field)
39
+ or payload_has_credentials(change.new_value)
40
+ or payload_has_credentials(change.old_value)
41
+ or (change.field in mask_fields and not allow))
42
+ if masked:
43
+ secrets.extend(value_strings(change.old_value))
44
+ secrets.extend(value_strings(change.new_value))
36
45
  if value is not None and masked:
37
- value = _mask(value)
46
+ value = REDACTED
38
47
  truncated = value is not None and max_length is not None and len(value) > max_length
39
48
  if truncated:
40
49
  value = "*" * max_length if max_length <= 3 else value[:max_length - 3] + "..."
41
50
  fields.append(SafeAuditField(change.field, value, masked, truncated))
42
51
  return SafeAuditEvent(
43
- self.kind, self.entity, self.entity_id, tuple(fields), self.trace_chain,
44
- self.actor, self.category,
52
+ self.kind, self.entity, self.entity_id, scrub(tuple(fields), secrets), scrub(self.trace_chain, secrets),
53
+ scrub(self.actor, secrets), self.category,
45
54
  )
46
55
 
47
56
 
@@ -739,6 +739,8 @@ class UserContext:
739
739
  elif entry.affected_rows is not None:
740
740
  entry.result_summary = f"{entry.affected_rows} rows affected"
741
741
 
742
+ from .log_privacy import sql_log_projection
743
+ entry = sql_log_projection(entry)
742
744
  logs = self.sql_logs()
743
745
  logs.append(entry)
744
746
  self._resources["sql_logs"] = logs
@@ -750,7 +752,7 @@ class UserContext:
750
752
  buf.entries.append(UnifiedLogEntry(
751
753
  timestamp=entry.started_at,
752
754
  user_identifier=self.user_identifier(),
753
- trace_chain=getattr(metadata, 'trace_chain', []),
755
+ trace_chain=entry.trace_path,
754
756
  payload=LogPayload.Sql(entry)
755
757
  ))
756
758
 
@@ -784,6 +786,8 @@ class UserContext:
784
786
  elif affected_rows is not None:
785
787
  entry.result_summary = f"{affected_rows} rows affected"
786
788
 
789
+ from .log_privacy import sql_log_projection
790
+ entry = sql_log_projection(entry)
787
791
  logs = self.sql_logs()
788
792
  logs.append(entry)
789
793
  self._resources["sql_logs"] = logs
@@ -1033,7 +1037,7 @@ class SqlLogEntry:
1033
1037
  result_summary: str
1034
1038
 
1035
1039
  class DiagnosticSqlLogSink:
1036
- """Value-bearing diagnostic SQL destination; the text sink is installed by default."""
1040
+ """Policy-projected SQL destination; the text sink is installed by default."""
1037
1041
  def write(self, entry: SqlLogEntry) -> None:
1038
1042
  raise NotImplementedError
1039
1043
 
@@ -1042,6 +1046,8 @@ class TextDiagnosticSqlLogSink(DiagnosticSqlLogSink):
1042
1046
  self._writer = writer
1043
1047
 
1044
1048
  def write(self, entry: SqlLogEntry) -> None:
1049
+ from .log_privacy import sql_log_projection
1050
+ entry = sql_log_projection(entry)
1045
1051
  elapsed_us = int(entry.elapsed.total_seconds() * 1_000_000) if entry.elapsed else 0
1046
1052
  self._writer(
1047
1053
  f"[TeaQL SQL][{entry.operation.name.lower()}][{elapsed_us}us] "
@@ -0,0 +1,93 @@
1
+ """Log-only projections. Never modify execution parameters or stored values."""
2
+
3
+ import logging
4
+ import os
5
+ import re
6
+ from dataclasses import fields, is_dataclass, replace
7
+ from functools import lru_cache
8
+
9
+ PLAINTEXT_ENV = "TEAQL_ALLOW_SENSITIVE_PLAINTEXT_LOGS"
10
+ PLAINTEXT_ACK = "I_UNDERSTAND_SENSITIVE_DATA_MAY_BE_WRITTEN_TO_DISK"
11
+ REDACTED = "[REDACTED]"
12
+ SQL_REDACTED = "[REDACTED SQL; NOT REPLAYABLE]"
13
+
14
+
15
+ @lru_cache(maxsize=1)
16
+ def _warn_plaintext():
17
+ logging.getLogger("teaql.runtime").warning(
18
+ "Sensitive plaintext logging enabled: application data may be written to disk. "
19
+ "Authentication secrets remain redacted."
20
+ )
21
+
22
+
23
+ def plaintext_enabled():
24
+ enabled = os.environ.get(PLAINTEXT_ENV) == PLAINTEXT_ACK
25
+ if enabled:
26
+ _warn_plaintext()
27
+ return enabled
28
+
29
+
30
+ def credential_name(name):
31
+ normalized = re.sub(r"[^a-z0-9]", "", str(name).lower())
32
+ return any(word in normalized for word in (
33
+ "password", "passwd", "passphrase", "privatekey", "secret", "accesstoken",
34
+ "refreshtoken", "idtoken", "apikey", "authorization", "credential",
35
+ "sessiontoken", "magiclinktoken",
36
+ ))
37
+
38
+
39
+ def payload_has_credentials(value):
40
+ value = getattr(value, "val", value)
41
+ if isinstance(value, dict):
42
+ return any(credential_name(k) or payload_has_credentials(v) for k, v in value.items())
43
+ if isinstance(value, (tuple, list)):
44
+ return any(payload_has_credentials(v) for v in value)
45
+ return False
46
+
47
+
48
+ def value_strings(value):
49
+ value = getattr(value, "val", value)
50
+ if isinstance(value, dict):
51
+ return [s for v in value.values() for s in value_strings(v)]
52
+ if isinstance(value, (tuple, list)):
53
+ return [s for v in value for s in value_strings(v)]
54
+ return [] if value is None or value == "" else [str(value)]
55
+
56
+
57
+ def scrub(value, secrets):
58
+ """Copy annotations, removing known values even when embedded in intent."""
59
+ if isinstance(value, str):
60
+ for secret in sorted(set(secrets), key=len, reverse=True):
61
+ value = value.replace(secret, REDACTED)
62
+ return value
63
+ if isinstance(value, list):
64
+ return [scrub(v, secrets) for v in value]
65
+ if isinstance(value, tuple):
66
+ return tuple(scrub(v, secrets) for v in value)
67
+ if isinstance(value, dict):
68
+ return {k: scrub(v, secrets) for k, v in value.items()}
69
+ if is_dataclass(value):
70
+ return replace(value, **{f.name: scrub(getattr(value, f.name), secrets)
71
+ for f in fields(value) if f.init})
72
+ return value
73
+
74
+
75
+ def sql_log_projection(entry):
76
+ # SQL has no per-parameter field provenance. A credential-bearing query
77
+ # therefore suppresses its whole payload, including on debug opt-in.
78
+ credentials = (credential_name(entry.sql) or credential_name(entry.debug_sql)
79
+ or payload_has_credentials(entry.params))
80
+ allow = plaintext_enabled() and not credentials
81
+ if allow:
82
+ return replace(entry, params=list(entry.params), trace_path=list(entry.trace_path))
83
+ secrets = value_strings(entry.params)
84
+ sql = entry.sql
85
+ # Arbitrary literal SQL is not safely redactable across dialects. Preserve
86
+ # parameterized shape only when there are no literals/comments to expose.
87
+ if any(token in sql for token in ("'", '"', "`", "--", "/*", "$")) or re.search(r"\b\d+\b", sql):
88
+ sql = SQL_REDACTED
89
+ return replace(entry, sql=scrub(sql, secrets), params=[None] * len(entry.params),
90
+ debug_sql=SQL_REDACTED, pretty_sql=SQL_REDACTED,
91
+ comment=scrub(entry.comment, secrets), purpose=scrub(entry.purpose, secrets),
92
+ audit_reason=scrub(entry.audit_reason, secrets),
93
+ trace_path=scrub(entry.trace_path, secrets))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teaql
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: TeaQL Runtime and Data Service for Python
5
5
  Author: TeaQL
6
6
  License-Expression: Apache-2.0
@@ -39,6 +39,28 @@ Dynamic: license-file
39
39
 
40
40
  # TeaQL Python SDK
41
41
 
42
+ ## Sensitive log data
43
+
44
+ Runtime diagnostic logs redact payload values by default, before delivery to
45
+ file, console, buffers, or custom logging sinks. Selecting a diagnostic sink
46
+ alone does not authorize plaintext. For controlled troubleshooting only:
47
+
48
+ ```bash
49
+ export TEAQL_ALLOW_SENSITIVE_PLAINTEXT_LOGS=I_UNDERSTAND_SENSITIVE_DATA_MAY_BE_WRITTEN_TO_DISK
50
+ ```
51
+
52
+ Only this exact value enables plaintext permission; empty values, `true`, and
53
+ whitespace variants do not. Enabling it emits a warning. Credential-classified
54
+ fields remain redacted. The flag does not force every sink to expose values.
55
+ SQL without reliable field/literal provenance may be suppressed and marked
56
+ `NOT REPLAYABLE`. Execution parameters and persisted business data are unchanged.
57
+
58
+ Do not put sensitive data in free-text comments or purpose declarations.
59
+ TeaQL cannot govern arbitrary application prints or independent driver loggers;
60
+ configure those separately. This setting does not erase older plaintext files.
61
+ Restrict access and retention when using plaintext diagnostics, then unset the
62
+ variable and restart processes when troubleshooting is complete.
63
+
42
64
  TeaQL Python SDK is a runtime engine and toolkit for building data-driven business applications. It provides seamless integration with the TeaQL ecosystem, fully aligned with the `teaql-rs` baseline.
43
65
 
44
66
  ## Recommended Agent Harness
@@ -100,5 +122,26 @@ orders = await client.execute_query(FederalQuery(
100
122
  await client.aclose()
101
123
  ```
102
124
 
125
+ ## Security Foundation Status
126
+
127
+ Python currently provides governed local SQL execution and a **TFP client**;
128
+ it does not claim a public TFP server endpoint. Application queries retain
129
+ non-empty comment/purpose and audited mutations retain their audit reason.
130
+ Runtime logging should keep parameterized SQL and intent separate from any
131
+ restricted value-bearing diagnostic output.
132
+
133
+ Portable `UserContext` opaque entity references are not implemented in the
134
+ Python runtime yet. Until that capability is added, applications must not
135
+ invent a Python-specific token format or serialize internal ID/version pairs as
136
+ if they were the TeaQL portable contract. A Python TFP client may carry an
137
+ opaque token issued by a trusted Java, Rust, Go, or .NET backend, but it must
138
+ not decode, rewrite, or mint that token.
139
+
140
+ The planned wire format, fail-closed behavior, shared golden vector, and exact
141
+ development-only acknowledgement are maintained in the canonical
142
+ [opaque entity reference contract](https://github.com/teaql/teaql-conformance/blob/main/design/opaque-entity-references.md).
143
+ Opaque tokens never replace the backend's authorization, tenant, ownership,
144
+ role, or optimistic-version checks.
145
+
103
146
  ---
104
147
  To run test validations and business logic simulations locally, simply run `pytest` in the project root.
@@ -43,6 +43,7 @@ src/teaql/runtime/builtin-messages-v1.json
43
43
  src/teaql/runtime/context.py
44
44
  src/teaql/runtime/env.py
45
45
  src/teaql/runtime/i18n.py
46
+ src/teaql/runtime/log_privacy.py
46
47
  src/teaql/runtime/module.py
47
48
  src/teaql/runtime/opentelemetry.py
48
49
  src/teaql/runtime/store.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes