I've written several technical specifications for programming languages and representations of programming languages. From this I've learnt some useful lessons that apply to a wide range of technical specifications.
It doesn't matter how carefully you've thought things through -- until you've implemented a specification it will have problems in it: mistakes, things you haven't thought of, conflicting requirements, etc. This means that any system built to the specification will have problems.
If you've implemented (and tested) part of a specification, then you can be fairly confident that the part you've implemented doesn't have problems (or if it does, you know what they are). But there will still be problems in the unimplemented part of the system, and also problems in the interactions between the implemented and unimplemented parts. Furthermore, fixing those problems might require changes to the implemented part that you thought was ok.
So don't claim that a specification is in any way "final" until you've implemented the whole system it describes, otherwise you'll be stuck with a system that has problems.
A classic problem when dealing with specifications is understanding why certain decisions were made. Specifications are full of "what" statements such as "A must precede B". If you wrote the specification yourself, six months later, there's a good chance that you'll remember that there was a very good reason why B could not precede A, but not what that reason was. If you didn't write the specification, you probably have no idea if there was a could reason why B could not precede A.
So it's an excellent idea to include "why" statements -- rationales explaining design decisions -- as well. It's also a good idea to mark these statements clearly, to distinguish the specification from the commentary. There are several different kinds of useful commentary, as the following examples show.
These examples are all short, but don't be afraid to make them as long as necessary to fully explain the decision. For example, the second example above could be improved with an explanation why it made C more complicated.
This information is useful in several ways:
I hate warnings; my experience is with compiler warnings, but I think this point extends more broadly to other kinds of systems.
In the case of a compiler, I believe that in most cases warnings indicate flaws in a language or its implementation -- if something is dangerous enough to require a warning, it should be disallowed, or redesigned to be less dangerous. Issuing a warning is a cop-out, an easy way to pretend that you have addressed the problem. It's like building a table, leaving a sharp edge on it, and then posting a sign saying "danger! sharp edge!"
(Obviously if you are implementing a design that you cannot change, the story is different; I'm addressing the case where you are designing the specification.)
Furthermore, smart users treat warnings as errors and always fix them, and dumb users ignore warnings and get their fingers burnt. So it's better to just avoid them.