Chapter 7: The Noul Primitive: Yes/No with Real Probabilities
Learning Objectives
Explain what a Noul truth probability between 0 and 1 represents
Use optional criteria to clarify what yes and no mean
Compose several Nouls as independent signals for a larger decision
Select thresholds for acting on a Noul based on risk
Pre-Quiz: What a Noul Is
What does the number returned by a Noul actually represent?
The probability that the statement in instructions is true
A confidence score describing how certain Jev is in a separate Choice answer
A severity rating on a 0-10 scale rescaled to 0-1
The percentage of similar past cases that were true
You write a Noul instruction as "The interface is stable" and plan to treat any value at or above 0.7 as "this interface is flapping." What is wrong with this design?
Nothing — 0.7 is a reasonable threshold for flapping detection
The instruction's direction is inverted, so a high probability means "not flapping," making the threshold backwards
The instruction should have used criteria instead of instructions
Flapping should be a Score primitive, not a Noul
A Noul returns 0.5 for "Does this Splunk event indicate a routing loop?" What is the recommended response?
Treat it as a weak "yes" and log it at low severity
Average it with other signals so it contributes half-weight to a composite score
Route it to a human, and consider whether the question or the state needs fixing
Treat it as a "no" since 0.5 is below the classic action threshold of 0.7
What a Noul Is
Key Points
A Noul returns a single number 0-1: the probability that the statement in instructions is true — no label, no rubric, no separate confidence field.
Write instructions so a HIGH probability always means YES; getting the direction backward silently breaks your thresholds.
0.5 means genuine ambiguity — equal odds of yes and no — not a "medium" score. Route it to a human instead of averaging it in.
This book standardizes the review floor at 0.35 everywhere, even though the published cookbooks disagree slightly (0.35 vs. 0.30) — the gap isn't meaningful, and one consistent number keeps the figures and the code in agreement.
A Noul is a route-map match for text that isn't a prefix: a binary answer to messy input, with a number attached for how clean the match was; measured repeat-call variation (mean standard deviation ~0.0102) is small enough to threshold on reliably.
A Probability That a Statement Is True, From 0 to 1
A Noul is the TypeSafe primitive for yes/no questions. You hand Jev a statement or a question about your state, and it returns a single number between 0 and 1: the probability that the answer is yes. That is the whole answer — there is no label, no rubric, no prose explanation, just a truth probability.
A Noul question has three parts: type set to "noul", a required instructions field holding the yes/no question, and an optional criteria field clarifying what true and false mean. In Python, the SDK class supplies type for you:
from typesafe_sdk import Noul, TypeSafeClient
client = TypeSafeClient()
response = client.system_one(
state=syslog_line,
questions={
"is_flap": Noul(
instructions="The message reports an interface repeatedly changing state",
),
},
)
print(response.answers["is_flap"].noul) # e.g. 0.94
The .noul attribute is the probability of yes. Phrase instructions so that a high probability unambiguously means "yes." If you write "The interface is stable" and then threshold at 0.7 to mean "flapping," you have built a bug into your control flow that no amount of testing the model will find.
Reach for a Noul whenever the judgment is genuinely binary: does this configuration contain a telnet line, does this Splunk alert describe a customer-facing outage, does this change ticket name a rollback owner. Reach for Choice instead when the answer is one of several named options, and for Score when the answer is a position on a spectrum. All three primitives can be evaluated in parallel against the same state in one request.
0.5 Means Equal Uncertainty, Not a Medium Value
This is the single most important thing to internalize about the Noul, and it is where engineers coming from monitoring thresholds get burned.
A Score answer returns an expected score plus a separate confidence value, and a Choice answer returns a selected label plus confidence and a probabilities distribution across labels. A Noul returns neither — only the noul field, with no separate confidence calculation. The probability itself encodes both direction and certainty, which is why a value of 0.5 does not mean "medium skill" or "medium anything."
Think of a QoS DSCP marking. AF31 is not "half of EF" — it is a different class with different meaning. A Noul of 0.5 is not "half true" or "medium severity." It means Jev assigns roughly equal probability to yes and no — the evidence in your state genuinely does not settle the question. The right response to 0.5 is almost never "treat it as a moderate hit." It is "a human needs to look at this, or my question was badly written, or my state was missing the field that would decide it."
Noul value
What it means
Typical NOC action
0.05
Strong no. The evidence clearly contradicts the statement.
Treat as false. Suppress the check, close the finding, move on without a note.
0.30
Probably no, but not clean. Some signal points the other way.
Treat as false for automation, but log the value. Sits just below the 0.35 review floor.
0.50
Equal probability for yes and no — genuine ambiguity, not a mid-level score.
Do not act automatically. Route to a human, and inspect whether the question or the state is the problem.
0.70
Probably yes. Enough to act on for low-risk and notify-only actions.
Common action threshold: open or enrich a ServiceNow ticket, raise severity, page the on-call for a notify-class event.
0.95
Strong yes. The evidence clearly supports the statement.
Treat as true. Safe to drive automated remediation if the action class also permits it.
Those cut points are not arbitrary. The TypeSafe guardrails cookbook builds its screening pipeline on a review threshold around 0.35 that sends a case to human review, and an action threshold around 0.70 that triggers the configured automatic response. The consistency cookbook describes the same idea with its lower edge at 0.30. The gap between 0.30 and 0.35 is not meaningful — both are saying "below roughly a third, stop looking." This book uses 0.35 as the review floor throughout, so that the number in the figures matches the number in the code, and both cut points get scaled to network risk later in this chapter.
Figure 7.1: The Noul truth probability scale, with the neutral band and review/action thresholds
flowchart LR
A["Below 0.35: Strong No"] --> B["0.35 to 0.69: Neutral Band - Route to Human Review"]
B --> C["0.70 to 0.94: Probably Yes - Act on Low Risk"]
C --> D["0.95 to 1.0: Strong Yes - Safe for Automated Action"]
Visual animation — coming soon
Analogy: A Route-Map Match With a Confidence Attached
Network engineers already think in binaries with a fuzzy edge, so the Noul has a natural analogy. A BGP prefix either matches a route-map clause or it does not — 10.1.0.0/16 either falls inside the prefix-list or it falls outside. There is no 0.7 match; the routing table is deterministic, and that determinism is exactly what makes it safe to build policy on.
Now imagine the same match, but the thing being matched is not a prefix. It is a sentence in a change ticket, or a paragraph of vendor release notes, or a free-text incident description typed by a field tech at 2 a.m. The question is still perfectly binary — "does this describe a hardware fault?" — but the input is not. A Noul gives you the route-map answer you wanted, with an honest label on how clean the match was.
That extra number changes what you are allowed to build. A regex-based syslog rule either fires or it does not, and when a message is worded slightly differently than your pattern expected, it silently does not fire. A Noul distinguishes three cases that a regex collapses into two: a clear match (0.95), a clear miss (0.05), and a message your rule genuinely cannot classify (0.5). That third case is where NOC automation historically fails quietly, and where a Noul lets you fail loudly and hand the case to a person instead.
The consistency of those numbers is what makes them usable as control-flow inputs. TypeSafe's consistency cookbook measures Noul stability by repeating identical calls and computing the standard deviation of each question's probability, reporting a mean standard deviation of 0.0102 against higher variation from general reasoning models. A threshold at 0.70 is only meaningful if repeated evaluations of the same syslog line cluster tightly; if the answer wandered between 0.4 and 0.9 on identical input, no threshold would hold.
Post-Quiz: What a Noul Is
What does the number returned by a Noul actually represent?
The probability that the statement in instructions is true
A confidence score describing how certain Jev is in a separate Choice answer
A severity rating on a 0-10 scale rescaled to 0-1
The percentage of similar past cases that were true
You write a Noul instruction as "The interface is stable" and plan to treat any value at or above 0.7 as "this interface is flapping." What is wrong with this design?
Nothing — 0.7 is a reasonable threshold for flapping detection
The instruction's direction is inverted, so a high probability means "not flapping," making the threshold backwards
The instruction should have used criteria instead of instructions
Flapping should be a Score primitive, not a Noul
A Noul returns 0.5 for "Does this Splunk event indicate a routing loop?" What is the recommended response?
Treat it as a weak "yes" and log it at low severity
Average it with other signals so it contributes half-weight to a composite score
Route it to a human, and consider whether the question or the state needs fixing
Treat it as a "no" since 0.5 is below the classic action threshold of 0.7
Pre-Quiz: Clarifying Yes and No
When should you add a criteria field to a Noul question?
Always, for every Noul you write, regardless of the question
Only when using the Python SDK, never with raw JSON requests
When the yes/no boundary is subtle and needs an explicit operational definition
Only when combining more than three Nouls in one request
Why does the chapter say "The config does not fail to include a local fallback" is a badly worded Noul instruction?
It is too long for the model to process reliably
It contains a double negative that makes it unclear which direction a high probability points
It uses technical jargon that Jev cannot understand
It should have been phrased as a Choice question instead
The chapter recommends naming "the two cases your teammates would argue about" when writing a Noul. What is this practice meant to catch?
Cases where Jev's inference speed is too slow
Ambiguous edge cases that your wording implicitly assumes are settled but doesn't actually decide
Situations requiring a weighted sum instead of a Noul
Requests that should be split across multiple API calls for cost reasons
Clarifying Yes and No
Key Points
The optional criteria field defines what counts as true and false; use it whenever a yes/no boundary is subtle — which in network operations is most of the time.
A Noul with instructions alone is fully valid. Reach for criteria to settle a boundary, not by default.
Jev interprets literally: it answers exactly the question asked and won't import unwritten assumptions, so name the edge cases your team would argue about and settle them in the wording.
Avoid double negatives, vague words ("OK," "problem," "looks like"), and compound conditions — one positive, checkable fact per Noul.
Never ask the model to make the decision ("Should we escalate?") — ask for the fact, and let your code own the policy.
Optional Criteria: Describing What Counts as Yes and No
The instructions field carries the question. The optional criteria field carries the definitions — descriptions clarifying what true and false outcomes mean, recommended "when yes/no boundaries are subtle."
Subtle boundaries are the normal case in network operations. "Does this config disable local authentication fallback?" is subtle, because a config can disable it for SSH and keep it for console. Criteria are where you write down the operational definition your team already uses informally:
Noul(
instructions=(
"The SSH login authentication path has no local fallback if the "
"remote AAA servers are unreachable"
),
criteria={
"true": (
"The aaa authentication login ssh line lists only remote server "
"groups such as tacacs+ or radius, with no local keyword, so an "
"unreachable AAA server leaves no way to log in over SSH."
),
"false": (
"The aaa authentication login ssh line ends with the local "
"keyword, or no aaa authentication login ssh line is present at "
"all so the switch still uses its local user database."
),
},
)
That true/false shape is the documented one. Compare it with the other two primitives: Choice takes criteria as a mapping of option name to description, and Score takes it as an ordered list of level descriptions. Note also that a Noul with instructions alone is fully valid — every cookbook example builds its question dictionary from instructions only. Reach for criteria when the boundary needs spelling out, not by default.
The payoff is that the definition lives in version control instead of in a senior engineer's head. When the NOC argues about whether a maintenance-window ticket counts as "customer-affecting," that argument is resolved once, in a string, and every subsequent evaluation applies the same rule.
Stating Boundary Cases Explicitly, Because Jev Interprets Literally
Jev answers the question you asked. Literal interpretation is a feature — it is what makes the probabilities stable — but it means the model will not silently import the assumptions you did not write down.
The network equivalents are everywhere:
Does a config with no aaa authentication login ssh line at all count as "local fallback disabled"? No — the switch still authenticates locally. But your question must say so, or the model may read "no remote-plus-local line" as a hit.
Does a rollback plan that says "restore from the nightly Git backup" count as present? Your criteria decide. Industry guidance is that generic "revert the config" statements are insufficient, and every rollback step should be documented with exact command syntax, a point-in-time target, and a named owner. If that is your bar, write it into the criteria.
Does a single interface transition count as a flap? A %LINK-3-UPDOWN followed by a %LINEPROTO-5-UPDOWN is one normal interface transition, and the two messages routinely occur together. If your question does not say "repeatedly," you will get a high probability on every clean link-up.
A useful discipline: for every Noul you write, name the two cases your teammates would argue about, and make sure the instruction or criteria decides both. If you cannot decide them, that is a sign the judgment is really a Choice with three options, one of which is "unclear."
Avoiding Double Negatives and Indirection
Ambiguous questions reduce the interpretability of the probability. In practice the ambiguity almost always comes from one of four sources: negation, vagueness, compounding, or a mismatch between the question's direction and the threshold in your code.
Badly worded instruction
Why it fails
Well worded instruction
"The config does not fail to include a local fallback."
Double negative. A high probability now means fallback is present — the opposite of the risk you are screening for.
"The SSH login authentication path has no local fallback if the remote AAA servers are unreachable."
"Is this config OK?"
Vague. "OK" has no definition, so the probability reflects the model's guess at your standard, not your standard.
"The running configuration contains at least one vty line with transport input telnet."
"The change is high risk and lacks a rollback plan."
Compound. Two independent facts share one number, and a 0.5 could mean either half is true.
Two separate Nouls: one for blast radius, one for rollback plan presence.
"Should we escalate this alert?"
Asks for a decision, not a fact. The escalation policy belongs in your code, not in the model.
"The event describes a forwarding loop between two or more routers."
"The device is not unreachable."
Negation plus indirection. Every reader has to translate it twice.
"The device responded to the most recent polling attempt."
"This looks like a routing problem."
Hedged and unbounded. "Looks like" and "problem" are undefined, so repeated runs drift.
"The event indicates packets are traversing the same set of routers repeatedly before their TTL expires."
The pattern behind every fix is the same: state a checkable fact, in the positive, one fact per question, and let your code own the policy. The model supplies the observation; the if statement supplies the decision.
Visual animation — coming soon
Post-Quiz: Clarifying Yes and No
When should you add a criteria field to a Noul question?
Always, for every Noul you write, regardless of the question
Only when using the Python SDK, never with raw JSON requests
When the yes/no boundary is subtle and needs an explicit operational definition
Only when combining more than three Nouls in one request
Why does the chapter say "The config does not fail to include a local fallback" is a badly worded Noul instruction?
It is too long for the model to process reliably
It contains a double negative that makes it unclear which direction a high probability points
It uses technical jargon that Jev cannot understand
It should have been phrased as a Choice question instead
The chapter recommends naming "the two cases your teammates would argue about" when writing a Noul. What is this practice meant to catch?
Cases where Jev's inference speed is too slow
Ambiguous edge cases that your wording implicitly assumes are settled but doesn't actually decide
Situations requiring a weighted sum instead of a Noul
Requests that should be split across multiple API calls for cost reasons
Pre-Quiz: Nouls as Building-Block Signals
Why does the guardrails cookbook screen a message with four Noul questions plus a Score in a single request, rather than sending several separate requests?
Because Jev requires at least five questions per call to function
Because the token cost is dominated by the state, so evaluating more questions against the same state in one request is far cheaper than one request per question
Because Noul and Score answers cannot be returned in separate requests
Because only one primitive type is allowed per TypeSafeClient instance
In the Aruba lockout-risk function, why does the chapter use boolean composition (and/not) rather than a weighted sum?
Because weighted sums cannot be implemented in Python
Because the lockout risk requires all three specific conditions to hold simultaneously, and a weighted sum could let two strong signals outvote a missing third
Because boolean composition is always more accurate than weighted sums
Because the Aruba documentation requires the use of and specifically
Why does the chapter recommend a much higher action threshold (0.95 plus corroboration) for destructive actions like shutting an interface, compared to 0.50 for adding a ticket note?
Because destructive actions require a second independent signal in addition to a very high probability, since the cost of being wrong is far higher
Because the model is measurably less accurate on destructive-action questions
Because 0.95 is the maximum value a Noul can return for high-risk questions
Because syslog severity levels cap out at 0.95 for critical events
Nouls as Building-Block Signals
Key Points
Ask many small Nouls against the same state in a single request; token cost is dominated by the state, so adding another check costs far less than a whole new request.
Boolean composition (threshold each Noul, then and/or/not) fits when a specific combination of facts defines the finding.
Weighted sums fit when you're accumulating concern rather than testing a logical rule — do the arithmetic in your own code, not in the model.
Always carry the individual probabilities into the ticket alongside any composite score, so a human can see which signal actually fired.
Thresholds are a policy decision scaled to blast radius: 0.50 for a read-only ticket note, up through 0.95 plus a second independent signal before anything destructive.
Asking Many Nouls in One Request
A single Noul is rarely the interesting object. The interesting object is a battery of them evaluated against the same state in one call, each one a signal that your code weighs. The TypeSafe guardrails cookbook is built exactly this way: it screens a message with one request carrying four Noul questions — jailbreak attempt, harmful request, medical advice, self-harm signal — plus a Score rating how much harm complying would do.
Swap hazards for misconfigurations and you have a config compliance scanner:
from typesafe_sdk import Noul, TypeSafeClient
HARDENING_CHECKS = {
"telnet_enabled": "The configuration permits telnet for management access",
"weak_snmp": "The configuration contains a default SNMP community string such as public or private",
"no_remote_syslog": "The configuration sends no logs to a remote syslog server",
"no_ntp": "The configuration defines no NTP server",
"mgmt_in_data_vrf": "SSH is bound to the default VRF rather than a dedicated management VRF",
}
client = TypeSafeClient()
response = client.system_one(
state=running_config,
questions={
key: Noul(instructions=question)
for key, question in HARDENING_CHECKS.items()
},
)
findings = {key: answer.noul for key, answer in response.nouls.items()}
Every check there comes from published hardening guidance. Telnet transmits credentials in cleartext and should be replaced with SSHv2, and default SNMP communities allow unauthorized reconnaissance and configuration retrieval. On AOS-CX, device-local logs are lost if the device is compromised or reboots, clock skew from a missing NTP server breaks authentication timestamps and log correlation, and binding SSH to vrf default instead of vrf mgmt leaves the management plane reachable from data VLANs.
The response.nouls collection holds the yes/no answers keyed by question name, alongside response.choices and response.scores for the other primitives and response.answers for all of them together. Because one request carries the whole battery, the token cost is dominated by the state — the config you sent once — not by the number of questions you asked about it.
Combining Nouls in Code: Boolean Logic and Weighted Sums
Once you have a dictionary of probabilities, you combine them. There are two idioms, and they are good at different things.
Boolean composition thresholds each Noul into a hard true/false and then applies ordinary logic. Use this when the rule you are encoding is genuinely logical — when a specific combination of facts, not an accumulation of concern, is what matters.
def is_lockout_risk(nouls: dict[str, float]) -> bool:
"""Remote AAA with no local fallback and no fail-through is a lockout risk."""
no_local_fallback = nouls["no_local_fallback"] >= 0.70
remote_aaa_configured = nouls["remote_aaa_configured"] >= 0.70
fail_through_set = nouls["authorization_fail_through"] >= 0.70
return remote_aaa_configured and no_local_fallback and not fail_through_set
That function encodes a real Aruba failure mode: AOS-CX guidance is that when command authorization is configured without authorization fail-through, a remote AAA server failure leaves the device unusable, so aaa authorization allow-fail-through must be set before configuring authentication fail-through to prevent lockout. Notice that the risk requires all three conditions — the and is doing real work. A weighted sum would let two strong signals outvote the missing third and produce a finding that is not actually a lockout risk.
Figure 7.2: Combining several Nouls with boolean logic into a lockout-risk decision
flowchart TD
A{"Remote AAA Configured (>= 0.70)?"} -- No --> E["Not a Lockout Risk"]
A -- Yes --> B{"No Local Fallback (>= 0.70)?"}
B -- No --> E
B -- Yes --> C{"Authorization Fail-Through Set (>= 0.70)?"}
C -- Yes --> E
C -- No --> D["Lockout Risk: Open ServiceNow Change"]
Weighted sums keep the probabilities as numbers and blend them into one composite score. Use this when you are accumulating concern rather than testing a rule — grading a device's overall hardening posture, or ranking a queue of change tickets by how much review they deserve.
CHECK_WEIGHTS = {
"telnet_enabled": 0.30,
"weak_snmp": 0.25,
"no_local_fallback": 0.20,
"no_remote_syslog": 0.15,
"no_ntp": 0.10,
}
def hardening_risk(nouls: dict[str, float]) -> float:
"""Weighted blend of independent hardening signals, 0.0 to 1.0."""
total = sum(CHECK_WEIGHTS.values())
return sum(nouls[key] * weight for key, weight in CHECK_WEIGHTS.items()) / total
Weights are yours to set, and they should reflect operational consequence rather than how often a check fires. Telnet and default SNMP communities carry the heaviest weight above because the management plane, not the data plane, is what device compromises typically exploit.
Two warnings. First, keep the arithmetic in Python — Jev is a fast judgment model, not a calculator, and the composite score is your code's responsibility: the model supplies observations, your code supplies control flow. Second, a weighted sum hides which signal fired. Always carry the underlying probabilities forward into your ticket so the engineer who opens it sees telnet_enabled: 0.97 and not only risk: 0.61.
Thresholds Scaled to Risk: Read-Only Versus Destructive Actions
A threshold is a policy decision, not a model property. The same 0.78 probability should trigger a ServiceNow comment and should absolutely not trigger a write memory.
The base pattern comes from the guardrails cookbook: a review threshold near 0.35 that routes a case to human review, and an action threshold near 0.70 that triggers the configured automatic response, with a severity score able to override and escalate a review into a block. Don't use a single cut point at 0.5 — probabilities 0.49 and 0.51 would cause opposite actions even though both express substantial uncertainty. Define a neutral band instead, roughly 0.35 to 0.70, that routes uncertain cases to human review.
Scale that idea to the blast radius of the action your code is about to take:
Action class
Example in the NOC triage service
Act-on-yes threshold
Human-review band
Rationale
Read-only / enrichment
Add a note to a ServiceNow ticket, tag a Splunk event, set a dashboard label
0.50
none
Being wrong costs an extra sentence in a ticket. Bias toward coverage.
Routing / assignment
Assign the ticket to the routing team versus the wireless team
0.60
0.40–0.60 falls back to the general queue
A misroute costs minutes of handoff, and the neutral band has a safe default.
Notify / escalate
Raise severity, page the on-call, open a Salesforce case for a customer
0.70
0.35–0.70 goes to a review queue
Matches the cookbook's action and review thresholds. False pages erode trust fast.
Non-service-affecting config change
Push the missing local keyword onto an AAA line, add a syslog destination
0.85
everything from 0.35 to 0.85 goes to a human
A write to a production device deserves near-certainty, plus a peer review step.
Destructive / service-affecting
Shut an interface, reload a device, withdraw a BGP prefix, execute a rollback
0.95 and a second independent signal
everything below 0.95
Never let one probability take down a link. Require corroboration and an approval.
Three rules make this table safe to operate. Require corroboration for destructive actions — change-management practice for high-risk work requires a minimum of two engineers, an implementer and an observer who validates each step, precisely because a single judgment is not enough. Your automation should mirror that: a second Noul, a telemetry check, or a human click. Mind the direction of the question — if your Noul asks "the change is safe to apply automatically," you need a high probability to proceed; if it asks "the change carries customer-facing risk," you need a low probability to proceed. Confusing the two is the single most common way a threshold table gets inverted in production. Log the probability, not just the decision — six weeks later, "routing_loop came back 0.91 against a 0.70 notify threshold" is auditable, while "the AI thought it was a loop" is not.
Figure 7.3: The risk-scaled threshold ladder, from read-only actions to destructive actions
flowchart LR
A["Read-Only or Enrichment: Threshold 0.50"] --> B["Routing or Assignment: Threshold 0.60"]
B --> C["Notify or Escalate: Threshold 0.70"]
C --> D["Non-Service-Affecting Config Change: Threshold 0.85"]
D --> E["Destructive or Service-Affecting: Threshold 0.95 Plus Corroboration"]
Visual animation — coming soon
Post-Quiz: Nouls as Building-Block Signals
Why does the guardrails cookbook screen a message with four Noul questions plus a Score in a single request, rather than sending several separate requests?
Because Jev requires at least five questions per call to function
Because the token cost is dominated by the state, so evaluating more questions against the same state in one request is far cheaper than one request per question
Because Noul and Score answers cannot be returned in separate requests
Because only one primitive type is allowed per TypeSafeClient instance
In the Aruba lockout-risk function, why does the chapter use boolean composition (and/not) rather than a weighted sum?
Because weighted sums cannot be implemented in Python
Because the lockout risk requires all three specific conditions to hold simultaneously, and a weighted sum could let two strong signals outvote a missing third
Because boolean composition is always more accurate than weighted sums
Because the Aruba documentation requires the use of and specifically
Why does the chapter recommend a much higher action threshold (0.95 plus corroboration) for destructive actions like shutting an interface, compared to 0.50 for adding a ticket note?
Because destructive actions require a second independent signal in addition to a very high probability, since the cost of being wrong is far higher
Because the model is measurably less accurate on destructive-action questions
Because 0.95 is the maximum value a Noul can return for high-risk questions
Because syslog severity levels cap out at 0.95 for critical events
Pre-Quiz: Worked Examples
In the Aruba example, why is authorization_fail_through checked with a LOW-probability comparison (below the review floor) rather than a HIGH-probability comparison like the other two checks?
Because the underlying question was written so a HIGH probability confirms fail-through IS enabled, so proving it is ABSENT requires a LOW probability
Because fail-through is inherently harder for Jev to detect than the other two settings
Because inverted checks are the only case where TypeSafe requires a criteria field
Because the SDK returns fail-through checks as a Choice object instead of a Noul
What role does the maintenance_expected Noul play in the Splunk routing-loop detection code?
It replaces the need for the other three Noul checks entirely
It acts as a suppressor: the page only fires when this probability is low, confirming the activity is not expected maintenance
It is averaged together with the other three signals into a single composite score
It sets the actual severity level passed to page_oncall
In the change-request example, rollback_present returns 0.93 but rollback_executable returns 0.06. What does this pairing demonstrate?
That the Noul battery is unreliable and produced a contradictory result
That a rollback section can exist in the ticket text while being operationally worthless — a distinction a keyword search for "rollback" cannot make
That rollback_present should have been weighted more heavily than rollback_executable
That the two checks should be merged into a single Noul to avoid confusion
Worked Examples
Key Points
Config compliance is a natural Noul battery: each hardening rule is an independent yes/no fact about the same config text that a grep can't safely express.
Splunk correlation: several individually-ambiguous signals become a confident routing-loop finding when counted together; a "maintenance_expected" suppressor Noul prevents the most common false page.
Counting how many independent signals crossed threshold is safer than averaging them — one strong 0.99 shouldn't drag a weak composite over the line.
A ServiceNow change ticket can have a rollback "section" that's completely unexecutable; splitting "has a rollback plan" into presence, executability, restore point, owner, and out-of-band access exposes what a keyword search misses.
All three worked examples share the same shape: ask several small honest questions, combine them in code, and act in proportion to what you could break.
Example 1: Does This Aruba Config Disable Local Authentication Fallback?
A NOC compliance job pulls running configurations nightly from the Aruba AOS-CX access layer. One switch returns this fragment:
aaa authentication login ssh group tacacs+
aaa authentication login console group tacacs+ local
aaa authorization commands ssh group tacacs+
ssh server vrf mgmt
Read it the way an engineer reads it. The console path ends in local, so someone at the switch with a serial cable can still log in when TACACS+ is down. The SSH path does not. Both problems are present, and neither is caught by a grep for "tacacs" — the string is on every line.
from typesafe_sdk import Noul, TypeSafeClient
client = TypeSafeClient()
aruba_config = """aaa authentication login ssh group tacacs+
aaa authentication login console group tacacs+ local
aaa authorization commands ssh group tacacs+
ssh server vrf mgmt"""
response = client.system_one(
state=aruba_config,
questions={
"no_local_fallback": Noul(
instructions=(
"The SSH login authentication path has no local fallback, so an "
"unreachable TACACS+ or RADIUS server would block SSH logins"
),
criteria={
"true": (
"The aaa authentication login ssh line lists only remote "
"server groups and does not end with the local keyword."
),
"false": (
"The aaa authentication login ssh line ends with local, or "
"there is no aaa authentication login ssh line at all so the "
"switch still uses its local user database."
),
},
),
"remote_aaa_configured": Noul(
instructions="The configuration authenticates logins against a remote TACACS+ or RADIUS server group",
),
"authorization_fail_through": Noul(
instructions="The configuration enables authorization fail-through so AAA server failure does not lock operators out",
),
},
)
nouls = {key: answer.noul for key, answer in response.nouls.items()}
# {'no_local_fallback': 0.96, 'remote_aaa_configured': 0.98,
# 'authorization_fail_through': 0.04}
The probabilities shown in the comment are illustrative of a clean case; your own values will vary with the config you send. What matters is the shape of the decision built on top of them:
LOCKOUT_ACTION_THRESHOLD = 0.85 # config-change class
REVIEW_FLOOR = 0.35 # standardized review floor throughout this book
if (nouls["remote_aaa_configured"] >= 0.70
and nouls["no_local_fallback"] >= LOCKOUT_ACTION_THRESHOLD
and nouls["authorization_fail_through"] < REVIEW_FLOOR):
open_servicenow_change(
short_description="AOS-CX lockout risk: SSH AAA has no local fallback",
risk="high",
evidence=nouls,
)
Three choices there are deliberate. The no_local_fallback check uses the 0.85 config-change threshold because the remediation writes to a production switch. The authorization_fail_through check is inverted — we want a low probability to confirm the setting is absent — and it is compared against the review floor rather than the action threshold, because a mid-range value there means "I could not tell," which should not silently pass. And the whole nouls dictionary rides into the ticket, so the engineer who picks it up sees each signal rather than a verdict.
Check the false case too. A switch with aaa authentication login ssh group tacacs+ local should produce a low no_local_fallback probability, and so should a switch with no AAA configuration at all — because the criteria explicitly say so. That second case is the boundary the criteria exist to settle.
Example 2: Does This Splunk Event Indicate a Routing Loop?
Splunk fires a webhook to the triage service when a saved search matches. The payload carries result with the first matching event row, plus sid, results_link, search_name, owner, and app.
Two independent facts point at a loop. The traceroute alternates between the same two next hops, and the %OSPF-5-ADJCHG messages show the same neighbor cycling between FULL and DOWN within ten seconds. Either alone is ambiguous: a TTL-exceeded spike can come from a traceroute-heavy monitoring host, and a single adjacency reset is routine. Together they are a loop — exactly the structure a Noul battery plus boolean composition handles well.
LOOP_CHECKS = {
"ttl_expiry_spike": (
"The event reports an elevated rate of packets whose TTL expired in transit"
),
"repeating_hops": (
"The traceroute output shows the same router addresses appearing more than "
"once in an alternating pattern"
),
"adjacency_churn": (
"The syslog excerpt shows a routing adjacency with the same neighbor going "
"down and coming back up more than once"
),
"maintenance_expected": (
"The event text states that this activity is expected during a planned "
"maintenance window"
),
}
response = client.system_one(
state=webhook_payload["result"],
questions={key: Noul(instructions=q) for key, q in LOOP_CHECKS.items()},
)
n = {key: answer.noul for key, answer in response.nouls.items()}
# {'ttl_expiry_spike': 0.97, 'repeating_hops': 0.95,
# 'adjacency_churn': 0.93, 'maintenance_expected': 0.02}
NOTIFY = 0.70 # notify/escalate class
REVIEW_FLOOR = 0.35 # below this, stop looking
forwarding_evidence = sum(
1 for key in ("ttl_expiry_spike", "repeating_hops", "adjacency_churn")
if n[key] >= NOTIFY
)
if forwarding_evidence >= 2 and n["maintenance_expected"] < REVIEW_FLOOR:
page_oncall(team="routing", severity=2, evidence=n,
splunk_link=webhook_payload["results_link"])
elif forwarding_evidence >= 1:
queue_for_human_review(evidence=n,
splunk_link=webhook_payload["results_link"])
The forwarding_evidence >= 2 test is the corroboration rule from the threshold table, expressed as a count of independent signals rather than an average — averaging would let a single 0.99 drag a composite over the line. The maintenance_expected Noul is a suppressor and runs in the opposite direction: we proceed only when we are confident this is not expected work. Because page_oncall receives the whole n dictionary and the results_link, the engineer who wakes up gets both the probabilities and one click back to the raw Splunk search.
Example 3: Is This Change Request Missing a Rollback Plan?
The last example runs against text a human wrote, which is where Nouls handle inputs that no parser could. A ServiceNow change request arrives for CAB review:
CHG0041892 — Migrate core uplink from Gi0/0/1 to Te0/0/3 (dc1-core-01)
Requested by: j.okafor Window: 2026-09-20 02:00-04:00 UTC Risk: Medium
Description:
Move the northbound uplink from the 1G copper interface to the new 10G optic on
Te0/0/3. New optic is installed and shows light. BGP session to the upstream
carrier will be rebuilt on the new interface with the same peer IP and ASN.
Implementation steps:
1. Shut Gi0/0/1
2. Configure Te0/0/3 with the existing uplink IP and description
3. Move the BGP neighbor statement to the new interface
4. Verify BGP session comes up and full table is received
5. Save config
Backout: If there are problems we will put it back the way it was.
Validation: Confirm the carrier session is established.
This ticket has a backout line, so a keyword search for "backout" or "rollback" marks it complete. It is not. Industry guidance is direct: generic "revert the config" statements are insufficient, and a real rollback plan names a point-in-time target, a step-by-step command sequence, an owner with console access, validation criteria, an estimated duration, and the backup location. The ticket also never mentions out-of-band access, which matters here — step 1 shuts the interface the engineer may be reaching the router through.
CHANGE_CHECKS = {
"rollback_present": "The request contains a backout or rollback section of any kind",
"rollback_executable": (
"The rollback description lists the specific commands or configuration "
"restore steps an engineer would run, rather than a general statement of "
"intent to revert"
),
"restore_point_named": (
"The request identifies the specific saved configuration or point in time "
"that a rollback would restore"
),
"rollback_owner_named": "The request names the person responsible for executing a rollback",
"oob_access_stated": (
"The request states that console or out-of-band management access is "
"available during the change window"
),
"validation_measurable": (
"The post-change validation steps state measurable pass criteria such as "
"expected route counts, convergence times, or interface error counters"
),
}
response = client.system_one(
state=change_request_text,
questions={key: Noul(instructions=q) for key, q in CHANGE_CHECKS.items()},
)
c = {key: answer.noul for key, answer in response.nouls.items()}
# {'rollback_present': 0.93, 'rollback_executable': 0.06,
# 'restore_point_named': 0.03, 'rollback_owner_named': 0.05,
# 'oob_access_stated': 0.04, 'validation_measurable': 0.11}
rollback_present at 0.93 and rollback_executable at 0.06 is the exact pattern that keyword matching cannot see: the section exists, and it is worthless. Now grade the ticket with a weighted sum, because here we genuinely are accumulating concern rather than testing a logical rule:
READINESS_WEIGHTS = {
"rollback_executable": 0.30,
"restore_point_named": 0.20,
"rollback_owner_named": 0.15,
"oob_access_stated": 0.20,
"validation_measurable": 0.15,
}
def readiness(c: dict[str, float]) -> float:
total = sum(READINESS_WEIGHTS.values())
return sum(c[k] * w for k, w in READINESS_WEIGHTS.items()) / total
score = readiness(c) # ~0.06
if score < 0.35:
reject_to_requester(
reason="Rollback plan is not executable as written",
gaps=[k for k, v in c.items() if v < 0.35 and k != "rollback_present"],
evidence=c,
)
elif score < 0.70:
route_to_cab(evidence=c)
else:
auto_approve_standard_change(evidence=c)
The three bands are the review and action thresholds again, applied to a composite. Note what the automation does not do: it never approves a change on its own judgment alone — the top branch applies only to changes already classified as standard, and everything ambiguous goes to the Change Advisory Board. The gaps list turns the rejection into actionable feedback: add a restore point, an owner, and out-of-band confirmation.
Figure 7.4: Worked example — the change-request rollback check, from submission to CAB routing
sequenceDiagram
participant SN as ServiceNow Change Request
participant Noul as Noul Battery
participant Score as Readiness Scorer
participant CAB as Change Advisory Board
participant Req as Requester
SN->>Noul: Send change request text as state
Noul-->>Score: Return six check probabilities
Score->>Score: Compute weighted readiness score
alt Score below 0.35
Score->>Req: Reject with list of failing checks
else Score between 0.35 and 0.70
Score->>CAB: Route for manual review
else Score 0.70 or above
Score->>SN: Auto-approve as standard change
end
The payoff is measurable: performing both pre-change and post-change validation is associated with change-related incidents dropping by 70–80%. The mechanisms that make a restore point meaningful also differ by vendor — Junos offers atomic rollback 1 and commit confirmed, Arista offers abandonable configuration sessions, AOS-CX offers checkpoints, and IOS requires configure replace or manual reversal. A rollback plan that does not name which one it uses is a plan nobody can execute at 3 a.m.
Visual animation — coming soon
Chapter Summary
The Noul is the simplest TypeSafe primitive and the one most likely to be misread. It answers a yes/no question with a single number between 0 and 1 — the probability that the answer is yes — and that number carries both the direction of the judgment and the certainty behind it, with no separate confidence field to consult. A 0.5 is not a medium finding; it is a declaration that the state does not settle the question. Because the probability is what you threshold on, the phrasing rule matters more than anything else in this chapter: write instructions as a single positive checkable fact so that high always means yes, and use the optional criteria to write down the boundary cases your team would otherwise argue about.
The power of the primitive shows up in composition, not in isolation. A battery of small Nouls evaluated against one state in one request gives you a set of independent signals, and your code decides what to do with them. Boolean composition fits when a specific combination of facts defines the finding; a weighted sum fits when you are accumulating concern. Either way, the arithmetic belongs in Python, the individual probabilities travel with the decision into the ticket, and the model never owns the control flow.
Thresholds belong to the action rather than to the model. The published pattern of a review band near 0.35 and an action threshold near 0.70 is a starting point that you stretch or shrink according to blast radius: 0.50 for adding a note to a ServiceNow ticket, 0.70 for paging the on-call, 0.85 for writing a line to a production switch, and 0.95 plus an independent corroborating signal before anything that shuts an interface or reloads a device. Define a neutral band so that 0.49 and 0.51 do not trigger opposite actions, log the raw probability next to every decision for audit, and never let one number take down a link.
One thing this chapter deliberately deferred is the other half of the picture: Choice and Score answers carry a separate confidence field that a Noul does not, and that field has its own thresholds, its own failure modes, and its own tuning discipline. The next chapter takes it up in full.
Post-Quiz: Worked Examples
In the Aruba example, why is authorization_fail_through checked with a LOW-probability comparison (below the review floor) rather than a HIGH-probability comparison like the other two checks?
Because the underlying question was written so a HIGH probability confirms fail-through IS enabled, so proving it is ABSENT requires a LOW probability
Because fail-through is inherently harder for Jev to detect than the other two settings
Because inverted checks are the only case where TypeSafe requires a criteria field
Because the SDK returns fail-through checks as a Choice object instead of a Noul
What role does the maintenance_expected Noul play in the Splunk routing-loop detection code?
It replaces the need for the other three Noul checks entirely
It acts as a suppressor: the page only fires when this probability is low, confirming the activity is not expected maintenance
It is averaged together with the other three signals into a single composite score
It sets the actual severity level passed to page_oncall
In the change-request example, rollback_present returns 0.93 but rollback_executable returns 0.06. What does this pairing demonstrate?
That the Noul battery is unreliable and produced a contradictory result
That a rollback section can exist in the ticket text while being operationally worthless — a distinction a keyword search for "rollback" cannot make
That rollback_present should have been weighted more heavily than rollback_executable
That the two checks should be merged into a single Noul to avoid confusion