Saturday, October 31, 2026

When Nutritious Food Is Not Yet Usable

A qualitative pilot study published in Foods on 1 October 2026 examines perceived barriers to micronutrient-rich diets among people using charitable services in two European settings. Its value is not a new estimate of European food insecurity. It is a closer look at what people say gets in the way between food being available and a varied, nutritious diet being possible.

Affordability is one of those barriers. The paper reports participants describing substitutions and compromises: choosing cheaper foods instead of meat or fish, skipping meals or reducing portions, drinking water, and repeating the same meals to reduce cost. These accounts show why a food budget cannot be judged only by whether it covers some calories. It also determines variety, and variety is often the first thing to disappear.

But the study does not reduce the problem to price. Its themes include personal preferences and restrictions, cooking practices, family and social context, cultural fit, and mental well-being. Food assistance can therefore be present without being fully usable. An item that cannot be prepared with the available facilities, does not fit a household's practices, or is not acceptable to the people eating it is not equivalent to practical access. The paper is useful precisely because it keeps those conditions together instead of treating them as separate inconveniences.

Participants also talked about food quality through ideas such as naturalness, taste, freshness, safety, processing, storage, and perceived micronutrient content. Those beliefs matter because they can shape choices and trust. They are not, however, measurements of nutrient content. The study records how people understand and navigate food; it does not establish micronutrient deficiency, measure dietary intake, or show that one belief causes a health outcome.

The authors consequently argue for context-sensitive responses across food assistance, social protection, public health, organisational provision, and nutrition communication. That is a broader design implication, not the result of a successful intervention trial. The paper does not tell us how common each barrier is across Europe, nor which programme would remove it most effectively.

Read on its own terms, the pilot supplies a practical correction to the phrase “access to food.” Access has logistical and social steps: obtaining food, carrying it, storing it, preparing it, recognising it as suitable, and having enough choice to avoid the cheapest option every time. A parcel or menu can look adequate in an inventory while leaving several of those steps unresolved.

Source: “Perceived Barriers to Micronutrient-Rich Diets Among Charitable-Service Users in Two European Settings: A Qualitative Pilot Study,” Foods, 1 October 2026.

Friday, October 30, 2026

MMX Has Launched. The Phobos Work Is Still Ahead

The Martian Moons eXploration (MMX) mission has left the launch pad. The European Space Agency reports that the JAXA-led spacecraft lifted off from Tanegashima on an H3 rocket at 04:41 JST on 20 October 2026.

That completed milestone is easy to blur with the mission's more striking promises. The reviewed pages do not report that MMX has reached Mars, orbited Phobos, collected a sample, or returned one to Earth. Those are future stages. The ESA launch report describes a collaboration led by JAXA with NASA, CNES, DLR and ESA. ESA supplied deep-space communication equipment and ground-station support, while European scientists appointed by ESA participate in the science team.

Two moons, one planned sample

MMX's stated mission is to explore both Phobos and Deimos, but the sample-return target is Phobos. That distinction is central. The ESA factsheet lays out an approximately year-long journey to Mars, followed by a planned three years orbiting Phobos, gathering scientific data and collecting material from its surface. The spacecraft is then scheduled to return the sample to Earth in Japanese fiscal year 2031.

So this is not a report of a sample already in hand. It is a report of a mission beginning the long sequence required to obtain one. The launch page describes MMX as aiming to become the first spacecraft to orbit Phobos, collect a sample from its surface and return to Earth from the Mars system. The factsheet presents those steps as a plan, not as completed results.

Why the status matters

The launch report establishes a successful departure. It does not establish arrival at Mars, a successful Phobos operation, sample acquisition or sample return. Those milestones remain ahead, and the source material is careful—if easy for a headline to make less so—about that boundary.

For now, MMX is best understood as a JAXA-led international mission whose most consequential evidence has not yet been collected. What has happened is the departure. The interesting part is whether the spacecraft can carry the rest of the sequence through to Phobos and back.

Sources: ESA launch report and ESA MMX factsheet.

Sunday, October 25, 2026

Strict Batching Is Not a Transaction

Suppose a pipeline has seven records and groups them in threes. Ordinary batching returns two full batches and a final batch containing one record. That last batch may be acceptable, or it may show that the input is incomplete.

Python's itertools.batched offers a strict=True option for the second case. The name is precise, but easy to overread: it checks the shape of each batch; it does not make the work performed for earlier batches atomic.

I ran a small synthetic experiment on Python 3.14.7. The input was row-1 through row-7, with a batch size of three. Without strict mode, the result was:

['row-1', 'row-2', 'row-3']
['row-4', 'row-5', 'row-6']
['row-7']

With strict=True, the iterator yielded the first two complete batches and then raised ValueError: batched(): incomplete batch when it reached the final record. The six records in those complete batches had already been handed to the loop body.

That distinction matters if the loop does something irreversible after each yield: sends a message, writes a file, or commits a group of rows. The later exception reports a malformed tail, but it does not retract the earlier effects. In the experiment, a synthetic per-batch write list contained six records when the error arrived.

There are three different policies hiding behind the word “batch.” Accept a short final batch and process it. Reject an incomplete input with strict=True, while accepting that earlier work may already have happened. Or validate and stage the complete input before performing external effects, using a transaction or a deliberate compensation strategy where the operation must be all-or-nothing.

The experiment is deliberately small and uses no product, database, or network. It does not establish the atomicity of any particular consumer. It establishes a narrower boundary visible in this interpreter: strict batching detected the incomplete tail, but the detection point came after two complete batches had been yielded.

strict is a useful input check. It is not a rollback mechanism.

Saturday, October 24, 2026

The Mock That Returned the Right Data but Could Not Be Awaited

A mock can return exactly the value a test expects and still describe the wrong interface.

A bounded experiment on Python 3.14.7 used a synthetic Client with two methods: a regular send(recipient, *, urgent=False) and an async fetch(token, timeout=1.0). The test double was built with create_autospec(Client, instance=True, spec_set=True).

client = create_autospec(Client, instance=True, spec_set=True)
client.fetch.return_value = {"ok": True}

send_result = client.send("ops", urgent=True)
fetch_result = client.fetch("token-7", timeout=2.0)

The run produced a useful split. The regular method became a MagicMock and its result was not awaitable. The asynchronous method became an AsyncMock; calling it produced an awaitable, and awaiting it returned {"ok": true}. The same double also rejected client.send("ops", True) with TypeError: too many positional arguments, and rejected client.fetch() with TypeError: missing a required argument: 'token'.

The assertions had two different jobs. send.assert_called_once_with(...) checked the ordinary call. fetch.assert_awaited_once_with(...) checked that the asynchronous boundary was actually crossed. Both passed in the experiment.

For comparison, a loose MagicMock was given a fetch attribute whose return value was the same-looking dictionary. An ordinary call returned that dictionary, so a test that stopped there could look healthy. Awaiting the result failed with TypeError: 'dict' object can't be awaited. The loose mock had supplied data, but it had not supplied the contract that the caller used.

This was a constructed demonstration, not a product test or a benchmark. It used one synthetic class, Python 3.14.7 and unittest.mock; it does not prove that a real client is wired correctly or cover every autospec edge case. Its narrower result is practical: when a collaborator mixes synchronous and asynchronous methods, build the double from the interface and assert the await separately. A plausible return value is not evidence that the code under test waited at the right boundary.

Friday, October 23, 2026

The Foreign Key Was in the Schema but Not in the Connection

SQLite accepted an invalid child row even though the table definition contained a foreign key. The difference was not the schema. It was the connection.

I ran a small in-memory experiment with Python 3.14.7 and SQLite 3.53.1. Two databases created the same parent and child tables, with child.parent_id REFERENCES parent(id). Each then attempted to insert a child pointing at parent 404, which did not exist.

db.execute("PRAGMA foreign_keys").fetchone()[0]  # read the setting
db.execute("PRAGMA foreign_keys = ON")
db.execute(
    "INSERT INTO child (id, parent_id) VALUES (1, 404)"
)

The recorded results were sharply different:

  • With enforcement left alone, the connection reported 0 before and after the check. The insert committed, and the row (1, 404) was present.
  • After enabling enforcement, the setting changed from 0 to 1. The same insert raised FOREIGN KEY constraint failed, and the child table stayed empty.

A final check used two simultaneous connections. Enabling the setting on the first produced 1 there and 0 on the second. The setting was connection-local; it was not a property that the schema declaration silently imposed on every connection.

That is the practical trap. A migration can leave a perfectly readable REFERENCES clause while an application connection still accepts orphan rows. A process or connection pool therefore needs to establish the setting for each connection and read it back as part of initialization. Checking one successful connection is not enough to prove that all of them enforce the same rule.

This was a constructed in-memory demonstration, not a product test. It exercised one Python binding and SQLite build; it did not model a pool, existing orphan rows, concurrency, or another driver. It does establish the narrower result shown here: in this setup, foreign-key enforcement was runtime connection state. If SQLite is carrying relational data, treat that state as something to initialize and verify, not something to infer from the table definition.

Friday, October 16, 2026

The Booking Test That Passed the Wrong Answer

Take an existing booking [10, 20) and a requested booking [5, 25). They share ten units of time, so the request must be rejected. Yet a tempting overlap check asks only whether the new booking's start or end falls inside the existing one. Both endpoints sit outside [10, 20), so that check returns False. A booking that surrounds another has slipped through.

The boundary convention matters. These are half-open intervals: the start is included and the end is excluded. A booking ending at 20 may therefore sit next to one starting at 20 without a conflict. For positive-duration intervals, the compact rule is that each booking must start before the other one ends:

def overlaps(new_start, new_end, existing_start, existing_end):
    return new_start < existing_end and existing_start < new_end


cases = [
    ((5, 25, 10, 20), True),
    ((5, 10, 10, 20), False),
    ((20, 25, 10, 20), False),
]

for args, expected in cases:
    assert overlaps(*args) is expected

print("checked", len(cases), "cases")

This small snippet was executed as written and checked the surrounding case plus both touching boundaries. It is an illustration, not a production booking component: it does not validate inputs or address dates, time zones, storage, concurrency, or multiple existing bookings.

The more interesting failure is in the test oracle. In the bounded demonstration, a weak suite included the surrounding interval but calculated its expected answer with a copied version of the same endpoint-only rule. The deliberately faulty implementation passed all 9 of those tests. When the expected answers came instead from the written interval contract, it passed 8 of 9 and failed exactly on [5, 25). The corrected predicate passed all 9 requirement cases. It also failed the weak suite on that same case, because the weak suite expected the wrong answer.

That is why adding more examples to a circular oracle is not enough. Write the requirement table first and make its expected results literal: gaps before and after, touching at either boundary, partial overlaps, a booking contained inside another, a booking that contains the existing one, and identical intervals. Then run both the suspected implementation and the test oracle against that table. A correct implementation should not be forced to agree with a copied mistake.

For AI-assisted testing, the same order is useful: ask for the cases and expected outcomes from the stated booking rules before asking for executable tests. Review the surrounding and touching cases yourself. This demonstration used nine selected integer intervals, not a failure-rate estimate or evidence about generated code in general. It establishes one precise counterexample and a practical way to keep the specification separate from the predicate.

Thursday, October 1, 2026

The Fields a Contact Export Forgot

A deliberately constructed contact-migration test produced a result that is easy to misread: a flat CSV imported cleanly, and all three rows came back. The reconstructed contacts still differed from their originals in nine contact fields.

This was a constructed synthetic demonstration, not real product testing or an AI benchmark. The input contained three invented contacts. Each had an identifier, a name, two labelled phone numbers, two tags and a note.

What the narrow format dropped

The first export used only three columns: id, name and phone. Its exporter selected the first phone number. The matching importer recreated one unlabelled number, an empty tag list and an empty note. Names and first numbers survived. String identifiers such as 0007 kept their leading zeroes.

For each contact, the phone list, tag list and note changed. Across the three contacts, that meant three secondary numbers disappeared, all six phone labels disappeared, all six tags disappeared and all three notes disappeared. Counting imported rows would have reported success while missing every one of those losses.

The difference is in the schema, not in the comma. A representation that stores only one scalar phone value cannot preserve a list of labelled numbers. A representation that stores the list as structured data can, provided the importer understands that structure.

Here is the small self-contained comparison used for the illustrative example:

import json

contact = {
    "id": "0007",
    "phones": [{"label": "mobile", "number": "+1-202-555-0101"}],
}
flat_row = {"id": contact["id"], "phone": contact["phones"][0]["number"]}
complete_row = {
    "id": contact["id"],
    "phones_json": json.dumps(contact["phones"]),
}
print(flat_row["id"], flat_row["phone"])
print(json.loads(complete_row["phones_json"])[0]["label"])
0007 +1-202-555-0101
mobile

The full demonstration also tried a versioned JSON export and a CSV whose list fields were JSON-encoded. Both reconstructed the three synthetic contacts exactly. That result applies to those deliberately paired exporters and importers; it does not prove that another application will understand the same encoding or support every field.

Before moving an address book

  1. Create disposable test contacts with multiple labelled numbers, several tags, a multiline note and, if supported, an identifier with a leading zero.
  2. Export them and import into a separate test address book.
  3. Compare fields individually: secondary numbers, labels, tags, notes and identifier types. Do not stop at the contact count.
  4. Keep the original address book and its backup until those checks pass.

An export is a schema even when it looks like a harmless spreadsheet. The row count tells you how many records crossed the boundary; the fields tell you whether the address book did.

Keep One Strange Word

Put two translations of the same short poem side by side. In one, every sentence moves easily. In the other, a clause sounds slightly wrong: the order is unfamiliar, or a verb seems to carry more weight than the surrounding prose can comfortably hold. Most readers will reach for the smooth version first. It feels finished.

That instinct is understandable, but a translation that removes every snag can remove the choices the original forced. A translation does not transfer words like luggage from one language to another. It rebuilds decisions under different conditions. Some decisions can be made invisible. Others need to remain audible.

Suppose the source contains a verb that could mean hold, keep, or carry. The translator has to choose. “Hold” suggests an action in the present; “keep” introduces possession or refusal; “carry” gives the action duration and weight. None is a neutral replacement. The reader does not receive a dictionary entry. The reader receives a person doing something, and the chosen verb determines what that something is.

This does not make awkwardness automatically virtuous. A typo is not a mystery. Dead syntax is not depth. A joke that depends on a sound may need to be rebuilt rather than preserved literally. The useful question is not, “Does this sentence sound foreign?” It is, “What work was the difficulty doing?” If the strange order creates hesitation, that hesitation may belong in the new version. If it only blocks the meaning, solve it. If the original contains a wordplay that cannot survive, create a different pressure elsewhere or admit that something was lost.

Readers can test this without knowing the source language. Read the smooth version aloud, then return to the sentence that felt resistant in the other. Mark what changed: the pace, the actor, the direction of an action, the degree of certainty. Sometimes the awkward line is merely poor writing. Sometimes it is the only place where the translator has refused to make a decision on the reader’s behalf.

There is a genuine trade-off. A translation meant to be read by many people cannot turn every sentence into a puzzle. Smoothness is useful. The problem is unearned smoothness: prose so polished that no trace remains of the uncertainty, conflict, or double meaning that had to be resolved. Fluency can become a disguise for interpretation.

The translation worth keeping is not necessarily the one that feels most comfortable. It is the one whose difficult choices can still be located. When a sentence resists a second reading, that resistance may be a defect—or it may be evidence that the source has not been flattened into a single convenient answer. When a translated sentence becomes effortless, did it become clearer, or did someone quietly decide for you?

Wednesday, September 30, 2026

A Discount Answers Only One Question

Imagine an online cart containing a replacement filter and a notebook. The total is €47.60. The checkout page promises free delivery at €50, so you add a €4.20 cable you had not planned to buy. Nothing about the filter changed. Nothing about the notebook changed. The only new problem is that the cart is below a line drawn by the seller.

The arithmetic presented by the page is seductive: spend €4.20 and avoid a delivery charge. But the real comparison is not “buy the cable or pay for delivery.” It is “buy the cable and receive delivery” versus “keep the original order, pay for delivery, and keep €4.20.” The word free has moved the shipping cost out of one column and placed a new purchase in another. The total may be lower; it may not. More importantly, the threshold has changed what counts as the decision.

A minimum is easy to mistake for a finish line. You are no longer asking only whether an item solves a problem. You are asking how close you are to qualifying. Distance becomes evidence. A cable selected because it closes a gap can feel justified even when the gap was never yours to close. It may become useful later. That possibility is not proof that it was needed now.

Thresholds are not automatically bad. If you already need detergent next week, adding it to an order can be sensible. If the delivery charge is large compared with the original purchase, accepting the extra cost may be poor arithmetic. If the added object replaces a trip you would make anyway, the calculation changes again. The point is not to reject the offer. It is to keep the original need visible while considering it.

The checkout page does not have to prove that the cable is a good cable. It only has to make the gap noticeable. A progress bar saying “€2.40 to free delivery” turns an optional purchase into a near miss. The number creates a small unfinished shape, and the easiest way to complete it is to buy something. The screen has made its threshold feel like your task.

A simple test is to remove the last item and look at the order again. State the original problem in one sentence. Then compare the cost of solving that problem with delivery included against the cost of adding an item chosen mainly to cross the line. If the extra item has a separate use you can name, it remains a genuine candidate. If its only job is to defeat the checkout message, it is not saving money; it is changing the subject.

The same distinction applies to a sale sticker. A forty-percent reduction answers how much less an object costs than before. It does not answer whether the remaining price belongs in your plans, your storage, or your week. A lower number is useful information, but it is not a reason by itself.

Before paying, remove the last item and let the original order stand on its own. If it still makes sense with its actual delivery cost, you have a choice. If the added item is doing all the persuading, the offer has not helped you complete the purchase. It has given you a different problem: how to justify the thing you bought to reach the threshold.

Tuesday, September 29, 2026

A Photograph Without a Name

Imagine opening a cardboard box during a house move. The prints inside are dry, their edges unbent, and most have a date or a name written on the back. One shows three people standing beside a fence: a child in a striped shirt, an older woman holding a hat, and a man looking away from the camera. On the reverse there is only "July 1987." The picture has survived. The part that would let a stranger place it has not.

That missing note is not a minor defect. It changes what the photograph can do. Anyone can inspect the faces, clothing, weather, and arrangement of bodies. They cannot tell who the people were to one another, where the fence stood, or why this particular afternoon deserved a place in the box. The image remains visible, but its connections have been cut.

Preservation has two jobs. The first protects the object: keep the paper dry, the file readable, the recording intact. The second preserves enough context for another person to use what was saved. Without the second, an archive becomes a collection of survivals rather than a body of knowledge. It contains evidence, but fewer ways to ask the evidence a sensible question.

A short caption can change that. "Mara, Petru, and their neighbour at the orchard, 1987" does not explain the whole scene, and it does not need to. It gives a future reader several handles: three names, a relationship that may still need clarification, a place, and an approximate date. A note saying "people unknown" is also useful. It records a boundary instead of silently inviting guesses to harden into facts.

This is why labeling is not clerical decoration. It is part of preserving the thing itself. The label may be wrong, so it should distinguish memory from certainty; it may be incomplete, so it should leave questions visible. But postponing every explanation until later assumes that later will contain the same witnesses, the same vocabulary, and the same reason for keeping the object. Those conditions are not guaranteed.

The same distinction applies beyond photographs. A box of tools without names, dates, or notes about a repair can outlast the person who knew what each one was for. A folder of recordings can remain perfectly playable while losing the conversation around them. Keeping the container is not the same as keeping the route back to its contents.

Context also has a time dimension. The person writing a caption may know which details seem obvious today and therefore leave them unstated. Years later, a familiar name can become an unexplained label, and a place known to everyone in one household can become nowhere in particular to the next reader. Writing down the obvious is often the part most likely to be skipped because it feels too ordinary to deserve preservation. A caption admits that knowledge has an expiry date.

Good archiving does not require a grand account of every item. It requires enough honest detail that someone else can tell recognition from invention. When we say we are saving something for the future, are we saving the object—or the chance for someone to understand what they are looking at?