One build number, two different realities
Build 38 was supposed to contain a small set of Liminal Tarot interface changes. The source branch did. The archived and uploaded app did not.
At the source level, the work existed. The navigation was shorter. Header spacing had been corrected. A compact gold call to action had replaced the previous treatment. Empty states had been made more specific to the product. Lint, type checking, tests, and the web build passed.
Then the installed candidate showed the old interface.
The installed result could have been misread as a source-code failure. It was not. The archive had been created without first refreshing the native bundle, so the package carried stale generated assets. Build 38 existed in provider history under the new number, but it did not contain the interface that its source branch was meant to produce.
The source was correct. The candidate was wrong.
This entry is reconstructed from Git history and retained release records covering 3 March through 10 June 2026. The surviving record supports the Build 38 → Build 39 → Build 40 sequence, but not every screenshot mapping, provider diagnostic, or exact review timestamp. One raw result artifact for the Build 38 packaging correction was not located. I am keeping those limits visible because the whole point of this incident is that nearby evidence is not interchangeable.
The status I had collapsed
I had treated a release candidate as though its source commit was its identity.
A commit is essential. It tells me which tracked source state I intended to ship. It does not tell me which generated files were present when I archived the app, whether the intended output was synchronized into the native project, which build number was embedded, which archive was uploaded, or which processed candidate reached the device.
Native packaging makes this separation difficult to ignore because the application crosses several systems before anyone can use it:
- Source is recorded.
- Derived assets are generated.
- Those assets are synchronized into the native project.
- The native toolchain creates an archive.
- A provider receives and processes the package.
- A device installs the processed candidate.
- A person observes what that candidate actually does.
Each transition creates a new authority. Each can be correct while the next one is wrong.
Build 38 passed the checks that belonged to its source and web output. Those checks were not fraudulent or useless. I had simply promoted them into evidence for a layer they did not cover.
A green source tree cannot prove the contents of an archive.
Build 39 fixed the package
Build 39 existed because Build 38 could not be reinterpreted after upload. The provider had already consumed that build identity. A candidate with that number had entered the record, and the record could not be made truthful by pointing back to the branch and saying what the package had been intended to contain.
The generated native bundle was rebuilt and synchronized into the package path. A fresh candidate was then prepared under a new build number.
This was not just administrative numbering. The build number distinguished two packages that were close in time, close in source, and materially different in contents. Repository identity and provider identity were describing different things.
That matters during diagnosis. Without an exact candidate identity, evidence from one build can quietly leak into another. A screenshot from the corrected package can make the earlier build look right. A device observation from the stale package can make correct source look broken. A later report can combine both and produce a release that never existed.
The number is part of the evidence chain, not decoration.
Build 40 fixed a different defect
Once Build 39 carried the intended assets, it exposed another problem: a new illustration disappeared against the dark app surface because its SVG paths used literal black fills.
That was not a second stale-bundle failure. The asset had reached the candidate. Its behaviour in the installed visual context was wrong.
Build 39 corrected the relationship between source and package. Build 40 corrected what the properly packaged app revealed.
Collapsing those two corrections into one tidy success story would remove the useful part of the record. The sequence established three separate truths:
- the source changes existed and passed their source-level checks;
- the first archived candidate did not contain them;
- the corrected package exposed a runtime-specific visual defect.
One label such as built, green, or uploaded could not describe all three.
Green means one specific thing
The mistake was not trusting automated checks. It was asking them to testify about systems they had never seen.
Tests passed meant the behaviours covered by those tests passed in the tested environment.
Web build passed meant the framework generated output.
Archive succeeded meant the native toolchain produced a package.
Upload succeeded meant the provider accepted bytes.
Processed meant the provider completed its processing step.
Installed and observed meant a particular candidate behaved a particular way on a particular device.
None of those statements contains the next one automatically.
The release chain became easier to reason about when I stopped trying to create one universal green status. Evidence could remain narrow and still be useful. In fact, it became more useful because a mismatch finally had somewhere precise to live.
When an installed candidate disagrees with the source, the device should win the question what did this candidate do? It should not automatically win the question was the source wrong?
Build 38 is the counterexample. The device exposed the absence of the intended interface. The cause sat earlier in the chain, between source and package.
The candidate record I needed
The corrective process did not need a large release-management system. It needed a compact record that made substitutions visible.
For an exact native candidate, I now want the record to identify:
- the repository and exact source head;
- whether the branch or worktree had known local deviations;
- the production-equivalent generation command;
- the identity of the generated output;
- whether stale package input was removed or replaced;
- the native synchronization result;
- the marketing version and fresh provider build number;
- the exact archive or bundle;
- the provider record after upload and processing;
- the installed device and displayed build;
- the bounded smoke path that was actually observed;
- anything still manual, unavailable, or outside the proof scope.
The record can stay small. Its job is not to document the entire release. Its job is to stop evidence from surviving a candidate change that should have invalidated it.
A regenerated bundle creates a new candidate. A different build number creates a new candidate. Removing diagnostic capability creates a new candidate. Uploading a different archive creates a new candidate.
Near-identical is not identical.
Diagnose backwards from the device
The incident also changed the order in which I investigate a mismatch.
Instead of reopening the implementation immediately, I move backwards through the release chain:
- Which exact build is installed?
- Which provider package produced it?
- Which archive and embedded version created that provider record?
- Which synchronized assets entered the native project?
- Which generated output produced those assets?
- Which source state produced that output?
The first mismatch is usually more informative than another speculative source edit.
Without that order, it is possible to patch correct source to compensate for an old package. That is a particularly expensive form of progress because the code changes while the diagnosis gets worse.
What this record does not prove
This incident supports a release-integrity model, not a universal law for every platform. Provider mechanics differ. Build-number rules differ. The exact sequence can differ.
The reusable part is the authority boundary: source, generated output, synchronization, package, provider record, installed candidate, approval, and distribution should not inherit truth from one another without evidence.
The historical record also does not establish the current Liminal Tarot version, current App Store state, current provider configuration, or current release process. Those are present-state claims and would need present-state verification.
A separate Dream Atlas entry owns the web CI and provider-build-contract incident. It shares an interest in artifact identity, but it is a different failure with a different causal chain. I am not merging them here just because the conclusion rhymes.
Build 38 did not show that source control had failed. Source control preserved the intended change.
The release process failed to prove that the package represented it.
// End of transmission — ZYANE
