NASAs Coding Requirements Are Insane

1.26M views6666 WordsCopy TextShare
ThePrimeTime
00:00 - Intro 4:52 - Restrict All Code 9:19 - All Loops Need A Fixed Upper Bound 10:37 - No Dynamic ...
Video Transcript:
so let's do this the 10 ways in which NASA writes critical safety now remember they're pretty good at being critically safe obviously the Voyager stuff is awesome all right most serious software projects use coding guidelines these guidelines are meant to State what the ground rules are for software to be written how it should be structured and which language feature should and should not be used curiously there is little consequence on what a good coding standard is among the many that have been written there are remarkably few patterns to discern except that each new document tends
to be longer than the one before it okay that's pretty good dark mode can you dark mode a PDF I don't know how to dark mode a PDF dog sorry this is NASA light mode okay the result is that most existing guidelines can tell well over a 100 rules or most exist okay the result is that most existing guideline contains well over a 100 rules sometimes with questionable justifications some rules especially those that try to stipulate the use of whites space in prrams python mentioned uh may have been introduced by personal preference others are meant
to prevent very specific and unlikely types of Errors from earlier uh from earlier coding efforts within the same organization not surprisingly the existing code guidelines tend to have little effect on what developers actually do when they write code by the way I'm loving whatever the start of this article is whatever NASA has cooked up the start of this is amazing because it is so right that there is a lot of personal preferences that are written WR into code guidelines uh the most dooming aspect of many of the guidelines is that they rarely allow for comprehensive
tool-based compliance checks tool-based checks are important since it often INF feasible to manually review the hundreds of thousands of lines of code that are written for larger projects I love the I love everything I love everything that is is currently being stated things should be rely on Automation and tools and it should have zero personal preference okay things are looking good things are looking good the benefit of existing coding guidelines is therefore often small even for critical applications a variable verifiable set of well-chosen coding rules could however make critical software components more thoroughly analyzable for
properties that go beyond compliance with the set of rules itself to be effective though the set of rules has to be small and must be clear enough that it can easily be understood and remembered the rules will have to be specific enough that they can be checked mechanically to put an easy upper bound on the number of rules for an effective guideline I will argue that we can get significant benefit by restricting to no more than 10 rules such a small set of course cannot be all-encompassing but it can give us a foothold to achieve
measurable effects on soft reliability and verifiability oh this is sounding so good I'm liking everything so far to support strong checking the rules are somewhat strict one might say even Draconian the trade-off though should be clear when it really counts especially in the development of safety critical code so I should probably consider this for the bot I'm writing right for my twitch bot stream recommendation bot obviously I should probably be considering these 10 rules because we need criticality in here okay I want verifiability to the max I want the Ten Commandments of programming all right
let's see it may be worth going the extra mile and living within structor limits then may be desirable in return we should be able to demonstrate more convincingly that critical software work as intended 10 rules for safety critical coding the choice of the language for safety critical code is in itself a key consideration but we will not debate it much here at many organizations JPL included critical code is written in C with its long history there is extensive tool support for this language including strong source code analyzers logical model extractors metric tools debuggers test support
tools and a choice of mature stable compilers for this reason C is also the target of the majority of coding guidelines that have been deployed or developed for Fairly pragmatic reasons then our code rules primarily Target C and attempt to optimize our ability to more thoroughly check the reliability of critical applications written in C all right you heard it here people I know there's a lot of soy devs out there right now okay a lot of you guys right now that when you heard the fact that they used C and they used the word safety
critical you're just like interns again last time I checked y'all don't have Voyager out there okay you're not Giga Chads okay you're a bunch of script kites laring as soft as system Engineers okay shut up let's see what they have to say Okay the following rules May provide a benefit especially if the low number means that developers will actually adhere to them each rule is followed with a brief rationale for its inclusion rule one restrict all code to very simple control flow constructs do not use goto statements set jump or long jump constructs and direct
or indirect recursion I don't know what indirect recursion means is indirect recursion just two functions that call back and forth to each other okay so help me understand this one how would you do something because this does happen this I mean this has happened many many a times where you have a you have a function that needs to go out and call another function that needs to go do something and do some sort of checking and then come back through some means that you don't control especially in languages that like if you don't have async
await I I guess you just have to block the thread is that all it's asking you just block the thread if you don't have async A8 right and you just sit there and you just block the thread and and you just do this in a while loop I'll have to think of the times that I use because I I I technically use indirect uh indirect recursion when doing reconnecting sockets and what I mean by that is if I if I just open up uh let's open up yeah socket uh this one there we go so
I have this reconnecting thing that will literally call a private reconnected that resets the state then call reconnect and reconnect will go in here and then call connect connect when it disconnects will recall connect so this is technically a form of of of I mean I guess I could just put this in a while loop and then having a wait I wonder if that's simpler now you really got my brain thinking about this it could just be an you know I kind of I kind of want to think more about that okay I hey I
will think more about that I will think more about that Prime already broke rule one well indirect recursion is is is a very powerful uh uh infinite problem solving mechanism but I I I can try it right I I am I have absolutely no problem trying something right rationale simple simple control flow translates into stronger capabilities for verification of often and often results in improved code Clarity I'd agree it is it is a bit it is a bit snaky to see that on a close like or on a uh on a close we call reconnect
on a reconnect we check to see if we're started and then if we haven't done this and then if we are uh connecting we return like there's a lot of these these statements that all exist in in ordering that kind of cause this problem to happen so I can I can understand this I can actually I can actually uh argue I can see why this happens right okay the banishment of recursion is perhaps the biggest surprise here without recursion though we are guaranteed to have an a cyclic function call graph uh which can be exploited
by code analyzers and can directly help to prove that all executions that should be bounded are in fact bounded note that this rule does not require that all functions have a single point of return although this is often simplifies control flow there are enough cases though where an early return or an early error return is a simpler Solution that's actually I mean that's a very pragmatic argument for it to be completely fair this is very uh this is a very pragmatic argument for why you shouldn't do this and to also be completely Fair my teacher
in my junior year or my senior year of college made us do oh no no it was actually my it was actually my sophomore year in college made us do an AVL tree if you're not familiar with AVL trees it's a type of self-balancing binary search tree that uses rotations there's like uh four different there's four different rotations right there's the long right there's an there's a uh there's an RR and LL or an RL or LR and there's these different rotations that uh OCC and they're actually pretty fun to do here I'll just write
it really I'll just write it really quickly for you um uh we'll call we'll call this here I'm just going to write it as a b c right so if you have a tree if you have a binary tree that looks like this then you want to reorganize it as b a uh C and if you have a binary tree that looks like uh a uh a b c right you reorganize this as uh a b c uh whoopsies hold on a c b and then do this rotation right pretty straightforward right it's a pretty
straightforward and he said we had the program of Navy elry we just had to use um we had to use uh no recursion it was a great it was one of it was a great learning experience for me all Loops must have a fixed upper bound it must be trivially possible for a checking tool to prove uh statically that a present upper bound on the number of iteration of a loop cannot be exceeded if the loop bound cannot be proven statically the rule is considered violated so does that hold on uh so does that mean
you can't use things like array do R do4 each because there's no there's no technical you like the upper bound is dynamic based on the array or is it so no while true rationale the absence of recursion and the presence of a loop bounds uh prevents runaway code this rule does not of course apply to iter uh iterations that are meant to be non-terminating uh in a process scheduler in those special cases the reverse rule is applied it should be statically provable that this iteration cannot terminate one way to support the rule is to add
explicit upper bounds to all Loops that have a variable number of iterations a let's see code that traverses a link list when the upper bound is exceeded uh is exceeded and assertion failure is triggered and the function containing the failure iteration returns an error see rule five about use of assertions okay okay interesting rule three do not use dynamic memory allocations after initializations and there went JavaScript and go damn actually no to be fair you could do this without actually you could do this in JavaScript and go you could do this in JavaScript and go
for the most part obviously uh anytime you did like G Funk it would I'm sure it allocates some amount of memory underneath the hood that you don't know about uh all interpreted languages not all interpreted languages that's not fair technically not all interpreted languages it just means you can't use closures right because closures would be I assume this means you cannot use closures correct you have to use uh you have to do Arena yeah you have to do uh you have to do Arena allocations effectively you can't use list or strings yes you can use
list and strings it's just that means that whatever memory you allocate is done in the beginning meaning um and I I think it's talking about the Heap not the stack is my assumption but on top of that my assumption is that like let's say you have something that produces a certain set of responses from a server you have an area of memory that's dedicated to the amount of responses that you could possibly ever need and then you just simply cycle through it like a ring buffer so that way there is no there's not there's no
allocations happening so all possible data has already been pre-allocated okay so at the start you should have all the memory you need correct which means you can totally have strings it's just that you you have to have a predefined area of how much Kings you should have Jesus that is so much thinking it is a lot of thinking it is very interesting circular buffers yeah uh this rule is common for safety critical software and appears in most coding guidelines the reason is simple memory allocators such as Malik and garbage collectors often have unpredictable behaviors that
can significantly impact performance a notable class of coding errors also stems from mishandling of memory allocations and free routines forgetting to free memory or continuing to use memory after it was freed attempt to allocate more memory than physically available overstepping boundaries on allocated memory Etc forcing all applications to live within a fixed pre-allocated area of memory can eliminate memory or many of these problems and make it easier to verify memory use note that the only way to dynamically claim memory in the absence of memory allocation from the Heap is to use stack memory in the
absence of recursion rule one an upper bound on the use of Stack memory can der uh can derived statically thus making it possible to prove that an application will always live within its pre-allocated memory means I mean it's it seems super cool that sounds nutty it doesn't actually sound that nutty this actually makes perfect sense and I've heard there's a thing that they do in game programming correct me if Dan Vil is still here correct me if I'm wrong on this one where they actually do um effectively budgets per uh like sub team and you
have only so much allowance either in memory or in CPU time that you're allowed to spend in your in your part of the program and that way as you exceed it you can kind of have General reporting being like hey uh physics team you're using too much time so therefore can we do something else yeah that sounds nice yeah and so it's kind of like I I know that's yeah I know that's a thing yeah I've I've heard a lot about that let's for physics foret yeah I know I'm just I gave an example that
I understood and that the Layman game programmer would understand right two little FPS no bonus this year yeah that's basically uh true uh broadly speaking it's a bit more nuanced than twitch chat allows but yeah yeah that's I'm trying to give my best layens defense of it Layman game programmer I am a Layman game programmer I have programmed small games but I understand that I am no game programmer all right rule four no function should be longer than what can be printed on a single sheet of paper in a standard reference format with one line
per statement and one line per declaration typically this means no more than about 60 lines of code per function okay that's that's pretty fair that's pretty fair like 60 lines is a good amount of room for you to be able to figure things out well Uncle Bob only allows somewhere between like three to five per function 60 lines of code is like a lot well it said on a single sheet on a single sheet right this is just printed on a single sheet which means about this notice that it's not like strictly a hard rule
but it's a it it well it's a hard rule in the sense that what can be printed on paper I feel like you can express quite a bit in 60 lines of code there has to be some way in which you can break this rule but I I feel like I can I I feel like I could generally and easily write code with 60 line maximum functions like I I don't feel like that would be hard at all yeah ghost standard library has thousands of lines yeah but ghost standard library is I wouldn't necessarily look
at ghost standard Library as one of the cleanest nicest piece of code ever written okay I I personally think the ghost standard library is pretty much uh I would call it a hot hot hot Garbo when it comes to reading it rational each function should be a logical unit of code that is understandable and verifiable as a unit it is much harder to understand a logical unit that spans multiple screens on a computer display or multiple pages when printed excessively long functions are assign a poorly constructed code I mean I generally agree with the statement
I think it's pretty rare to find code that's actually longer than 60 lines of code and I think generally when you do I think generally when you do a it's like something that's very explicitly difficult and needs to all be together due to locality of behavior or B it's a horeshit a function right unless if you write react I believe what I said stands all right rule five the assertion density of the code should average to a minimum of two assertions per function assertions are used to check for anomaly conditions that should never happen in
real life executions assertions must always be side effect free and should be defined as booing tests when an assertion fails an explicit recovery action must be taken EG by returning an error condition to the caller of the function that executes the failing assertion any assertion for which a static checking tool can prove that it can never fail and never hold violates this rule I.E it is not possible to satisfy the rule by adding unhelpful assertion true statements I feel like I like this this rule a lot I feel like there's something that's very good about
this Rule and I feel like I need to I need to exercise this more I feel like I need to I still need to keep on doing this because like I said I still do quite a few assertions in in in my codebase right I do I do do this quite a bit but my current assertions right here actually hard crash my program so when I have somehow internally created a message that does not conform to the expectations that I'm getting then I believe that I have so go I have goofed up so hard that
it needs to blow up the result pattern everywhere then effectively it's the result pattern everywhere rational uh statistics for industrial coding effort indicate that a unit tests often find at least one defect per 10 to 100 lines of code written the odds of intercepting defects increase with assertion density use of assertions is often also recommended as part of strong defensive coding strategy one thing that's really nice is that if you can come up with any sort of fuzzing strategy you can actually test your program right you can actually like completely test your program to the
sense where you can just like throw at it a whole bunch of random stuff and somewhere within your program you should be having actual defensive statements being made to ensure that further down asserts are never actually happening right and so you can actually also drop a lot of unit like specific unit tests for fuzz testing using this which is pretty crazy uh assertions can be used to verify pre- and post conditions of functions parameter values return values of functions and loop and variance because assertions are side effect free they can selectively disabled uh after testing
in performance critical code let's see a typical use of assertions would be the following if C assert p is greater than equals true return err with uh with the let's see with the assertion defined as above Define c assert debugging failed assertion file line all this stuff false okay so there's some sort of test debugging uh let's see in this uh definition file and line are predefined by macro pre-processors to produce the file name and line number of the failing assertion the syntax pound e turns the assertion hold on the syntax e turns the assertion
condition e into a string that is printed as part of the error message in code destined for embedded process there is of course no place to print the error message itself in that case the call to test debugging is turned into a noop and the assertion uh turns into a pure Boolean test and let's see that enables error recovery from anomalous Behavior you know like uh what's it called so where this so this is of where I started on my little journey of this like power of 10 thing is that uh I had yon uh
join us from tiger beetle and they do this they like he was very specific about this thing and he can just throw anything at tiger beetle like any data anything and it will behave properly at all times it does like something like 200 years worth of query testing every single day on every single build and so it's just like constantly getting hammered and has a sear everywhere it's it's a super cool super cool project data objects must be declared at the smallest possible level of scope this is just c not having proper error handling and
syntax features I strongly disagree with that statement uh asserts are in fact very very useful even I mean and tiger beetle is written in Zig which has proper air handling utilities it has it has result objects but it has it its own error handling result objects which actually provide much better stack traces than uh standard errors it's very very cool I think he said 8,000 asserts existed at the time of the interview yeah asserts are not handling asserts are in fact not air handling they are invariant uh invariant hard stops if you will um let's
see is it any different than just returning an error in Zig you return an error in Zig you return an error in Zig or a value in Zig but it's it's an air a yeah they don't hard stop here they have to have recovery which I think is fine which I think having a hard stop here or having a soft stop and some sort of Air recovery mechanism but you build Air recovery mechanism and everything uh this rule supports basic principle of data hiding clearly if an object is not in scope its value cannot be
referenced or corrupted similarly if erroneous value of an object has to be diagnosed a few let's see the fewer number of statements where the value could be assigned the easier to diagnose the problem the rule discourages the ReUse of variables for multiple incompatible purposes uh which can complicate fault diagnosis uh op potentially mentioned is that what I'm hearing uh I don't really I I'm not I'm not concretely understanding rues six other than it just sounds like it sounds like what I think it liter I mean it just sounds like you just Define it where you
use it and that's always the smallest one is that what it's saying it sounds yeah are we encapsulating here all right rule rule seven the return value of non-void functions must be checked by each calling function and the validity of the parameters must be checked inside each function I don't I don't know what that means hold on what does this mean uh this is possibly the most frequently violated Rule and therefore somewhat more suspect as a general rule in it St is form the rule means that even the return value of a print F statement
and file close statements must be checked one can make a case though that if the response to an error would rightfully be no different than a response to a success there is little point in explicitly checking the return value this is often the case uh with calls to print F and close so actually that's one reason why I really do like um I think I I think I have some Zig in here uh uh let's see so this right here this right here so DNA can return uh an object to see if anything happened to
it while it was going on and I am explicitly stating in a very explicit sense that I do not care about the return value right uh there you go see that so when I do this oh oh whoopsies was that just uh it hold on let's see if I can just do this uh uh fun uh fu I can just do that um let's see if I just call Foo I'm pretty sure if I just do Foo I thought if I'm not mistaken that should it it probably isn't working with the the zls LSP I
haven't set it up correctly apparently there's a way to do build build on this um Zig build can I do that there you go nice right there so this is supposed to show up in my editor it's supposed to show up right here but this is just saying the aor union is ignored meaning that I had to I am not handling I'm simply ignoring it or I'm simply just not using it and so I can do this saying I know there's something that happens I just don't care that it happens which I think is pretty
great I think is I think that's a great great thing to do I actually I actually really do enjoy this about Zig and I think I think rust has the same thing except for they just do it as warnings when you disregard the uh a result that's returned or an async that's returned I I like that rule though I think that's a great rule I think that's a great Great rule in casee in cases like this it can be acceptable to explicitly cast the function return value to void thereby indicating that the programmer explicitly and
not accidentally decides to ignore the return value yes so that would be the underscore explicitly casting it to a void in more dubious cases a comment should be pre uh present to explain why the return value is irrelevant in most cases though the return value of a function should not be ignored especially if error return values must be propagated up the function call chain standard Library famously violates this rule with potentially grave consequ quences see for instance what happens when you accidentally execute stir Lane zero or stir stir cat this with the standard C string
Library it's not pretty by keeping the general rule we can make sure that exceptions must be justified with mechanical Checkers flagging violations often it will be easier to comply with the rule than to explain why non-compliance might be acceptable I like that this is this is I mean this is great this is just a generally A good rule you know so much of programming is just learning some of these techniques to not shoot your yourself in the foot the use of pre-processor must be limited to the inclusion of header files and simple macro definitions token
pasting variable argument list elipses and recursive macro calls are not allowed all macros must expand into complete static units the use of conditional compilation directives is often uh is often also dubious but cannot always be avoided this means that there should rarely be justification for more than one or two conditional compilation directives even in EN large software development efforts beyond the standard boiler plate that avoids multiple inclus of the same header file each uh such use should be flagged by the tool by a tool-based checker and justified in the code I mean definitely not wrong
on being careful about the pre-processing macros I have never had more problems understanding code than pre-processing macros pre pre-processing macros are one of the hardest things ever this rule would actually complicate our game so much more I don't like this at all interesting I don't like pre-processor pre-processors in general yeah I I can can understand that pre-processors can definitely cause a whole bunch of problems um and people more generally call them macros uh generally macros can be very nice but they also generally can be massively difficult to understand like very very difficult to understand thank
God C does not have macros yeah they're they are an incredible ham Hammer they're an incredible tool but just like all incredible tools they are just super dangerous the SE uh pre-processor has a powerful obic tool that can destroy code Clarity and befuddle many text-based checkers the effect of constructs in unrestricted pre-processor code can uh be extremely hard to decipher even with formal language definitions in hand in a new implementation of C preprocessors developers often have to resort to using earlier implementations as a referee for interpreting complex defining language in the C standard the rationale
for the caution against conditional compilation is equally important note that with just 10 condition compil 10 conditional compilation directives there could be up to uh 2 to the 10 possible versions of the code Each of which would have to be tested causing a huge increase in the required test effort I mean that this is a this is fantastic points here right it's it is it is a good point that especially conditionally compiling is just a nightmare generally speaking conditioning compiling is a nightmare though there are times you just have to have it I mean I
think that's what I like about this is that this is a very pragmatic rule recognizing that you cannot avoid conditionally compiling but conditional compile is also very very difficult and is also very awful right it's yes the rust the rust cargo feature I assume the rust cargo features purely exist mostly due to the fact that rust compilation is slow and most rust binary most people that I know have not been too into rust binaries and it's more just the worry of of the slow ass compilation time now I'm sure rust binaries at the end would
be very very important but as far as I can tell a lot of it has to do with just the slowness it's a more of a KN out of slowness which actually adds a whole bunch of I always end up getting something like clap and then I forget feature derive and then I have to get feature derive or then I get uh request and then forget forget request feature whatever or you get any of this and it just gets worse and worse and worse have you seen uh autosar uh C code I'm sure it's terrible
clap sorry yeah you got to get clap dog all right the use of pointers should be restricted specifically no more than one level of d referencing is allowed pointer D reference operations may not be hidden in macro definitions or inside T typed def declarations function pointers are not permitted pointers are easily misused even by experienced programmers they can make it hard to follow or analyze the flow of data in a program especially by tool-based Static analyzers function po pointers similarly can seriously restrict the types of checks that can be performed by Static analyzers and should
only be used if there is a strong justification for their use and ideally uh alternate means are provided to assist tool-based Checkers to determine flow of control and function call hierarchies for instance if a function pointers are used it can become impossible for a tool to prove absence of recursion so alternative guarantees would have to be uh provided to make up for this loss uh in analytical capabilities how do you do like async stuff interrupt R yeah let's see I don't think they do async I'm sure they do async that's the neat part you don't
know you you yeah you have to do some sort of mutex usage right some sort of semaphor mutex lock usage and then some other reference to the memory right async is not deterministic so nope well it's not that it's not deterministic but you you have to do some level of it right like imagine you just you you have a probe here's my probe and bro has a little camera on it right and it's taking a photo well at some point you take the photo it goes through it processes it dumps this all on memory and
when it's done it says it's done and then something has to wake up so like even even interrupts waking up certain parts of the code has to be isn't that also some level of a function pointer yeah mutex I would just assume it's a mutex lock right so my assumption would be that you have a piece of code that has some sort of you know uh on Photo right and you are sitting here going like this uh you're doing AC choir whatever this the semaphore of one is whatever the semaphore of one is in in
C I forget what it is lock unlock uh or lock acquire whatever it is and it just sits here and waits and then when a photo comes through then it goes through here and processes all the code and then goes back up here and goes back onto this thing I like this okay so it I mean this one seems harder all code must be compiled from the first day of development with all compiler warnings enabled at the compiler's most pedantic setting all code must be compiled with these settings without any warnings all code must be
checked daily with at least one but preferably more than one state the art static source code analyzer and should pass the analysis with zero warnings I mean that that that that seems pretty reasonable honestly especially if you're starting out brand new like if you're starting out brand new you should be able to you should be able to do this one there are several very effective static source code and analyzers on the market today and quite a few free wear tools as well too there you go there is simply no excuse for any software development effort
not to make use of this readily available technology it should be considered routine practice even for non-critical code development the rule of zero warnings applies even in cases where the compiler or static analyzer gives an uh an erroneous warning if the compiler or static analyzer G gets confused the code causing the confusion should be Rewritten so that it becomes more trivially uh valid many developers have been caught in assumptions that a warning was surely invalid only to realize much later that the message was in fact valid for a less obvious reason static analyzers have somewhat
somewhat of a bad reputation due to early pre predecessors such as lint that produced mostly invalid messages but this is no longer the case the best static analyzers today are fast and they produce selective and accurate messages their use should not be negotiable at any serious software project to be fair I'm not a serious software guy so that makes sense I mean I mean based and amazing based and amazing best rule 10 I mean I think this makes I I I honestly think this one just makes complete practical sense it's very difficult to probably apply
this to a lot of projects though like what what could you do with JavaScript we can all agree that lint sucks in JavaScript uh a lot most of es lint is complete Garbo it's mostly complete Garbo uh eslint it's mostly just people's opinions about what is good and what is bad no you can't re and reject are in fact or re and re are in fact bad arguments to promises right uh what is it where are don't I have some JavaScript in here right when people are like new promis uh uh what's it called res
reg actually uh uh excuse me right just delete is slit yeah I agree I agree I agree the first two rules guarantee the creation of clear and transparent control flow structures that is easy to build test and analyze the absence of dynamic memory allocation stipulate by the third rule eliminates the class of problems related to allocation and freeing of memory and let's see the use of stray pointers Etc the next few rules 4 to 7 are fairly broadly acceptable accepted as standards for good coding style some benefits of other coding Styles have been uh Advanced
for safety critical systems uh EG the discipline of design by contract can partly be found in rules 5 to 7 these 10 rules are being uh used experimentally at JPL in the writing of mission critical software with encouraging results uh after overcoming a healthy initial reluctance to live within such strict confines developers often find that compliance with the rule does tend to benefit code Clarity analyzability and code safety the rules lessen the burden on the developer and tester to establish Key properties of the code EG termination or boundness safe use of memory and stack Etc
by other means if this rule seems Draconian at first bear in mind that they are meant to make it possible to check code where very literally your life may depend on its correctness code that is used to control the airplane that you fly on the nuclear power plant a few miles from where you live or the spacecraft that carries astronauts into orbit the rules act like seat belts in your car initially they are perhaps a little uncomfortable but after a while uh their use becomes second nature and not using them becomes unimaginable I love this
document even if I don't agree with all the rules I love this document I am just shocked that anything produced by the government could be as coherent as this is this is exceptionally coherent document that seems to be H that seems to have been written by somebody who is just trying to be practical and that's that love it the name is the primagen
Copyright © 2025. Made with ♥ in London by YTScribe.com