Schema designs we've rejected

This page is for listing API choices we considered, and rejected.

Most readers probably don't need to be interested in this, but when considering API changes, sometimes the same or similar ideas come up, so it's useful to keep some records of how we weighed and made choices in the past.

Excessive use of unions for inputs and outputs

Something we could do, and considered, is huge unions of stringprefix for the inputs and outputs:

type Ware union {
	| WareTar "tar:"
	| WareGit "git:"
} representation stringprefix

type WareTar struct {
	hash String
}

type WareGit struct {
	hash String
}

Most output types would the same structure, but we repeat it as types anyway just in case:

type Output union {
	| OutputTar "tar:"
	# more fileset pack types go here.
} representation stringprefix

type OutputTar struct {
	hash String
}

Why not:

It's just absurdly verbose and type-spamming and doesn't really help anyone very much.

The example doesn't look too bad, but this list would get out of hand if actually implemented.

What we're doing instead:

Just strings. It's fine.

We could use an enum for packtype without issue, probably, but it doesn't seem super necessary or useful, either, given that there's kiiiinda a plugin sorta thing going on down there.

Attempting to Use Types to describe Sandboxing Degree

We could try to use an abundance of schema types to immediately and clearly describe and verify whether features that mean lesser degrees of sandboxing are being used.

Or we could try, anyway. Here's now that turned out, as a draft:

# FormulaSketch describes an intent to do some stuff.
# FormulaSketch is the vaguest form of Formula,
# and needs to be processed into a FormulaWired,
# and then finally resolved into a FormulaCrystalized,
# before it can be executed.
#
# Almost any kind of input description is allowed in a FormulaSketch,
# including ones that aren't going to be pure or replayable.
type FormulaSketch struct {
	input {InputKey:InputSketch}
	action Action
	ouptut {OutputName:Output}
}

# FormulaWired is the same as FormulaSketch,
# but ingests are no longer allowed (the ingestion must now have been computed),
# nor are 'candidate' references allowed (catalog selection must be done now).
# Catalog references are still allowed -- so, this is a replayable
# instruction, but only assuming a catalog is associated.
type FormulaWired struct {
	input {InputKey:InputWired}
	action Action
	ouptut {OutputName:Output}
}

# FormulaCrystalized is the same has FormulaWired, but even further pinned down.
# It's fully curried, no non-hashed references left -- ready to run.
# (Correspondingly, it's lost a lot of semantic info, such as catalog references,
# meaning this structure has no external references,
# and is nicely content-addressable itself with minimal noise sources,
# but also not very suited to be explainable
# (so we often keep the other forms too)).
type FormulaCrystalized struct {
	input {InputKey:InputCrystalized}
	action Action
	ouptut {OutputName:Output}
}

type InputSketch union {
	| WareID "ware:"
	| Pipe "pipe:"
	| CatalogRef "catalog:"
	| CandidateRef "candidate:" # Like catalog, but dangling a bit.
	| Ingest "ingest:" # Sideeffect rich.
	| Mount "mount:" # Extremely sideeffect rich.
} representation stringprefix

type InputWired union {
	| WareID "ware:"
	| Pipe "pipe:"
	| CatalogRef "catalog:"
} representation stringprefix

type InputCrystalized union {
	| WareID "ware:"
	# Review: okay, irritatingly, we do still want 'mount' here.  Sometimes.
} representation stringprefix

type Action union {
	| ActionExec "exec" # Container execution.  Simplest.
	| ActionScript "script" # Like exec, but easier to use, more debuggable.
	| ActionNoop "noop" # For the rare case where I/O is all you needed.
} representation keyed

type Output struct {
	packType string
	# filters are not defined in this draft, yet.
}

Doing work here with types instead of predicates is kinda nice.

Why not?

Another alternative, also not great:

What we're doing instead:

Different types for the inputs of a formula vs a plot still seem useful, because those are whole different subsystems.

(We could imagine someone building a swappable component that needs to speak formulas, but doesn't need to speak plots (and we've done that before, and probably will do it again), so it's not a bad idea to have some scope cutoff points there.)

Stuff like "mount" are just going in the unions, though. If an API takes it, then we're putting it in the union; that's it. No attempts to do clever logical checks with schemas.

Gather directives being a whole family of types

Tried this:

type GatherDirective struct {
	from SandboxPort
	strategy GatherStrategy
}

# TODO please find a less enterprise name for this.
type GatherStrategy union {
	| Packtype string # in the simple case: just say what pack format to use!
	| GatherStrategyComplex map # if you need filters or something, this.
} representation kinded

type GatherStrategyComplex struct {
	packtype Packtype
	filters FilterMap
}

Why not?

It looked weird in a bunch of ways.

TODO: if your port is a VariableName, do you... really need a GatherStrategy? Nope — we actually need a zero value for that case, idk how to squeeze that in. (We also might want something fancier here in the future too, e.g. if we try to do object passing that's gonna need an opt-in mechanism, so that really complicates things.)