Documentation
¶
Overview ¶
Package resultframe carries a seeded run's project result out of a sandbox whose only channel home is its own output stream (Task 20402).
Why the log ¶
A Kubernetes Pod built by the kubernetes driver has no ServiceAccount token and nothing mounted that leads back to the hub, so it cannot tell anybody anything — and by the time the driver could read a file out of it, the emptyDir holding the file is gone with the Pod. Its log stream is the one channel that survives, which is also how the write-back report comes home (executor.WriteBackSentinel). The project result is a different shape of thing — up to executor.MaxProjectResultBytes of compressed bytes rather than one line of JSON — so it travels as a frame: a block of lines the wrapper prints last, after the harness has exited, and the driver lifts back out of the stream before anybody reads it as a transcript.
The wire format ¶
Every line of a frame starts with Marker, then the frame's tag, then a verb:
##cloop-project-result-v1## <tag> begin <kind> <length> <sha256> ##cloop-project-result-v1## <tag> data <base64> … ##cloop-project-result-v1## <tag> end
The tag is chosen by the driver — the handle ID — and handed to the wrapper, so a line naming another tag is somebody else's text and passes through as transcript. That is what keeps a run that prints this package's own test fixtures, or a nested run's frame, from being mistaken for its own result. The tag is not a secret and is not meant to be one: see "Trust" below.
Each line is written with a single write(2) of at most maxLineBytes, under the kernel's PIPE_BUF, so a line can be preceded or followed by another process's line in the stream but never cut by one. Lines that are not the frame's may sit between the frame's own: in a container log stdout and stderr are merged line by line by the runtime, and the wrapper's own stderr is one of them. They are not part of the frame and do not disturb it.
What is refused ¶
A frame is accepted only whole: declared length and SHA-256 both match what arrived, an end line closed it, and nothing else bearing its tag was seen before or after. Anything else yields an error and no bytes — a truncated frame (the stream ended inside it), an interleaved one (a second begin while the first was open), a duplicated one (a second frame, or a line of one, after the first ended), an unframed one (data or end with no begin) and an oversized one (more than the kind's ceiling declared or delivered). There is no partial result to be had from this package, which is the property the hub's merge depends on: half a project result merged would be a run's outcomes recorded for some of its tasks and not others.
Trust ¶
The workload shares the stream and can print anything, including a frame bearing the right tag. That is contained the same way on every transport:
- it cannot make a frame larger than a device's project_result frame could be — the ceilings are executor.MaxProjectResultBytes for a result and executor.MaxProjectResultErrBytes for a reason, checked before a byte is kept;
- it cannot make the hub take a forged frame over the real one: the wrapper's frame always follows the harness's exit, so a forged frame is always followed by a second one and the pair is refused as duplicated — a workload can deny itself its own result, which corrupting its own database would do just as well;
- and the content of a result is the workload's to decide anyway. A device reads the result out of the database the workload wrote, so a result the workload forged in full claims nothing a result it shaped by writing its database could not. The hub's merge (projectseed.Merge) is where what a result may change is decided, and it treats every result as the least trusted party's account.
Index ¶
Constants ¶
const Marker = "##cloop-project-result-v1##"
Marker begins every line of a frame. The version is in the marker so that a different format can never be read as this one.
Variables ¶
var ( // ErrNoFrame: the stream carried no frame with this scanner's tag. ErrNoFrame = errors.New("no project result frame") // ErrTruncated: a frame began and the stream ended, or its end line // arrived, before every byte it declared. ErrTruncated = errors.New("the project result frame was cut off") // ErrDuplicate: a second frame, or a line of one, bearing the same tag — // before the first ended (interleaved) or after it (duplicated). ErrDuplicate = errors.New("more than one project result frame") // ErrMalformed: a line bearing the tag that is not a valid frame line, a // data or end line with no frame open, or a payload whose length or // checksum is not the one declared. ErrMalformed = errors.New("malformed project result frame") // ErrTaken: the frame was accepted and has already been handed out. ErrTaken = errors.New("the project result frame was already collected") // ErrDropped: the frame was accepted and then let go before it was // collected (see Scanner.Drop). ErrDropped = errors.New("the project result frame was dropped before it was collected") )
Errors a scanner reports. Each is wrapped with a sentence saying what was seen; errors.Is matches the class.
Functions ¶
func ValidTag ¶
ValidTag reports whether tag can label a frame: 1–64 characters of letters, digits, '.', '_' and '-' — a single field on a space-separated line, and nothing a terminal or a log viewer would interpret.
func Write ¶
Write renders payload as one frame tagged tag onto w.
Every line goes out in a single Write call, which for a pipe is a single write(2) under PIPE_BUF and therefore atomic. The frame is preceded by an empty line: the workload may have left a line unfinished on the same stream, and a begin line appended to it would not start a line and would not be recognised. The cost is one blank line in the transcript.
An error describes the input (Write refuses a frame no scanner would accept) or the first failed write; a frame cut off by a failed write is one the scanner will refuse as truncated, never one it will accept.
Types ¶
type Frame ¶
type Frame struct {
Kind Kind
// Payload is exactly the bytes that were declared, length and checksum
// verified. It is the scanner's own allocation, sized to the declared
// length, so retaining it retains nothing else.
Payload []byte
}
Frame is a frame a scanner accepted.
type Kind ¶
type Kind string
Kind is what a frame carries.
const ( // KindResult carries the compressed project result document // (projectseed.Harvest's output). KindResult Kind = "result" // KindError carries the wrapper's reason for having no document: the // workload removed its project, the database would not open. Plain text. KindError Kind = "error" )
type Scanner ¶
type Scanner struct {
// contains filtered or unexported fields
}
Scanner separates one tag's frame from the stream it is embedded in.
Feed it the stream in whatever chunks it arrives; it returns the transcript — every byte that is not a line of the frame, in order — and keeps the frame. Only an unfinished line that could still become a frame line is held back, so ordinary output is forwarded as soon as it diverges from the marker, and a workload printing gigabytes without a newline costs nothing.
It is safe for concurrent use. The zero value is not usable; call NewScanner.
func NewScanner ¶
NewScanner returns a scanner for frames tagged tag.
func (*Scanner) Buffered ¶
Buffered reports how many bytes of an unfinished line the scanner is holding, so a test can pin the bound.
func (*Scanner) Close ¶
Close marks the end of the stream and returns whatever the scanner was still holding that turned out to be transcript — the start of a last line that never ended and never became a frame line. A frame still open is truncated. Close is idempotent.
func (*Scanner) Drop ¶
Drop lets go of an accepted frame that has not been collected, so a holder with a memory budget can evict it. A later Take reports ErrDropped with why. It reports how many bytes were released.
func (*Scanner) Feed ¶
Feed consumes one chunk of the stream and returns the part of it that is transcript. After Close every byte is transcript.
func (*Scanner) Result ¶
Result reports the frame without handing it out: what Take would return now, except that the frame stays. For a summary line, say.
func (*Scanner) Take ¶
Take returns the accepted frame once and lets go of it: a second call reports ErrTaken. A result is handed out once because merging the same run twice would book its spend twice.
func (*Scanner) WithheldLines ¶
WithheldLines reports how many lines of frame protocol the scanner kept out of the transcript.