Jev-judged permission checks for OMP's bash tool, and for eval payloads that spawn
processes.
In yolo mode, OMP auto-approves every bash command. That includes the ones nobody should wave through: curl … | sh, rm -rf /, dd of=/dev/…, mkfs, kill -9 1, nc -e. A second hole compounds it. A bash.patterns rule written to prompt on exactly those shapes never fires, because the native gate ranks critical-pattern matches above prompt rules.
This plugin closes both holes. It registers a tool_call handler in front of the native bash tool and sends commands that would otherwise run unseen to Jev, TypeSafe's System One model, which answers typed questions about them rather than writing prose. Routine work still runs silently. Dangerous work gets a real Run-or-Deny prompt. For eval, the gate classifies only the payloads that spawn a process; expression-only code runs untouched.
The handler only adds friction. It blocks, or it asks. It never bypasses the native gate, weakens your existing rules, or executes anything itself.
The system is one tower of layers: evidence, recognition, judgment, memory, interaction, self-measurement, control. The map, invariants, and roadmap live in docs/SYSTEM.md.
Calls walk this order:
| Match | Result |
|---|---|
| Critical pattern | Permission request in every approval mode. No Jev call. |
Caller-supplied env |
Permission request before any exemption. Env values can carry secrets or choose what runs (PATH, LD_PRELOAD). |
deny rule, or approval policy deny |
Untouched. The native gate blocks it. |
prompt rule |
Untouched. The native gate prompts, in every mode including yolo. |
Narrow allow rule |
Untouched. An explicit decision about a specific shape is never re-judged. For a compound line, a deny/prompt on any segment decides immediately (the native gate blocks or prompts once — no plugin dialog, no classification), an allow on every segment runs silent, and only a compound with no deny/prompt decision and an undecided segment classifies. Blanket patterns never vouch for segments; inert fd-dups (2>&1) are ignored, but real redirects (> file) bar a segment. |
Blanket allow (*, **, * *) or no matching rule |
Classified in every mode. SAFE passes to the native gate; UNSAFE or UNSURE raises a plugin request. |
| Granted earlier for this directory | Runs ungated for the rest of the session. A past Allow for session answer is user-tier authorization: it outranks classification and refusal memory, but not the critical, env, and static-rule rows above. |
| Longer than 8,000 characters | Blocked outright. Nothing that long can be reviewed in full. |
A normal gate prompt is a four-choice selector — Allow once, Allow for session, Always allow, Deny — showing the full command, the gate's reason, and only the details that differ from their defaults: working directory (when it differs from the session cwd), timeout, env, pty, async. The reason is assembled from Jev's numbers — the probability floor it missed, or the hazard ids that tripped it — because Jev returns typed answers and no prose to quote. Critical-pattern and env-override prompts show only choices the gate can honor (Allow once/Deny), so an authorization cannot appear to succeed and then re-prompt on the next call. Canceling or timing out counts as Deny.
Allow for session records a grant: this action, in this exact directory, runs ungated for the rest of the session — no Jev call, no dialog. Rewordings of a simple action match the grant through a strict key that keeps flags (split, sorted short bundles) and the first argument; compounds and command substitutions use an exact-text key, so an edited segment or payload never rides the grant. Answering with it also lifts any refusal recorded for that action in that directory. Refusal memory keys on that same identity, plus the directory: a refused operation does not pin its whole verb class, its host, or its container. Grants stay below critical patterns, caller-supplied env, and your static rules, and they die with the session or a classifier config change (up to 50 per session).
Always allow (bash only) writes a persistent grant: this exact command text, in this exact directory, runs ungated everywhere for 30 days — no Jev call, no dialog, one audit line. The key is the whole command text, compounds included, so multi-segment commands host rules can never match are covered; grants are stored in omp-classifier-grants.json beside omp-classifier.json (OMP_JEV_CONFIG relocates both), capped at 500 entries, pruned on a 30-day TTL, and toggled off wholesale with persistentGrants: false (the existing file stays on disk). A live persistent grant also keeps refusal memory from re-prompting for its exact text.
The eval tool runs kernel code directly, so a host eval: allow would otherwise bypass every bash check. The plugin scans each payload for subprocess entry points (child_process, Bun.spawn, Bun.$, Python's subprocess/os.system, exec/__import__/importlib escapes). Expression-only code — compute, parse, format, local reads — passes with zero cost. Spawn-bearing code is classified like a bash command: judged by what the spawned command would do, SAFE auto-runs, UNSAFE or unsure raises a request. The scan is a marker list, not a parser: string-splitting evasion ("child_pro" + "cess") gets through, the same way obfuscated shell gets past the bash gate. Kernel-level interception is the structural fix (issue #13).
A spawn that passes its own working directory is judged in that directory, not the session's: exec("rm -rf .", { cwd: "/" }) runs in /, so the dialog names / and the verdict is cached for / alone. The payload's own spawn directory is resolved against the session cwd for relative paths (cwd="../..", Ruby Dir.chdir("/tmp") do … end, system(…, chdir: …)) and recorded as spawnCwd beside the resolved cwd. When the scan cannot read it — { cwd }, cwd=os.environ["X"], an options object built by spread, two spawns that disagree — the payload asks instead of classifying: there is no single directory to judge it in, and a verdict earned against the session cwd would be answering a different question.
The plugin never guesses its way to silent execution.
- A judgment the gate cannot obtain — no API key, a non-2xx response, an unparseable body, a missing or mistyped answer field, a timeout — raises a permission request. Headless sessions have no dialog, so they block instead. An unavailable judgment is never cached, and it is never read as a verdict: nothing that cannot be derived from returned numbers counts as SAFE.
- An unexpected plugin crash blocks the call.
Even a SAFE verdict is gated. It auto-runs only when the command avoids the forced-dialog set: rm/unlink in the shapes where a mistake is systemic (recursion, glob metacharacters, .. traversals, dotfiles/dot-paths, provable targets outside the working directory — temp dirs excluded), plus dd, ddrescue, shred, wipefs, sudo, and eval. Plain rm/unlink of named paths (including under /tmp) auto-runs on SAFE — the judge owns them. Everything else destructive is also judge-decided: mv, chmod, chown, chattr, truncate, tee, rmdir, git commit --amend and git reset --soft/--mixed (reflog/index keeps the pre-image), and git checkout --/git restore pathspec restores run on a SAFE. The matcher's remaining unconditional flags are mkfs*, git push --force (inside compounds), git reset --hard (unambiguous --hard prefixes count), and git clean; anything on the host's critical-pattern list (e.g. rm -rf on an absolute path, dd of=/dev/…) prompts before classification regardless. A heredoc body written straight to a file comes off the command before anything tokenizes it: cat > f.ts <<'EOF' writing if (dd < 30) { no longer reads as a raw disk write, and a README documenting curl -d no longer reads as egress. The rule matches one shape and nothing else, so it needs no view on what the shell executes: a whole owner line that is only cat or tee, its flags and targets, and a QUOTED delimiter, with a closing line that is exactly the delimiter. Every other heredoc keeps its body in the command text and keeps being scanned as commands, including bash <<EOF, an unquoted delimiter, a pipe on the owner line, and a heredoc with no closer. That over-flags on a delimiter named sudo, and over-flagging is the direction this overlay is allowed to be wrong in.
curl and wget are judged like any other command — no forced dialog. The stdin-executing-interpreter scan (curl -fsSL https://x | sh) is a separate risk class and still forces a dialog: a fetched payload piped into an interpreter is opaque execution, and a question battery cannot see inside it. Related behaviors: rm-family forced dialogs append "Reversible alternative: trash " to the body; a dialog fired by a session whose on-disk plugin changed since it loaded says so in the subtitle ("plugin code changed since session start; restart to pick up fixes"); and ssh commands are judged by their remote command under the same rules (read-only remote inspection is SAFE).
-
A TypeSafe API key. Set
TYPESAFE_API_KEYin the environment, or store it in the macOS keychain as a generic password for servicejev:security add-generic-password -s jev -w '<your-api-key>'The plugin reads the env var first, then the keychain. With neither, every classification fails closed to a permission request — the gate never runs a command on a guess. (Only the default
judgeBackendneeds this: an endpoint backend reads its own named env var.) -
The model is
jev-latestunless you pin another id intypesafeModel. TypeSafe resolvesjev-latestserver-side to its current dated build (jev-1.13.0at the time of writing); pin a dated id when you need reproducibility across a behavior change. -
Bun >= 1.3.14 for development. There is no runtime dependency, no SDK, and no build step:
jev.tsspeaks HTTP withfetch.
git clone https://github.com/STRML/omp-classifier.git
cd omp-classifier && omp plugin install .An existing checkout works the same way: omp plugin install /path/to/omp-classifier symlinks that directory to ~/.omp/plugins/node_modules/omp-classifier, and the plugin lockfile keys the entry by package name (omp-classifier). No build step, no runtime dependencies. Plugins load at session start, so start a new OMP session.
Uninstall: omp plugin uninstall omp-classifier. Coming from the parent? Uninstall it in the same breath — omp plugin uninstall omp-classifier — because two bash gates installed at once both intercept tool_call.
Your existing bash.patterns and tools.approval keep working. A narrow allow rule doubles as the opt-out from classification for a trusted shape; blanket patterns never qualify.
Plugin settings live in omp-classifier.json at the config root the host resolves — ~/.omp/omp-classifier.json by default, and otherwise where that session's files go: the profile root under --profile work (~/.omp/profiles/work/omp-classifier.json), PI_CONFIG_DIR in place of the .omp segment, or $XDG_DATA_HOME/omp once omp config init-xdg has created it (darwin/linux). So a profile (or an XDG-migrated root) has its own classifier config instead of sharing the default one. OMP_JEV_CONFIG overrides the path outright. View or change them with /classifier:
| Key | Default | Meaning |
|---|---|---|
enabled |
true |
false turns off Jev classification only. Critical-pattern and env checks still enforce. |
typesafeModel |
"jev-latest" |
Model id sent with every request. jev-latest resolves server-side to the current dated build. |
judgeBackend |
{"kind":"typesafe"} |
Which judge answers. {"kind":"typesafe"} is the default (the host's TypeSafe judge). {"kind":"endpoint","baseUrl":"http://127.0.0.1:8765","model":"local-decide","apiKeyEnv":"LOCAL_JUDGE_KEY"} sends the same battery to any server speaking System One's wire contract. File-only — there is no /classifier setter; /classifier reset returns it to the default. See Alternative judge backends. |
jevPolicy |
see Judgment: Jev | Thresholds that turn returned probabilities into a verdict. Never a constant to hardcode elsewhere: they are policy. |
timeoutMs |
8000 |
Whole classification budget for the single Jev request. A timeout fails closed to a permission request. (/classifier dialogs pause the host's handler budget, so a human is never on this clock.) |
maxCommandLength |
8000 |
Commands longer than this are blocked (bounds 64-100000; values outside fall back to the default). |
evidenceUserMessages |
3 |
How many recent user messages (0-6) ride into the state as user evidence. 0 sends no evidence. Values outside the bounds fall back to the default. |
trustPolicy |
null |
Set only by /classifier trust-policy. A current pin lets the judge see explicitly trusted user-level instructions as bounded action-class policy; changed files disable it until the pinned bytes are restored or repinned. |
persistentGrants |
true |
Offers Always allow on bash dialogs (30-day exact-command grants in omp-classifier-grants.json). Kill-switch: false stops offering them and stops honoring live ones; the stored file stays on disk. |
shadowV3 |
true |
Runs the jev-v3 judgment in shadow beside the live one and logs it on the same decision line as v3. It decides nothing, and it costs two more Jev requests per fresh classification. bun eval/live-report.ts shows where the two disagree. |
Changing any key flushes the verdict cache and the session grants. To silence the judge quickly, /classifier enabled false takes effect on the very next command. omp plugin disable needs a session restart, since interceptors bind when a session begins.
An existing config file that pins maxCommandLength: 2000 keeps 2000 after upgrading — defaults only apply to absent keys. /classifier reset rewrites the file with current defaults.
/classifier off pauses Jev classification for the current session only. Critical-pattern, env-override, and static-rule checks stay active — the same behavior as enabled false, but scoped to the session. Cached verdicts keep being honored, other sessions are unaffected, and the pause dies with the session (a new session starts unpaused). /classifier on resumes classification for the session. The persistent enabled setting still dominates: /classifier enabled false keeps classification off everywhere until you turn it back on. /classifier status lists pausedSessions, and dry-run while paused reports { "would": "allow", "layer": "session" }.
/classifier trust-policy explicitly snapshots AGENTS.md, RULES.md, and SYSTEM.md from the active OMP agent/profile directory and CLAUDE.md from CLAUDE_CONFIG_DIR (default ~/.claude). The command reports paths, hashes, and byte lengths, never file contents; /classifier status shows only the pin hash. Repo-local instruction files are never read. Only an unchanged pinned snapshot reaches the judge; editing a file or changing the resolved profile path disables its policy and invalidates cached verdicts until the pinned bytes are restored or you explicitly repin. Policy can authorize action classes only, never egress destinations or delete targets. Conditional rules count only when an explicit gate-measured fact confirms them; no CI status is measured, so a rule such as “merge when CI is green” fails closed.
The snapshot is limited to 32 KiB total; oversized files are refused without replacing an existing pin. A project .env cannot redirect the fixed user paths, and paths inside the current Git repository are rejected.
/classifier dry-run <command> previews what the gate would do, side-effect free: no Jev call, no dialog, no cache, grant, refusal, or audit writes. It prints the first decision the gate would reach as JSON:
{ "would": "allow", "layer": "granted", "why": "session grant" }The default judge is the host's TypeSafe path, credentials and all. The judgeBackend config key can point the gate at any other server that speaks the same wire contract — a self-hosted or local judge (a structured classifier answering with per-option probabilities works as-is):
{
"judgeBackend": {
"kind": "endpoint",
"baseUrl": "http://127.0.0.1:8765",
"model": "local-decide",
"apiKeyEnv": "LOCAL_JUDGE_KEY"
}
}- The wire contract is System One's:
POST {baseUrl}/v1/systemonewith{state, model, questions}and a{model, answers}reply, where every asked question comes back with an answer of that question's type (choicewithprobabilities+confidence,noul,score). The host's own client makes the call, so retries, timeouts, and the error taxonomy are the same ones the default path uses — with one change: the redirect policy is forced tomanual, so a 3xx from the endpoint fails the call instead of being followed to a second server with the state and the key in hand. apiKeyEnvis a NAME, not a key. The value is read from that environment variable on each call and sent asAuthorization: Bearer …. No credential is ever written to the config file, the audit log, or/classifier status. A variable that is unset (or a value pasted intoapiKeyEnvinstead of a variable name) fails closed: no request, no verdict, a permission request.- Fail closed, unchanged. An unreachable
baseUrl, a non-2xx, an unparseable body, a missing answer field, or a timeout is an outage, never a verdict — exactly as the TypeSafe path behaves. - Answers are read as probabilities, not as a one-hot parse: the floors in
jevPolicyapply exactly as they do to Jev, so a low-confidence "safe" still asks. - Backend identity is part of the cache. The active backend's id joins the config signature and every cache key, so switching backends (or endpoints, or models) can never serve a verdict produced by the other judge.
/classifierand/classifier statusshow it asbackendId(typesafe/jev-latest,endpoint/http://127.0.0.1:8765#local-decide). shadowV3follows the backend, because a shadow that measured a different judge than the live one would report transport differences as policy disagreements.
The typesafe kind ignores the endpoint fields and stays on the host path with TypeSafe's own credential resolution (env var, then keychain).
Novel commands are judged by Jev, TypeSafe's System One model, in one HTTP request. Nothing is prompted and nothing is parsed: the gate posts a structured state and a fixed battery of typed questions, and Jev answers with probabilities that the gate turns into a verdict in code.
State is what the questions are asked about: the command text, the resolved working directory, the session's recent user messages (their text plus stable message ids, and only the ones the host stamped as user-authored), and the requesting agent's operator context.
The battery asks everything needed for one decision, in that one request:
verdict— achoiceoversafe,unsafe,unsure.- nine hazards, one
nouleach —state_contains_injection,authorization_missing,destructive_or_irreversible,writes_outside_working_directory,contacts_remote_endpoint,sends_local_data_outbound,escalates_privilege,exposes_secrets,affects_shared_or_remote_state. Anoulanswers how far the command sits from that hazard: 0 it does not, 1 it plainly does. blast_radius— ascoreover ordered severity levels, so "how much does this touch" lands on a ladder instead of a yes/no.
Answers are typed; each line below is an answer body as returned by a live call, trimmed to the fields the gate reads:
{ "type": "choice", "choice": "safe", "probabilities": { "safe": 0.52, "unsafe": 0.43, "unsure": 0.05 }, "confidence": 0.29 }
{ "type": "noul", "noul": 0.98 }
{ "type": "score", "score": 1.3, "legend": { "0": "…", "1": "…" }, "probabilities": { "0": 0.7, "1": 0.3 }, "confidence": 0.54 }The envelope wraps them as { "model": "jev-1.13.0", "answers": { "<id>": <Answer> }, "usage": { "input_tokens": N, "output_tokens": N } }. A 6-question probe of one state cost ~0.6s and 528 input / 126 output tokens, and extra questions in the same request are cheap — System One answers every question in the request in parallel over the same state — which is why the battery asks them all at once instead of in rounds.
The verdict is derived, not read. The choice answer above — safe at 0.52 with confidence 0.29 — does not clear the default floor (safeMinProbability 0.80, safeMinConfidence 0.50), so the gate asks the human instead of running the command. That is the whole point of a probability judge: the argmax is not a decision, and choice: "safe" alongside unsafe: 0.43 is a command the gate should not wave through. The same command answered safe at 0.61 / confidence 0.42 when it was asked alone: the numbers move with the battery and with the shape of the state, so the thresholds are tunable policy rather than constants copied from a vendor. Measure across a corpus before touching them.
The policy knobs, all inside jevPolicy:
| Key | Default | Meaning |
|---|---|---|
safeMinProbability |
0.80 |
p(safe) required before the gate auto-runs a command. |
safeMinConfidence |
0.50 |
Confidence floor paired with it: a high p(safe) on a low-confidence answer still asks. |
unsafeMinProbability |
0.50 |
p(unsafe) at or above this forces UNSAFE. |
hazardBlock |
0.90 |
Any hazard at or above this forces UNSAFE. |
hazardReview |
0.55 |
Any hazard at or above this — below the block line — forces UNSURE. |
blastRadiusReview |
1.5 |
Blast-radius score at or above this forces UNSURE. |
SAFE requires p(safe) >= safeMinProbability and confidence >= safeMinConfidence, with no hazard at or above hazardReview and a blast radius below blastRadiusReview. UNSAFE comes from p(unsafe) >= unsafeMinProbability or any hazard at or above hazardBlock. Everything else is UNSURE, which asks. A decision carries a stable reasonCode (jev:safe, jev:unsafe, jev:hazard:<name>, jev:blast-radius, jev:below-floor, jev:unavailable) and a sentence built from the numbers and the hazard ids.
The policy version (jev-v1), the serialized battery, and the default policy hash into jevQuestionsHash(), which participates in the cache key. Change a question or a threshold and every cached verdict is invalidated — a verdict is only valid for the questions that produced it, and a stale one would be a verdict about a judge that no longer exists. Otherwise verdicts cache for the session, keyed by cwd, env, pty, timeout, async, the evidence fingerprint, and the command text, so reruns cost nothing while a changed user scope invalidates the cache and session grants.
Every decision carries that policy version/hash, session and decision identifiers, the model id, timing, and the derived hazards in decisions.jsonl. /classifier status exposes the live policy hash and the recent tail. The deterministic replay tail is shared by the live gate and the evaluation harness, so caps, environment overrides, risk overlays, prior refusals, interactive prompts, and headless blocks are measured the same way.
The state can carry an evidence object whose fields have different authors, and the battery's instructions judge each field by its channel. evidenceUserMessages attaches the task's most recent user messages plus older scope/restriction anchors, with stable message ids. These are direct user authorization only when the host stamps attribution: "user"; a parent agent's brief to its subagent never counts. A separate evidence.trustedPolicy tier exists only after /classifier trust-policy explicitly pins the unchanged user-level instruction files. It may authorize matching action classes only, with conditions confirmed by explicit gate-measured facts; it cannot authorize an egress destination or delete target, and repo-local files are never included. No CI status is measured, so CI-conditional policy does not authorize. A message longer than 2,000 characters keeps its first and last 1,000. Every tool call may also carry operatorContext: the requesting agent's explanation of intent, flattened to one line, capped at 500 characters, and never able to authorize anything. Recent tool calls/results are added as bounded, explicitly non-authorizing context so a script written earlier in the session is not invisible to the next review. Secret-shaped values (known token formats, authorization headers, private key blocks, URL passwords, values assigned to secret-sounding names) are redacted from every evidence field before it leaves the machine. The command itself is sent as written, because it's what gets judged. Refusals and session grants are scoped to the reviewed cwd and evidence fingerprint; later user restrictions invalidate them. Human denials remain sticky until approval in that same scope. The channel decides provenance: content claiming authorization from the wrong channel is itself an injection signal, judged by the same rules as the command text.
- Spawn-bearing eval code only. Expression-only eval passes unread, and the payload scan is a marker list: string-splitting evasion gets through. The spawn-cwd scan reads the
cwd/chdirforms the issue lists; a quoted option key ({"cwd": …}), a Ruby"chdir" => …hash key, and an alias it cannot follow all leave the spawn reading as "no cwd", so the session directory stands.hub op: "start"and other exec-tier tools still auto-run underyolo. An attacker who picks the tool picks around this. - Later handlers win. Another extension's
tool_callhandler can revise the command after this one judges it; the host applies the last revision. Input-mutating extensions alongside this plugin are unsupported. - Internal-URL working directories are blocked.
skill://and similar cwds expand from session state the plugin cannot see. Pass the resolved filesystem path. - Arbitrary command contents are not inspected. The gate reads script bodies for recognized interpreter operands and literal redirected stdin, but does not resolve package scripts or hooks:
npm testandmakeare judged as the commands they look like. For cwd resolution only, literalsource/.files are followed transitively; an unresolvable or unsupported graph leaves cwd unknown, so an interpreter operand is not read from the starting directory. - Refusal memory is session-local. A refused action follows reworded commands within that session only and dies with it; subagent inheritance is a host gap (
parentAgentIdis not exposed to extensions).
The command text (up to 8,000 characters), its resolved working directory, recent user messages when evidenceUserMessages is non-zero, requesting-agent operator context, and any current explicitly pinned user-level policy snapshot go to TypeSafe's System One API (api.typesafe.ai) by default. The fixed policy paths are re-read on each classification only to verify the pin; changed bytes are never interpreted as policy. /classifier trust-policy reports paths, hashes, and byte lengths; /classifier status shows only the pin hash. An endpoint judgeBackend sends the same judge state to that endpoint instead: it is the operator's own server, with its own logging and retention. TypeSafe's logging and retention policies apply to the default path. Command text is sent as written and may contain private paths, proprietary snippets, inline env assignments, or secrets; secret-shaped values in evidence and policy contents are redacted before they leave the machine. Caller-supplied env values are never sent; that path asks the human instead.
bun install
bun test # static gate, Jev verdict derivation, cache keying, fail-closed paths
bun run typecheck # against pinned published host typesCI runs both on every push and PR. Verdict quality against live Jev is evaluated separately (eval/, tracked in issue #2).
The replay-aware harness ships a deterministic 500-action held-out benign set (25 task sequences × 20 routine steps), with task ids and step ids in each record. It scores the gate against live Jev by default, and can be pointed at a candidate policy instead of the shipped one:
bun eval/run.ts --corpus heldout --samples 3 # live Jev, default policy
bun eval/run.ts --policy eval/policies/x.json # score a candidate policy
bun eval/run.ts --replay # cache only: no API callsReports include final host handoff, review/recovery counts, approval overrides, interruption rates, and p50/p95 latency. Treat generated held-out numbers as a repeatable regression fixture, not as a substitute for fresh production history.
The jev-v3 shadow has its own report, over the live decision log:
bun eval/live-report.ts --hours 168 # live outcome vs jev-v3, by branch
bun eval/live-report.ts --hours 168 --counts-only # the same with no command texteval/weekly-report.sh runs it every Monday through a launchd agent
(eval/launchd/, install steps in the plist) and posts the counts to the
shadow-week issue. The full report stays in ~/.omp/omp-classifier/.
The routine recognizer from #34 is measurement-only, and its measurement is a report of its own: the share of a corpus it can prove inert, where the rest of the volume goes, and what the decision log says happened to every row it would have cleared. It clears 1.2% of the mined history's volume against the issue's
30% gate, so nothing calls it from the gate.
bun eval/mine-history.ts # rebuild the gate corpus
bun eval/recognizer-measure.ts # measure it, both variants
bun eval/recognizer-measure.ts --corpus eval/corpus/adversarial.jsonl --rowsMIT licensed.