Skip to content

ADR-015 S6a: WIT export lifting for WASI command components#343

Merged
hyperpolymath merged 2 commits into
mainfrom
claude/lucid-pascal-0xL5X
May 24, 2026
Merged

ADR-015 S6a: WIT export lifting for WASI command components#343
hyperpolymath merged 2 commits into
mainfrom
claude/lucid-pascal-0xL5X

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Summary

Implements ADR-015 S6a (WIT export lifting) to enable AffineScript programs to be invoked as WASI commands via wasmtime run and other WASI 0.2 hosts. When a parameter-less fn main() is present, the compiler now auto-emits a _start : () -> () shim that calls main and drops its result, allowing the command adapter to lift this into a wasi:cli/run export.

Key Changes

Compiler (lib/codegen.ml)

  • Auto-emit _start : () -> () shim whenever a parameter-less fn main() is present
  • Shim calls main and drops its i32 result via Drop instructions
  • Purely additive: existing reactor consumers, game-loop hooks, and __indirect_function_table exports are byte-unchanged
  • Skips if _start already exists or main has parameters

Toolchain (tools/provision-component-toolchain.sh)

  • Provision both reactor and command adapters from wasmtime v44.0.1
  • Add ADAPTER_COMMAND_URL and ADAPTER_COMMAND_SHA256 (8ff2ea78d31179f12d6fb1d2cadc0ab83356cd049f362d5a8d94a4070c7a15bd)
  • Refactor adapter fetching into reusable fetch_adapter() function
  • Maintain backward-compatibility aliases (ADAPTER_URL, ADAPTER_SHA256, ADAPTER_PATH) pointing to reactor

Componentizer (tools/componentize.sh)

  • Add --command and --reactor mode flags (reactor is default)
  • Select appropriate adapter based on mode
  • Validate _start export presence in command mode (fail-fast with pointer to codegen contract)
  • Post-componentization, assert wasi:cli/run export exists in command mode (catches adapter/lift regressions)
  • Updated usage and error messages to reflect both modes

Testing (tests/componentize/command_smoke.sh)

  • New end-to-end smoke test for S6a contract
  • Compiles fixture with parameter-less main, componentizes with --command
  • Asserts: valid WASI-0.2 component, wasi:cli/run export, wasmtime run invocation succeeds (exit 0), ownership section survives
  • SKIP-safe when toolchain or wasmtime not provisioned

Documentation

  • Updated ECOSYSTEM.adoc and TECH-DEBT.adoc to reflect S6a completion
  • Updated SETTLED-DECISIONS.adoc with S6a details and remaining sub-slices (S5, S6b, S6c)
  • Updated .machine_readable/6a2/META.a2ml with S6a ledger entry and staged plan breakdown

Implementation Details

  • The _start shim is inserted as a new function in the module's function list, with its index recorded in the exports
  • Type interning ensures the () -> () type is reused if it already exists
  • The shim's body is a single Call to main followed by Drop instructions for each result type
  • Command mode validation uses wasm-tools print to check for _start export and wasm-tools component wit to verify the lift
  • All adapter fetches are checksum-verified with fail-closed semantics

https://claude.ai/code/session_01NK8KaMbWEwJZfvmrq8Nm8X

…oke)

Lift a parameter-less `fn main()` into the WASI 0.2 (preview2) command
shape so `wasmtime run <component>` actually runs an AffineScript
program end-to-end, closing the "real-host main-invoke" gap left open
by S3 (reactor-only componentize) and S4a/b (preview1 import bridging).

Codegen (lib/codegen.ml):
- After exports_with_mem is assembled, when `main` is exported and
  takes no parameters, append a `_start : () -> ()` shim whose body
  is `Call main_idx; Drop` (drops main's i32 result — current ABI is
  uniformly i32-returning even for `() -> ()`). Skips silently if a
  user already exports `_start` or `main` has parameters.
- Purely additive: every existing consumer (reactor componentize,
  game-loop hosts, `__indirect_function_table` for closures) sees the
  same exports plus an extra `_start`. Lambda-table elem indices are
  unaffected (start_func is appended after lambdas in all_funcs).

Tooling:
- tools/provision-component-toolchain.sh now fetches both the reactor
  and command adapters (wasmtime v44.0.1), each sha256-pinned and
  fail-closed. Back-compat: the S3-era `ADAPTER_URL`/`_SHA256`/`_PATH`
  variables remain as reactor-aliased exports.
- tools/componentize.sh learns `--command` / `--reactor` modes;
  `--command` selects the command adapter, fails fast with a pointer
  to the codegen contract if the core is missing `_start`, and
  asserts `wasi:cli/run` is exported in the resulting component.

Gate:
- tests/componentize/command_smoke.sh — opt-in, SKIPs cleanly without
  the toolchain or wasmtime; otherwise compiles a fixture, asserts the
  `_start` shim, runs the command componentize path, asserts the
  ownership section survives + `wasi:cli/run` is lifted, and runs the
  component under `wasmtime run` (exit 0 is the contract).

Verified end-to-end on this branch: `wasmtime run` succeeds on a
real fresh-compiled component (`fn consume(x) { x }` /
`fn main() { consume(42) }`), the existing S3 reactor smoke still
passes, and the full OCaml test suite stays green (295 tests).

Docs:
- TECH-DEBT.adoc / ECOSYSTEM.adoc INT-03 rows: S6 split into S6a
  (WIT export lifting, DONE) + S6b (sockets) + S6c (default-flip).
- META.a2ml ADR-015 decision text mirrors the split.
- SETTLED-DECISIONS.adoc mirror updated in lockstep.

WIT world (`wit/affinescript.wit`) unchanged — already the contract
of record; the command-mode output exports a subset (`wasi:cli/run`
plus the adapter's required imports). Sockets + default-target flip
remain in scope for S6b/S6c per the ADR.

Refs #180
@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 143 issues detected

Severity Count
🔴 Critical 13
🟠 High 69
🟡 Medium 61

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Stray AI.a2ml in root -- use 0-AI-MANIFEST.a2ml only",
    "type": "banned",
    "file": "AI.a2ml",
    "action": "delete",
    "rule_module": "root_hygiene",
    "severity": "high"
  },
  {
    "reason": "Superseded by 0-AI-MANIFEST.a2ml",
    "type": "banned",
    "file": "AI.djot",
    "action": "delete",
    "rule_module": "root_hygiene",
    "severity": "high"
  },
  {
    "reason": "Issue in quality.yml",
    "type": "missing_workflow",
    "file": "quality.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Issue in security-policy.yml",
    "type": "missing_workflow",
    "file": "security-policy.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Action actions/checkout@v6 needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action denoland/setup-deno@v2 needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath hyperpolymath enabled auto-merge (squash) May 24, 2026 04:52
Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
@hyperpolymath hyperpolymath merged commit 23a9e94 into main May 24, 2026
12 of 16 checks passed
@hyperpolymath hyperpolymath deleted the claude/lucid-pascal-0xL5X branch May 24, 2026 05:06
@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 142 issues detected

Severity Count
🔴 Critical 13
🟠 High 68
🟡 Medium 61

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Stray AI.a2ml in root -- use 0-AI-MANIFEST.a2ml only",
    "type": "banned",
    "file": "AI.a2ml",
    "action": "delete",
    "rule_module": "root_hygiene",
    "severity": "high"
  },
  {
    "reason": "Superseded by 0-AI-MANIFEST.a2ml",
    "type": "banned",
    "file": "AI.djot",
    "action": "delete",
    "rule_module": "root_hygiene",
    "severity": "high"
  },
  {
    "reason": "Issue in quality.yml",
    "type": "missing_workflow",
    "file": "quality.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Issue in security-policy.yml",
    "type": "missing_workflow",
    "file": "security-policy.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action hyperpolymath/standards/.github/workflows/governance-reusable.yml@main needs attention",
    "type": "unpinned_action",
    "file": "governance.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Action actions/checkout@v6 needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Action denoland/setup-deno@v2 needs attention",
    "type": "unpinned_action",
    "file": "publish-jsr.yml",
    "action": "pin_sha",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/example/smoke_driver.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/cli.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  },
  {
    "reason": "TypeScript file detected -- banned language",
    "type": "banned_language_file",
    "file": "/home/runner/work/affinescript/affinescript/affinescript-deno-test/mod.ts",
    "action": "flag",
    "rule_module": "cicd_rules",
    "severity": "critical"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants