# Future

- [ ] Nix builds and environments — see {ref}`arch-nix-builds`
- [ ] Worker/agent split — see {ref}`arch-worker-agent-split`
- [ ] Container executor (Docker/Podman for local use)
- [ ] MicroVM executor
- [ ] JSON Schema generation for pipeline format (`schemars`)
- [ ] GitHub webhook support (different signature format:
  ~X-Hub-Signature-256~ with ~sha256=~ prefix)


## Schema-driven workflow parsing for Forgejo / Github Actions

Both the GitHub Actions runner and the Forgejo runner define the
workflow YAML structure in schema files:

- GitHub: `workflow-v1.0.json` (MIT license) in
  `src/Sdk/WorkflowParser/`, embedded as a .NET resource
- Forgejo: `workflow_schema.json` (GPL-3.0, compatible with AGPL) in
  `act/schema/`, embedded via Go's `//go:embed`

The schemas use the same custom format. Each field definition includes
a `context` array specifying which expression contexts (`github`,
`inputs`, `env`, `vars`, etc.) are valid at that position. The Forgejo
runner uses this at runtime to validate `${{ }}` expressions against
the allowed contexts per field.

MyCI currently uses handwritten serde structs for workflow parsing.
Long-term, adopting the upstream schema would:

- Keep field support in sync with Forgejo/GitHub
- Validate expression contexts per field automatically
- Reduce manual maintenance when adding new fields

### Approaches

**Runtime**: Parse YAML into a generic value tree, walk it guided by
the schema, validate and extract values. Replace serde structs with a
schema walker that produces the same domain types. This is what both
runners do.

**Compile-time**: Use `build.rs` to read the schema JSON and generate
Rust code — either a static context lookup table (field path to
allowed expression contexts) or full serde structs. This gives
compile-time verification that the schema is consistent, while keeping
the typed domain model.

**Hybrid**: Keep hand-written domain types (`Pipeline`, `Job`, `Step`)
but generate the expression context table from the schema at compile
time. Serde handles the structural parsing, the generated table drives
expression validation.

### Proc macros as schema bridge

Rust procedural macros run at compile time and can read external files.
A custom derive macro could read the upstream schema JSON and use it
to adjust generated Rust code:

```rust
#[derive(Deserialize, SchemaValidated)]
#[schema("workflow_schema.json", definition = "job")]
struct JobDef {
    env: HashMap<String, String>,
    // ...
}
```

The macro would:

- Read the schema at compile time, no runtime cost
- Verify that struct fields match the schema definition — compile
  error if they drift apart
- Generate expression context metadata per field
- Generate allowed-values validation where the schema defines them

This makes the upstream schema the single source of truth. When the
schema is updated (e.g. new Forgejo release), a recompile catches
any mismatches. The developer maintains the domain model, the macro
fills in the mechanical validation parts.

### Target picture: schema-scoped expression contexts

The schema defines per field which expression contexts are available.
For example, `job.if` allows `[github, needs, vars, inputs]` while
`step.run` allows `[github, env, vars, inputs, steps]`. This
information should flow into the domain model so that expression
evaluation is constrained by design, not by convention.

**Parse time**: When a field contains `${{ secrets.TOKEN }}` but the
schema says only `[github, env]` are available at that position,
reject it immediately with a clear error. No need to attempt
evaluation — the schema already says it's invalid.

**Evaluation time**: The expression evaluator receives only the
contexts the schema permits for that field. It doesn't need runtime
checks — the data it's given is already correctly scoped.

**Domain model**: Each field that supports expressions carries its
allowed contexts as metadata. This could be:

- Type-level (different types per context set, maximum safety, verbose)
- Runtime tag (enum or bitfield of allowed contexts per field, pragmatic)
- Generated constant (proc macro emits a static table from the schema)

The key principle: the schema is the contract, the code enforces it
at the boundary (parsing), and the evaluator operates within the
contracted scope. Any access to a context not listed in the schema
is a user error caught early, not a runtime surprise.
