[{"data":1,"prerenderedAt":378},["ShallowReactive",2],{"i-lucide:chevron-down":3,"i-lucide:menu":8,"home-insights":10,"i-lucide:route":346,"i-lucide:container":348,"i-lucide:flame":350,"i-lucide:git-branch":352,"i-lucide:check":354,"i-lucide:server":356,"i-lucide:map-pin":358,"i-lucide:plug":360,"i-lucide:cloud-off":362,"i-lucide:flag":364,"i-lucide:clock":366,"i-lucide:package-check":368,"i-lucide:phone-call":370,"i-lucide:flask-conical":372,"i-lucide:file-check-2":374,"i-lucide:arrow-right":376},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m6 9l6 6l6-6\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 5h16M4 12h16M4 19h16\"\u002F>",[11,184],{"id":12,"title":13,"author":14,"body":15,"date":173,"description":174,"draft":6,"extension":175,"meta":176,"navigation":177,"path":178,"readingTime":179,"sector":180,"seo":181,"stem":182,"__hash__":183},"insights\u002Finsights\u002Foffshore-connectivity-field-systems.md","Design field systems for the connection you actually have","Cipher4 Engineering",{"type":16,"value":17,"toc":162},"minimark",[18,22,25,28,33,36,39,42,45,49,55,58,61,65,68,74,80,86,92,98,102,105,108,112,115,134,137,141,144,147,150],[19,20,21],"p",{},"Most operational software fails offshore for the same reason. It was built on a desk in a city, on a connection that never dropped, and it treats the network as something that is simply there.",[19,23,24],{},"Then it goes to a platform two hours off the coast, where bandwidth is shared between the safety systems, the crew's welfare traffic and whatever the last vendor installed, and it discovers that \"there\" is a spectrum rather than a state. The link is up but slow. The link is up but the latency is four seconds. The link is up for six minutes in every hour. The link is down and nobody will say for how long.",[19,26,27],{},"A system that assumes a network handles the first case badly and the rest not at all.",[29,30,32],"h2",{"id":31},"what-actually-goes-wrong","What actually goes wrong",[19,34,35],{},"The failure is rarely dramatic. It looks like this:",[19,37,38],{},"A technician completes an inspection on a tablet. The form posts, the request hangs, and after thirty seconds it times out. The technician retries. It times out again. He is standing in a hot module with three more items on his list, so he writes the readings on a paper glove liner and carries on. That evening he types some of them into a spreadsheet from memory. Two of the four readings are approximations. Nobody ever knows which two.",[19,40,41],{},"The system did not crash. No alert fired. But the record is now fiction, and the integrity engineer onshore will make a decision on it in a fortnight.",[19,43,44],{},"The second failure is worse because it is silent. An app queues a submission in memory, the tablet is put on charge, the OS reclaims the process, and the queue goes with it. Nobody was told. The data simply never existed.",[29,46,48],{"id":47},"the-rule-we-build-to","The rule we build to",[19,50,51],{},[52,53,54],"strong",{},"Capture is local and synchronous. Transmission is remote and eventual. The two are never the same operation.",[19,56,57],{},"In practice that means when a technician presses save, the record is written to durable local storage on the device and the interface confirms it immediately. That confirmation is honest: the data exists, it is safe, and it will survive a reboot. Whether it has reached shore is a separate question with its own separate indicator.",[19,59,60],{},"This sounds obvious. It is routinely got wrong, because the naive implementation (post to the API, show a spinner, show a tick) works perfectly in every test anyone runs in an office.",[29,62,64],{"id":63},"what-follows-from-that","What follows from that",[19,66,67],{},"Once capture and transmission are separate, several other decisions stop being optional.",[19,69,70,73],{},[52,71,72],{},"Every record needs an identity generated on the device."," If the server assigns the identifier, the device cannot reference the record until the round trip completes, and you are back to needing a network. Generate a UUID locally at the point of capture and let it be the identity for the record's whole life.",[19,75,76,79],{},[52,77,78],{},"Sync has to be idempotent."," A device that half-succeeded will retry. If your endpoint creates a new record every time it is called, an intermittent link produces duplicates, and duplicated inspection records are worse than missing ones because they look complete.",[19,81,82,85],{},[52,83,84],{},"Order cannot be assumed."," A device that has been offline for three days will submit records that predate ones already on the server. Timestamps come from the device, so the server has to accept out-of-order arrivals and sort on the captured time rather than the received time. This also means device clocks matter, and device clocks drift.",[19,87,88,91],{},[52,89,90],{},"Conflict needs a stated policy, decided before you need it."," Two people edited the same job record while both were offline. Last-write-wins is a policy. Field-level merge is a policy. Flagging for human resolution is a policy. Having no policy is also a policy, and it is the one that loses data.",[19,93,94,97],{},[52,95,96],{},"Payloads need to be sized for the worst link, not the average one."," A 4MB inspection photograph is not going anywhere over a congested VSAT segment. Compress on the device, upload the record and the images as separate operations, and let the record land without waiting for the media.",[29,99,101],{"id":100},"the-part-that-is-not-technical","The part that is not technical",[19,103,104],{},"Field systems are also judged on whether the person using them can tell what is happening. If a technician cannot see the difference between \"saved here\" and \"sent to shore\", they will invent a mental model, and it will be wrong in whichever direction is most convenient at the end of a twelve-hour shift.",[19,106,107],{},"We show two states explicitly, in plain language, on every record. Saved on this device. Sent to shore. A count of what is still waiting, visible without navigating anywhere. It is unglamorous, and it is the difference between a system people trust and one they work around.",[29,109,111],{"id":110},"testing-it-properly","Testing it properly",[19,113,114],{},"The test that matters is not \"does it work offline\". It is:",[116,117,118,122,125,128,131],"ul",{},[119,120,121],"li",{},"Airplane mode for four hours of realistic work, then reconnect.",[119,123,124],{},"Force-quit the app mid-sync, reopen it.",[119,126,127],{},"Reboot the device with fifty records queued.",[119,129,130],{},"Throttle the connection to 50kbps with two seconds of latency and packet loss, which is closer to a bad VSAT day than a clean disconnection.",[119,132,133],{},"Let the battery run flat mid-submission.",[19,135,136],{},"The throttled case catches more real bugs than the offline case, because a fully absent network takes an obvious code path and a terrible network takes the same path as a good one.",[29,138,140],{"id":139},"why-this-is-worth-the-effort","Why this is worth the effort",[19,142,143],{},"An operator does not buy a field system to have a field system. They buy it because the current arrangement produces records they cannot rely on, and decisions made on unreliable records eventually produce an incident, a failed audit, or a piece of equipment that fails earlier than it should have.",[19,145,146],{},"A system that quietly loses one record in twenty has not improved on the paper it replaced. It has made the loss harder to notice.",[148,149],"hr",{},[19,151,152],{},[153,154,155,156,161],"em",{},"If you are running field data capture in an environment where connectivity is genuinely unreliable and you are not confident about what happens when it fails, that is a conversation worth having. ",[157,158,160],"a",{"href":159},"\u002Fcontact","Get in touch",".",{"title":163,"searchDepth":164,"depth":165,"links":166},"",2,3,[167,168,169,170,171,172],{"id":31,"depth":164,"text":32},{"id":47,"depth":164,"text":48},{"id":63,"depth":164,"text":64},{"id":100,"depth":164,"text":101},{"id":110,"depth":164,"text":111},{"id":139,"depth":164,"text":140},"2026-07-14","Offshore and remote-site software fails for a predictable reason. It assumes a network. Here is how we build systems that keep working when the link drops, and what that changes about the architecture.","md",{},true,"\u002Finsights\u002Foffshore-connectivity-field-systems","6 min read","Oil & Gas",{"title":13,"description":174},"insights\u002Foffshore-connectivity-field-systems","_7db3HVq_2Kwqd0jzBhC6Ryw7PWUk3zZonkX83Hrdw0",{"id":185,"title":186,"author":14,"body":187,"date":337,"description":338,"draft":6,"extension":175,"meta":339,"navigation":177,"path":340,"readingTime":341,"sector":342,"seo":343,"stem":344,"__hash__":345},"insights\u002Finsights\u002Fprocurement-systems-that-survive-audit.md","What makes a procurement record survive an audit",{"type":16,"value":188,"toc":329},[189,192,195,198,202,205,208,211,214,218,225,228,231,235,238,244,250,256,262,268,274,278,281,284,288,291,297,303,309,313,316,319,321],[19,190,191],{},"An audit query almost never asks who won. It asks why.",[19,193,194],{},"Why was this bidder shortlisted and that one excluded. Why did the technical weighting change between the first round and the second. Why was an award made to the third-lowest price. Why does the evaluation sheet have a score that does not appear in any of the submitted documents.",[19,196,197],{},"Most procurement systems answer the first question well and the rest badly, because they were designed to record outcomes. An outcome is a row in a table. Reasoning is a sequence of states, each with an author and a time, and most systems overwrite it.",[29,199,201],{"id":200},"the-overwrite-problem","The overwrite problem",[19,203,204],{},"Here is the pattern we find most often when we assess an existing sourcing process.",[19,206,207],{},"A tender is published with a set of evaluation criteria. During the process someone realises the technical criteria are too narrow, genuinely and for a good reason. They edit the criteria in the system. The record now shows the revised criteria as if they had always been the criteria.",[19,209,210],{},"Nothing dishonest happened. But the system has destroyed the evidence that would prove it. Two years later, an auditor comparing the published tender document against the system record finds a discrepancy with no explanation attached, and the burden of proof has quietly moved onto the person who made a reasonable decision.",[19,212,213],{},"The same thing happens with scores that get adjusted after a moderation meeting, with bidders who are excluded on a compliance point that is later resolved, and with deadlines that are extended for a legitimate reason.",[29,215,217],{"id":216},"append-do-not-overwrite","Append, do not overwrite",[19,219,220,221,224],{},"The single most useful property of an auditable procurement record is that ",[52,222,223],{},"nothing is ever edited in place",". Every change is a new entry that references the previous state, carries the identity of the person who made it, the time, and a reason.",[19,226,227],{},"The current state is a projection of that history rather than a row that gets updated. When an auditor asks why the criteria changed, the system answers directly: on this date, this named person changed this criterion from A to B, for this stated reason, and here are the four bids that had already been received at that point.",[19,229,230],{},"This costs more to build than a table with an update statement. It costs dramatically less than reconstructing a decision from three people's email two years after they made it.",[29,232,234],{"id":233},"what-the-trail-has-to-hold","What the trail has to hold",[19,236,237],{},"In practice, a record that answers real audit questions needs six things.",[19,239,240,243],{},[52,241,242],{},"The criteria as published, immutably."," Including their weightings, at the moment of publication, with a hash or version identifier that the published document can be checked against.",[19,245,246,249],{},[52,247,248],{},"Every submission with its arrival time."," Recorded by the system, not typed by anyone. A late bid that was accepted needs to show as late and accepted, with a reason, rather than quietly on time.",[19,251,252,255],{},[52,253,254],{},"Scores attributed to individual evaluators."," Not a consolidated number. If three people scored a bid and one of them scored it very differently, that divergence is information, and a single averaged figure destroys it.",[19,257,258,261],{},[52,259,260],{},"The moderation step as its own event."," Where scores were adjusted in discussion, the before, the after, who was present and the stated rationale. This is the step most systems have no representation for at all.",[19,263,264,267],{},[52,265,266],{},"Exclusions with the specific ground."," \"Non-compliant\" is not a ground. \"Did not provide the tax clearance certificate required in section 4.2\" is a ground.",[19,269,270,273],{},[52,271,272],{},"The award decision separate from the recommendation."," The evaluation team recommends. Someone with authority awards. When those differ, and they sometimes legitimately do, the record needs to hold both and the reason for the difference.",[29,275,277],{"id":276},"the-uncomfortable-part","The uncomfortable part",[19,279,280],{},"Building this well means the system will sometimes make it visible that a process was run poorly. That is not a defect. A system that can only represent a clean process will be worked around the moment a process is not clean, and then it holds nothing useful at all.",[19,282,283],{},"The organisations that get the most out of this are the ones that treat an awkward audit trail as cheaper than an unexplainable one.",[29,285,287],{"id":286},"where-it-pays-off","Where it pays off",[19,289,290],{},"Three places, in our experience.",[19,292,293,296],{},[52,294,295],{},"Time to respond to a query."," Assembling a defence from email takes days of a senior person's time. Producing it from a system takes minutes, and it is complete.",[19,298,299,302],{},[52,300,301],{},"Supplier disputes."," A bidder who believes they were treated unfairly is much easier to answer when you can show exactly what was scored and by whom. Most disputes end at that point.",[19,304,305,308],{},[52,306,307],{},"Institutional memory."," The person who ran the tender leaves. The reasoning does not leave with them.",[29,310,312],{"id":311},"a-note-on-local-content","A note on local content",[19,314,315],{},"For organisations operating under the Nigerian Oil and Gas Industry Content Development Act, local content is not a report you produce annually. It is a scored criterion in individual evaluations and a continuing obligation across the portfolio.",[19,317,318],{},"Capturing it at the point of sourcing, per bid and per award, as a first-class field rather than a note, turns the annual reporting exercise into a query. Capturing it afterwards turns it into an archaeology project.",[148,320],{},[19,322,323],{},[153,324,325,326,161],{},"We build procurement and tendering systems for operators who are held to a documented process. If your current trail would not comfortably answer the questions above, ",[157,327,328],{"href":159},"tell us what you are working with",{"title":163,"searchDepth":164,"depth":165,"links":330},[331,332,333,334,335,336],{"id":200,"depth":164,"text":201},{"id":216,"depth":164,"text":217},{"id":233,"depth":164,"text":234},{"id":276,"depth":164,"text":277},{"id":286,"depth":164,"text":287},{"id":311,"depth":164,"text":312},"2026-06-02","Most digital procurement systems record the decision but not the reasoning. Two years later that distinction is the whole ballgame. What an auditable sourcing trail actually needs to hold.",{},"\u002Finsights\u002Fprocurement-systems-that-survive-audit","5 min read","Supply Chain",{"title":186,"description":338},"insights\u002Fprocurement-systems-that-survive-audit","gRPx9KxKtuSnh7taPV3Ck_UWRQ_OEaIgcOao_zQ3Y5g",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":347},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Ccircle cx=\"6\" cy=\"19\" r=\"3\"\u002F>\u003Cpath d=\"M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15\"\u002F>\u003Ccircle cx=\"18\" cy=\"5\" r=\"3\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":349},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M22 7.7c0-.6-.4-1.2-.8-1.5l-6.3-3.9a1.72 1.72 0 0 0-1.7 0l-10.3 6c-.5.2-.9.8-.9 1.4v6.6c0 .5.4 1.2.8 1.5l6.3 3.9a1.72 1.72 0 0 0 1.7 0l10.3-6c.5-.3.9-1 .9-1.5Z\"\u002F>\u003Cpath d=\"M10 21.9V14L2.1 9.1M10 14l11.9-6.9M14 19.8v-8.1m4 5.8V9.4\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":351},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 3q1 4 4 6.5t3 5.5a1 1 0 0 1-14 0a5 5 0 0 1 1-3a1 1 0 0 0 5 0c0-2-1.5-3-1.5-5q0-2 2.5-4\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":353},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M15 6a9 9 0 0 0-9 9V3\"\u002F>\u003Ccircle cx=\"18\" cy=\"6\" r=\"3\"\u002F>\u003Ccircle cx=\"6\" cy=\"18\" r=\"3\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":355},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 6L9 17l-5-5\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":357},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Crect width=\"20\" height=\"8\" x=\"2\" y=\"2\" rx=\"2\" ry=\"2\"\u002F>\u003Crect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\" ry=\"2\"\u002F>\u003Cpath d=\"M6 6h.01M6 18h.01\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":359},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0\"\u002F>\u003Ccircle cx=\"12\" cy=\"10\" r=\"3\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":361},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 22v-5m3-9V2m2 6a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1zM9 8V2\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":363},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10.94 5.274A7 7 0 0 1 15.71 10h1.79a4.5 4.5 0 0 1 4.222 6.057m-2.926 2.753A4.5 4.5 0 0 1 17.5 19H9A7 7 0 0 1 5.79 5.78M2 2l20 20\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":365},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":367},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"M12 6v6l4 2\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":369},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M12 22V12m4 5l2 2l4-4\"\u002F>\u003Cpath d=\"M21 11.127V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.729l7 4a2 2 0 0 0 2 .001l1.32-.753\"\u002F>\u003Cpath d=\"M3.29 7L12 12l8.71-5M7.5 4.27l8.997 5.148\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":371},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 2a9 9 0 0 1 9 9m-9-5a5 5 0 0 1 5 5m-4.168 5.568a1 1 0 0 0 1.213-.303l.355-.465A2 2 0 0 1 17 15h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2A18 18 0 0 1 2 4a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v3a2 2 0 0 1-.8 1.6l-.468.351a1 1 0 0 0-.292 1.233a14 14 0 0 0 6.392 6.384\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":373},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2M6.453 15h11.094M8.5 2h7\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":375,"hidden":177},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M4 22h14a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v4\"\u002F>\u003Cpath d=\"M14 2v4a2 2 0 0 0 2 2h4M3 15l2 2l4-4\"\u002F>\u003C\u002Fg>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":377},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14m-7-7l7 7l-7 7\"\u002F>",1788302052580]