frootai-orchard 1.0.0__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.
@@ -0,0 +1,95 @@
1
+ node_modules/
2
+ website/build/
3
+ website/.docusaurus/
4
+ website/node_modules/
5
+ npm-mcp/node_modules/
6
+ .DS_Store
7
+ *.log
8
+ .env
9
+
10
+ # [M8.18] Foundry deployment config — real values (subscription IDs, etc.)
11
+ # stay local; only the .example template is committed.
12
+ .frootai/foundry-config.json
13
+
14
+ # [M9.11] Per-tenant ACA deployment params — same rationale.
15
+ infra/hosted-mcp/aca.parameters.*.json
16
+ !infra/hosted-mcp/aca.parameters.example.json
17
+ # Pulumi state + tenant stacks
18
+ infra/hosted-mcp/pulumi/Pulumi.*.yaml
19
+ !infra/hosted-mcp/pulumi/Pulumi.yaml
20
+ infra/hosted-mcp/pulumi/node_modules/
21
+ infra/hosted-mcp/pulumi/bin/
22
+
23
+ # [M9.14 / M9.15 / M9.16] k6 load-test results (per-run outputs, not committed).
24
+ load-tests/hosted-mcp/last-run.json
25
+ load-tests/hosted-mcp/last-run-federated.json
26
+ load-tests/hosted-mcp/last-run-autoscale.json
27
+
28
+ # [M9.20] Alpha-tenant onboarding — real configs hold subscription IDs.
29
+ scripts/alpha-tenants.json
30
+ scripts/alpha-tenants/
31
+
32
+ # Test artefact + intermediate report directories — not source code.
33
+ tmp/
34
+
35
+ # Release-gate tarballs and AFCLI-T021 platform evidence are regenerated per run.
36
+ artifacts/
37
+
38
+ # Internal development plans — NOT pushed to remote
39
+ .internal/
40
+
41
+ # Website lives in separate private repo (frootai/frootai.dev)
42
+ website/
43
+ website-v2/
44
+ website-v3/
45
+ website-v4/
46
+ website-v5/
47
+ website-v6/
48
+ website-implementation-plan.md
49
+
50
+ __pycache__/
51
+ *.pyc
52
+
53
+ npm-sdk/node_modules/
54
+ vscode-extension/esbuild.config.d.mts
55
+
56
+ # FAI Factory — temp files (catalog is committed)
57
+ .factory/harvest.json
58
+ .factory/fai-catalog.prev.json
59
+ .factory/changes.json
60
+ .factory/logs/
61
+
62
+ # Build artifacts (Phase 1 OT-3, May 3, 2026)
63
+ # vsix bundles are produced by vsce-publish.yml; never commit
64
+ *.vsix
65
+ **/*.vsix
66
+ # functions deploy.zip is produced by deploy-chatbot.yml; never commit
67
+
68
+ # [X6.4] Cosign bundle-signing key material — NEVER commit the private key.
69
+ # Generated by scripts/marketplace/sign/gen-key.mjs during the founder-only
70
+ # key ceremony (docs/security/cosign-key-ceremony.md); stored in Vault.
71
+ cosign.key
72
+ *.key
73
+ **/cosign.key
74
+ functions/deploy.zip
75
+ **/deploy.zip
76
+ # Python build wheels & sdists are produced by pypi-publish.yml; never commit (Phase 2 cleanup, May 3, 2026)
77
+ python-sdk/dist/
78
+ python-mcp/dist/
79
+ **/*.whl
80
+ **/*.egg-info/
81
+ .pytest_cache/
82
+ **/.pytest_cache/
83
+
84
+ # V0.3 avm-cache: per-module deep-fetched AVM source files (regenerable via npm run verified:catalog --deep-fetch)
85
+ data/avm-cache/
86
+
87
+ # [P1.1] Live telemetry ingest (real tenant data) — never commit; seed is the committed fallback
88
+ observability/*.live.ndjson
89
+
90
+ # 'public/' was an accidental submodule gitlink (no .gitmodules) that broke the
91
+ # Cloudflare Workers Build clone (--recurse-submodules). Untracked + ignored.
92
+ /public/
93
+
94
+ # harvest service corpus (13MB build artifact, bundled into image not git)
95
+ aca/frootai-harvest/corpus/
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ All notable changes to `frootai-orchard` will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-05-26 (Phase A6.9–A6.16)
9
+
10
+ > **Released under tag `orchard-v1.0.0`** (Phase A10 — General Availability). v1.0 additions:
11
+ >
12
+ > - **STABILITY.md** — explicit public-export list (`__all__` is the source of truth) + v1.x semver contract at `frootai-core/python-sdk/orchard/STABILITY.md` (inherits from repo-root `STABILITY.md`).
13
+ > - **v1.0 semver promise** — every public symbol frozen until v2.0; 6-month minimum deprecation window.
14
+ > - **Deprecation framework** at `frootai_orchard._deprecate` (`emit_deprecation`, `format_deprecation_line`); byte-equivalent with the npm peer at `@frootai/orchard-types/lib/deprecate`; opt-out via `FROOTAI_SUPPRESS_DEPRECATIONS=1`.
15
+
16
+ > **Released under tag `orchard-v0.6.0-sdk`** (Phase A6 close — [retro](https://github.com/frootai/frootai-core/blob/main/planning/retros/orchard-phase6.md)). Ships with:
17
+ >
18
+ > - **PyPI trusted publishing** via OIDC (PEP 740 attestations — NO `PYPI_TOKEN` secret used). Audit via the PyPI JSON API: `https://pypi.org/pypi/frootai-orchard/1.0.0/json` → `urls[].provenance`
19
+ > - **CycloneDX 1.5 SBOM** uploaded as 90-day workflow artifact (`orchard-sboms/orchard-py.sbom.json` — zero runtime deps so the SBOM lists only the root component)
20
+ > - **Cross-SDK conformance certificate** uploaded as 90-day workflow artifact (SHA-256 manifest fingerprint, identical byte-for-byte contracts as `@frootai/orchard` v1.0.0)
21
+ > - **Wheel size ≤ 100 KB** enforced as a publish gate (currently ~50 KB)
22
+
23
+ Initial public release of the Python SDK. Byte-equal contract with the npm
24
+ `@frootai/orchard` v1.0.0 SDK.
25
+
26
+ ### Added
27
+
28
+ - `create_client(**opts)` — public client factory
29
+ - `client.list(variety=, where=, limit=)` — iterator over catalog fruits
30
+ - `client.search(query, where=, limit=)` — client-side scored search
31
+ - `client.show(id)` — single fruit detail with index-scan fallback
32
+ - `client.bushel.list/add/remove` — authenticated bushel sync (Team tier+)
33
+ - `client.whoami()` — never-raises auth probe
34
+ - `client.cursor.encode/decode` — opaque HMAC-signed pagination cursors
35
+ - 10-class typed error hierarchy (`FrootaiOrchardError` + subclasses)
36
+ - Filter expression language (`field:value AND/OR/NOT (...)`) byte-equal with npm
37
+ - 3-tier cache (LRU 1k entries → disk `~/.frootai/cache/sdk/` 1h → network) with stale-while-error
38
+ - Decorrelated-jitter retry + per-host circuit breaker + Retry-After honoring
39
+ - Shared `~/.frootai/.token` auth file with the `frootai` CLI
40
+ - `frootai_orchard._orchard_types` byte-equal mirror of `@frootai/orchard-types` v1.0.0
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.5
2
+ Name: frootai-orchard
3
+ Version: 1.0.0
4
+ Summary: Programmatic SDK for the FrootAI Orchard catalog — type-safe list/search/show/bushel surface for partner apps, CI pipelines, and BI integrations. Reads the same ~/.frootai/.token + ~/.frootai/cache as the frootai CLI for transparent shared auth.
5
+ Project-URL: Homepage, https://frootai.dev/orchard
6
+ Project-URL: Repository, https://github.com/frootai/frootai-core
7
+ Project-URL: Issues, https://github.com/frootai/frootai-core/issues
8
+ Project-URL: Changelog, https://github.com/frootai/frootai-core/blob/main/python-sdk/orchard/CHANGELOG.md
9
+ Author-email: FrootAI <hello@frootai.dev>
10
+ License: MIT
11
+ Keywords: accelerators,ai-agents,catalog,frootai,orchard,sdk,solution-plays
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+
24
+ # frootai-orchard
25
+
26
+ Programmatic Python SDK for the FrootAI Orchard catalog — type-safe `list` / `search` / `show` / `bushel` surface for partner apps, CI pipelines, and BI integrations.
27
+
28
+ Byte-equal contract with the npm `@frootai/orchard` v1.0.0 SDK: same enums, same filter expression language, same HMAC-signed pagination cursors, same retry semantics, same shared `~/.frootai/.token` auth file. A query that returns 12 results in JS returns the same 12 results in Python.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ pip install frootai-orchard
34
+ ```
35
+
36
+ Zero runtime dependencies. Python 3.10+. Stdlib only (`urllib`, `hmac`, `base64`, `json`, `hashlib`, `pathlib`).
37
+
38
+ ## Quickstart
39
+
40
+ ```python
41
+ from frootai_orchard import create_client
42
+
43
+ client = create_client()
44
+
45
+ # Anonymous catalog reads — no auth required
46
+ for fruit in client.list(variety="azure", where="ripeness:Mature OR ripeness:Bearing"):
47
+ print(fruit["name"], "—", fruit.get("tagline", ""))
48
+
49
+ # Search with scoring
50
+ for hit in client.search("rag", limit=10):
51
+ print(f'{hit["_score"]:>2} {hit["name"]}')
52
+
53
+ # Single fruit detail
54
+ detail = client.show("ms-azure-openai-rag")
55
+
56
+ # Authenticated — bushel sync (Team tier+)
57
+ client.bushel.add("ms-azure-openai-rag")
58
+ print(client.bushel.list()["ids"])
59
+
60
+ # Never-raises auth probe
61
+ print(client.whoami())
62
+ # → {"signed_in": True, "anonymous": False, "redacted": "…a3f9", ...}
63
+ ```
64
+
65
+ ## Auth priority
66
+
67
+ 1. `token=` keyword argument
68
+ 2. `FROOTAI_TOKEN` environment variable
69
+ 3. `~/.frootai/.token` shared file (same as the `frootai` CLI)
70
+
71
+ A `frootai login` in your terminal transparently authorizes Python SDK calls.
72
+
73
+ ## Typed errors
74
+
75
+ Every public surface raises only `FrootaiOrchardError` subclasses — partners catch by type, never by string match:
76
+
77
+ ```python
78
+ from frootai_orchard import errors as fai_err
79
+
80
+ try:
81
+ client.bushel.add("some-fruit")
82
+ except fai_err.NotSignedInError:
83
+ print("Run `frootai login` first")
84
+ except fai_err.EntitlementRequiredError as e:
85
+ print(f'Upgrade required: {e.context["entitlement"]}')
86
+ except fai_err.RateLimitError as e:
87
+ print(f"Slow down — retry after {e.retry_after_ms} ms")
88
+ ```
89
+
90
+ ## Filter expression language
91
+
92
+ Identical grammar to the npm SDK:
93
+
94
+ ```python
95
+ client.list(where="tier_class:paid AND variety:azure")
96
+ client.list(where="ripeness:Mature OR (variety:gcp AND category:rag)")
97
+ client.list(where='NOT category:eval')
98
+ ```
99
+
100
+ ## License
101
+
102
+ MIT
@@ -0,0 +1,79 @@
1
+ # frootai-orchard
2
+
3
+ Programmatic Python SDK for the FrootAI Orchard catalog — type-safe `list` / `search` / `show` / `bushel` surface for partner apps, CI pipelines, and BI integrations.
4
+
5
+ Byte-equal contract with the npm `@frootai/orchard` v1.0.0 SDK: same enums, same filter expression language, same HMAC-signed pagination cursors, same retry semantics, same shared `~/.frootai/.token` auth file. A query that returns 12 results in JS returns the same 12 results in Python.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install frootai-orchard
11
+ ```
12
+
13
+ Zero runtime dependencies. Python 3.10+. Stdlib only (`urllib`, `hmac`, `base64`, `json`, `hashlib`, `pathlib`).
14
+
15
+ ## Quickstart
16
+
17
+ ```python
18
+ from frootai_orchard import create_client
19
+
20
+ client = create_client()
21
+
22
+ # Anonymous catalog reads — no auth required
23
+ for fruit in client.list(variety="azure", where="ripeness:Mature OR ripeness:Bearing"):
24
+ print(fruit["name"], "—", fruit.get("tagline", ""))
25
+
26
+ # Search with scoring
27
+ for hit in client.search("rag", limit=10):
28
+ print(f'{hit["_score"]:>2} {hit["name"]}')
29
+
30
+ # Single fruit detail
31
+ detail = client.show("ms-azure-openai-rag")
32
+
33
+ # Authenticated — bushel sync (Team tier+)
34
+ client.bushel.add("ms-azure-openai-rag")
35
+ print(client.bushel.list()["ids"])
36
+
37
+ # Never-raises auth probe
38
+ print(client.whoami())
39
+ # → {"signed_in": True, "anonymous": False, "redacted": "…a3f9", ...}
40
+ ```
41
+
42
+ ## Auth priority
43
+
44
+ 1. `token=` keyword argument
45
+ 2. `FROOTAI_TOKEN` environment variable
46
+ 3. `~/.frootai/.token` shared file (same as the `frootai` CLI)
47
+
48
+ A `frootai login` in your terminal transparently authorizes Python SDK calls.
49
+
50
+ ## Typed errors
51
+
52
+ Every public surface raises only `FrootaiOrchardError` subclasses — partners catch by type, never by string match:
53
+
54
+ ```python
55
+ from frootai_orchard import errors as fai_err
56
+
57
+ try:
58
+ client.bushel.add("some-fruit")
59
+ except fai_err.NotSignedInError:
60
+ print("Run `frootai login` first")
61
+ except fai_err.EntitlementRequiredError as e:
62
+ print(f'Upgrade required: {e.context["entitlement"]}')
63
+ except fai_err.RateLimitError as e:
64
+ print(f"Slow down — retry after {e.retry_after_ms} ms")
65
+ ```
66
+
67
+ ## Filter expression language
68
+
69
+ Identical grammar to the npm SDK:
70
+
71
+ ```python
72
+ client.list(where="tier_class:paid AND variety:azure")
73
+ client.list(where="ripeness:Mature OR (variety:gcp AND category:rag)")
74
+ client.list(where='NOT category:eval')
75
+ ```
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,73 @@
1
+ """
2
+ frootai-orchard — public Python SDK for the FrootAI Orchard catalog.
3
+
4
+ Usage:
5
+ from frootai_orchard import create_client
6
+ client = create_client()
7
+ for fruit in client.list(variety="azure"):
8
+ print(fruit["name"])
9
+
10
+ Shared auth with the `frootai` CLI: reads `~/.frootai/.token` so a
11
+ `frootai login` in your terminal transparently authorizes SDK calls.
12
+ Override priority: `token=` arg > `FROOTAI_TOKEN` env > `~/.frootai/.token`.
13
+
14
+ Mirror of the npm `@frootai/orchard` v1.0.0 SDK. Cross-language enum
15
+ byte-equality enforced by the cross-SDK conformance test suite.
16
+ """
17
+
18
+ from . import _orchard_types
19
+ from . import auth, cache, cursor, errors, filter_lang, http_client
20
+ from .client import create_client
21
+ from .cursor import build_cursor_codec
22
+ from .errors import (
23
+ AuthError,
24
+ ConflictError,
25
+ EntitlementRequiredError,
26
+ ERROR_CODES,
27
+ FrootaiOrchardError,
28
+ NetworkError,
29
+ NotFoundError,
30
+ NotSignedInError,
31
+ RateLimitError,
32
+ ServerError,
33
+ TokenExpiredError,
34
+ ValidationError,
35
+ )
36
+ from .filter_lang import build_predicate as build_filter_predicate
37
+ from .filter_lang import parse as parse_filter
38
+
39
+ __version__ = "1.0.0"
40
+ VERSION = __version__
41
+
42
+ __all__ = [
43
+ # Factory
44
+ "create_client",
45
+ # Error types (for isinstance checks at the call site)
46
+ "FrootaiOrchardError",
47
+ "AuthError",
48
+ "NotSignedInError",
49
+ "TokenExpiredError",
50
+ "EntitlementRequiredError",
51
+ "RateLimitError",
52
+ "NetworkError",
53
+ "ValidationError",
54
+ "NotFoundError",
55
+ "ConflictError",
56
+ "ServerError",
57
+ "ERROR_CODES",
58
+ # Filter language (partners can use this directly to inspect ASTs)
59
+ "parse_filter",
60
+ "build_filter_predicate",
61
+ # Cursor codec (partners building their own pagination shell)
62
+ "build_cursor_codec",
63
+ # Version
64
+ "VERSION",
65
+ "__version__",
66
+ # Submodules
67
+ "errors",
68
+ "auth",
69
+ "http_client",
70
+ "filter_lang",
71
+ "cursor",
72
+ "cache",
73
+ ]
@@ -0,0 +1,130 @@
1
+ """A10.3 — Deprecation framework (warn-once-per-process), Python mirror of orchard-types/lib/deprecate.js.
2
+
3
+ Byte-equivalent behaviour with the npm peer:
4
+ * WARN ONCE PER PROCESS PER SYMBOL.
5
+ * NEVER raise — emit to ``sys.stderr`` (never stdout).
6
+ * Opt-out via ``FROOTAI_SUPPRESS_DEPRECATIONS=1``.
7
+ * Machine-readable single-line payload:
8
+ ``[FROOTAI_DEPRECATED] symbol="x" since="1.x.y" removed_in="2.0.0" message="..."``
9
+
10
+ Doctrine:
11
+ * Stdlib only — no third-party deps.
12
+ * Pure ``format_deprecation_line`` exported for tests.
13
+ * Test injection via keyword args (``emitted``, ``write``, ``env``).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ import re
21
+ import sys
22
+ from typing import Callable, Mapping, MutableSet, Optional
23
+
24
+ DEPRECATION_ENV_SUPPRESS = "FROOTAI_SUPPRESS_DEPRECATIONS"
25
+ DEPRECATION_PREFIX = "[FROOTAI_DEPRECATED]"
26
+ _SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$")
27
+
28
+ # Per-process suppression set (mirrors the JS module-scope Set).
29
+ _EMITTED_SYMBOLS: MutableSet[str] = set()
30
+
31
+
32
+ def format_deprecation_line(
33
+ *,
34
+ symbol: str,
35
+ since: Optional[str] = None,
36
+ removed_in: Optional[str] = None,
37
+ message: Optional[str] = None,
38
+ replacement: Optional[str] = None,
39
+ ) -> str:
40
+ """Pure: build the canonical one-line deprecation message."""
41
+ if not isinstance(symbol, str) or not symbol:
42
+ raise TypeError("format_deprecation_line requires non-empty symbol")
43
+ parts = [
44
+ DEPRECATION_PREFIX,
45
+ f"symbol={json.dumps(symbol)}",
46
+ ]
47
+ if isinstance(since, str) and _SEMVER_RE.match(since):
48
+ parts.append(f"since={json.dumps(since)}")
49
+ if isinstance(removed_in, str) and _SEMVER_RE.match(removed_in):
50
+ parts.append(f"removed_in={json.dumps(removed_in)}")
51
+ if isinstance(replacement, str) and replacement:
52
+ parts.append(f"replacement={json.dumps(replacement)}")
53
+ if isinstance(message, str) and message:
54
+ parts.append(f"message={json.dumps(message)}")
55
+ return " ".join(parts)
56
+
57
+
58
+ def emit_deprecation(
59
+ *,
60
+ symbol: str,
61
+ since: Optional[str] = None,
62
+ removed_in: Optional[str] = None,
63
+ message: Optional[str] = None,
64
+ replacement: Optional[str] = None,
65
+ emitted: Optional[MutableSet[str]] = None,
66
+ write: Optional[Callable[[str], None]] = None,
67
+ env: Optional[Mapping[str, str]] = None,
68
+ ) -> dict:
69
+ """Emit a deprecation warning ONCE per process per symbol.
70
+
71
+ Returns ``{"emitted": bool, "line": str|None, "reason"?: str}``.
72
+ """
73
+ if not isinstance(symbol, str) or not symbol:
74
+ raise TypeError("emit_deprecation requires non-empty symbol")
75
+
76
+ effective_env = env if env is not None else os.environ
77
+ if effective_env.get(DEPRECATION_ENV_SUPPRESS) == "1":
78
+ return {"emitted": False, "line": None, "reason": "suppressed_by_env"}
79
+
80
+ effective_set = emitted if emitted is not None else _EMITTED_SYMBOLS
81
+ if symbol in effective_set:
82
+ return {"emitted": False, "line": None, "reason": "already_emitted_this_process"}
83
+
84
+ try:
85
+ line = format_deprecation_line(
86
+ symbol=symbol,
87
+ since=since,
88
+ removed_in=removed_in,
89
+ message=message,
90
+ replacement=replacement,
91
+ )
92
+ except Exception as err: # pragma: no cover - defensive; mirrors npm impl
93
+ line = (
94
+ f"{DEPRECATION_PREFIX} symbol={json.dumps(symbol)} "
95
+ f"message={json.dumps('format_error: ' + str(err))}"
96
+ )
97
+
98
+ def _default_write(s: str) -> None:
99
+ try:
100
+ sys.stderr.write(s)
101
+ except Exception: # pragma: no cover - never propagate
102
+ pass
103
+
104
+ writer = write if callable(write) else _default_write
105
+ try:
106
+ writer(line + "\n")
107
+ except Exception: # pragma: no cover - never propagate
108
+ pass
109
+ effective_set.add(symbol)
110
+ return {"emitted": True, "line": line}
111
+
112
+
113
+ def _reset_emitted_symbols_for_test() -> None:
114
+ """TEST UTILITY — clear the per-process set. NEVER call from production."""
115
+ _EMITTED_SYMBOLS.clear()
116
+
117
+
118
+ def _get_emitted_symbols_for_test() -> set:
119
+ """TEST UTILITY — defensive copy of warned-symbol set."""
120
+ return set(_EMITTED_SYMBOLS)
121
+
122
+
123
+ __all__ = [
124
+ "DEPRECATION_ENV_SUPPRESS",
125
+ "DEPRECATION_PREFIX",
126
+ "format_deprecation_line",
127
+ "emit_deprecation",
128
+ "_reset_emitted_symbols_for_test",
129
+ "_get_emitted_symbols_for_test",
130
+ ]
@@ -0,0 +1,123 @@
1
+ """
2
+ frootai-orchard — byte-equal enum mirror of @frootai/orchard-types v1.0.0.
3
+
4
+ This module is **byte-for-byte identical** to:
5
+ frootai-core/orchard-types/index.js
6
+
7
+ Drift is prevented by the cross-language enum test in
8
+ scripts/orchard/test/sdk_orchard_py_test.py which loads the JS module via
9
+ subprocess and asserts byte-equality with this module.
10
+
11
+ Why a separate Python file instead of pip-installing a py-types package:
12
+ - Zero runtime dependencies (matches @frootai/orchard peer-dep model)
13
+ - Drift detection runs against the JS source of truth on every regression
14
+ - Partners get the enums without an extra install
15
+
16
+ Doctrine:
17
+ - Every enum is a tuple (immutable at runtime — closest Python equivalent
18
+ to Object.freeze on an Array).
19
+ - TIER_RANK is a dict but exposed via MappingProxyType for read-only view.
20
+ - No exported function has side effects.
21
+ """
22
+
23
+ from types import MappingProxyType
24
+ from typing import Mapping, Sequence
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Accelerator manifest enums
28
+ # ---------------------------------------------------------------------------
29
+
30
+ VARIETY_ENUM: Sequence[str] = ("azure", "gcp", "aws", "oss", "hybrid")
31
+ OWNER_TYPE_ENUM: Sequence[str] = ("first_party", "community", "partner", "frootai")
32
+ FROOT_LAYER_ENUM: Sequence[str] = ("F", "R", "O1", "O2", "T")
33
+ RIPENESS_ENUM: Sequence[str] = ("Seedling", "Sapling", "Bearing", "Mature")
34
+ SEASON_ENUM: Sequence[str] = ("Spring", "Summer", "Autumn", "Winter")
35
+ CATEGORY_ENUM: Sequence[str] = (
36
+ "rag", "agent", "multi-agent", "voice", "vision", "chat", "doc", "code",
37
+ "infra", "mcp", "security", "search", "data", "eval", "edge",
38
+ "industry-healthcare", "industry-finance", "industry-retail", "industry-public-sector",
39
+ )
40
+ TRUST_BADGE_ENUM: Sequence[str] = (
41
+ "microsoft_official", "google_official", "aws_official",
42
+ "azd_template",
43
+ "frootai_compatible_full", "frootai_compatible_partial", "frootai_pollinated",
44
+ "eval_proven", "safety_layer", "production_ready",
45
+ )
46
+ ORIGIN_ENUM: Sequence[str] = ("harvested", "cultivated", "first_party")
47
+ POLLINATION_RELATION_ENUM: Sequence[str] = (
48
+ "baseline", "extends_to", "alternative", "uses_pattern", "provides_infra",
49
+ )
50
+ POLLINATION_SOURCE_ENUM: Sequence[str] = ("auto", "manual", "community_pr")
51
+ NO_PLAY_MATCH_TAG: str = "no-play-match"
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Telemetry enums
55
+ # ---------------------------------------------------------------------------
56
+
57
+ EVENT_ENUM: Sequence[str] = (
58
+ "subcommand_invoked",
59
+ "install_succeeded",
60
+ "upgrade_to_play_attempted",
61
+ "tool_invoked",
62
+ )
63
+
64
+ ALLOWED_PROP_KEYS: Sequence[str] = (
65
+ "cmd",
66
+ "variety",
67
+ "paid",
68
+ "dry_run",
69
+ "success",
70
+ "exit_code",
71
+ "has_json",
72
+ "tier_class",
73
+ "hooks_count",
74
+ "error_code",
75
+ "ms_elapsed",
76
+ "client",
77
+ "has_args",
78
+ )
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # Entitlement / tier enums
82
+ # ---------------------------------------------------------------------------
83
+
84
+ TIER_ENUM: Sequence[str] = ("free", "pro", "team", "business", "enterprise")
85
+
86
+ TIER_RANK: Mapping[str, int] = MappingProxyType({
87
+ "free": 0,
88
+ "pro": 1,
89
+ "team": 2,
90
+ "business": 3,
91
+ "enterprise": 4,
92
+ })
93
+
94
+ KNOWN_ENTITLEMENTS: Sequence[str] = (
95
+ "upgrade-to-play",
96
+ "bushel-sync",
97
+ "telemetry-dash",
98
+ "sso-saml",
99
+ "audit-export",
100
+ )
101
+
102
+ # ---------------------------------------------------------------------------
103
+ # Pure helpers — no IO, no dependencies
104
+ # ---------------------------------------------------------------------------
105
+
106
+
107
+ def is_tier_at_least(tier: str, required: str) -> bool:
108
+ """Returns True if `tier` is at least `required` per TIER_RANK ordering."""
109
+ a = TIER_RANK.get(tier)
110
+ b = TIER_RANK.get(required)
111
+ if not isinstance(a, int) or not isinstance(b, int):
112
+ return False
113
+ return a >= b
114
+
115
+
116
+ def is_valid_enum(value: str, enum_seq: Sequence[str]) -> bool:
117
+ """Returns True if `value` is a valid member of `enum_seq`."""
118
+ if not isinstance(value, str):
119
+ return False
120
+ return value in enum_seq
121
+
122
+
123
+ ORCHARD_TYPES_VERSION: int = 1