Define the v0 publish manifest, parse it via std.json into a typed
struct, and validate it.
- src/manifest/manifest.sx: typed Manifest { app, version, channel,
artifacts: List(ManifestArtifact) } + ManifestArtifact { platform,
path, filename, content_type, metadata }. parse_manifest walks the
std.json Value tree into the struct, surfacing every malformed/
missing/wrong-type field as a distinct typed ManifestErr (BadJson,
WrongType, MissingField, UnknownPlatform, MissingArtifact, Io) — never
a silent default. Reuses P2.1 parse_platform for the platform id.
validate_manifest checks each artifact path exists on disk (fs.exists),
resolved relative to the manifest's directory. Strings are copied into
the caller's allocator (long-lived-container rule).
- examples/dist.json: representative valid manifest (android_apk + ios).
- examples/fixtures/: tiny stand-in artifact byte files referenced by it.
- tests/manifest_parse.sx: parses dist.json and asserts fields; asserts
the three failure classes surface distinct typed errors.
Stand up the foundation every later step depends on:
- Source layout: src/, src/infra/, tests/, examples/ (.gitkeep markers).
- Makefile: `build` compiles the smoke program via $SX, `test` runs the
runner over tests/**/*.sx, `publish-example` placeholder (real in P3.4).
Compiler located via `SX ?= /Users/agra/projects/sx/zig-out/bin/sx`.
- tests/run.sh: POSIX-sh runner; discovers tests/**/*.sx, runs each via
`$SX run`, prints ok/FAIL, exits 0 only when all pass (errors on zero
tests so the gate is never silently empty).
- tests/smoke.sx: passing smoke test importing modules/std.sx — proves
toolchain wiring end-to-end (std resolves via the binary's own location).
- .gitignore: ignore build/ artifacts.