Skip to content

fix: declare IO.NetListen; adapt to FileError, ambiguous ctors, and stricter default-arg checking - #5

Merged
Ch4s3 merged 7 commits into
mainfrom
fix/netlisten-cap
Jul 29, 2026
Merged

Ch4s3 merged 7 commits into
mainfrom
fix/netlisten-cap

Conversation

@Ch4s3

@Ch4s3 Ch4s3 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Everything needed to build bastion against current march main. Found while moving forgepm onto it.

Capabilities

march makes capability propagation through imports an error rather than a warning: a module importing HttpServer (directly, or via Conn) must declare needs IO.NetListen itself. Added to the 21 modules that do. The count kept growing as it propagated transitively — upload, auth, rate_limit, cors, channel_server, error_overlay only surfaced after the first round.

FileError

File.* now returns Result(_, FileError), a variant, not Result(_, String). Six sites concatenated the error straight into a message; they go through to_string, which renders the underlying text verbatim ("No such file or directory" — no constructor noise). Forge.Routes.generate also leaked a FileError out of a Result(_, String); converted at the boundary.

Ambiguous constructors

Header is ambiguous between Http.Header and WebSocket.Header; Conn and NoUpgrade likewise. Every site here destructures values from HttpServer, so they qualify to Http.Header / HttpServer.Conn / HttpServer.NoUpgrade.

Form.render

A required parameter after defaulted ones does not type-check: the reduced-arity desugaring matches the trailing argument against the first default's type, so render_inner was checked against method : String. Not a regression — the shape fails on older toolchains too; earlier errors simply masked it. There are no call sites in bastion or forgepm, so render_inner moves ahead of the defaults; Form.render(conn, action, fn -> ...) still reads naturally. Happy to delete the function instead if you'd rather.

Testing helpers

lib/testing/test_auth.march and lib/testing/test_conn.march are not in bastion's own forge check graph, so their capability and ambiguity errors only surface when a consumer's test build pulls them in — which, before march-language/march#106 stopped pruning sibling test modules, never happened.

Verification

forge check against march main + march-language/march#106: 44 errors → 0. forgepm builds and runs its full 544-test suite green against this.

Depends on march-language/depot#1 for the depot-side constructor ambiguities that also show up in bastion's dependency graph.

Ch4s3 added 4 commits July 28, 2026 17:15
march main makes capability propagation through imports an ERROR rather
than a warning: a module importing HttpServer (directly or via Conn) must
declare `needs IO.NetListen` itself. Adds it to the 21 modules that do.

Two further adaptations to current main:

  - File.* now returns Result(_, FileError), a variant, not Result(_, String).
    Six sites concatenated the error straight into a message; they now go
    through to_string, which renders the underlying message verbatim
    ("No such file or directory"). Forge.Routes.generate also propagated a
    FileError out of a Result(_, String), now converted at the boundary.

  - `Header` is ambiguous between Http.Header and WebSocket.Header. Both
    sites destructure headers from HttpServer.req_headers/resp_headers, so
    both qualify as Http.Header.

Takes `forge check` against main-a5c3dc70 from 44 errors to 18. The rest
are not bastion's: 17 ambiguous JoinKind/SortDir constructors (Ast vs
DataFrame) inside depot, plus Form.render, whose trailing required
`render_inner` after three defaulted params does not type-check under the
stricter checking — that signature needs a decision, not a mechanical fix.
A required parameter after defaulted ones does not type-check: the
reduced-arity desugaring matches the trailing argument against the first
default's type, so `render_inner` was checked against `method : String`
and `enctype`'s default against `render_inner`. Not a regression — the
shape fails on main-8c87ccb2 too; earlier errors simply masked it.

No call sites exist in bastion or forgepm, so reordering breaks nothing,
and `Form.render(conn, action, fn -> ...)` still reads naturally.
Missed by the earlier sweep: the testing helpers are not in bastion's own
`forge check` graph, so the requirement only surfaced when a consumer's
test build pulled Bastion.Test.Auth in.
The testing helpers are not in bastion's own `forge check` graph, so
these only surface when a consumer's test build pulls them in — which,
before the resolver kept sibling test modules, never happened.

  - `needs IO.NetListen` + `needs IO.Clock` (both propagate in through
    HttpServer).
  - `Conn`, `Header` and `NoUpgrade` are each declared by two modules in
    scope; every use here means the HTTP one, so they qualify to
    HttpServer.Conn / Http.Header / HttpServer.NoUpgrade.
Ch4s3 added 3 commits July 28, 2026 22:56
`write(path)(content)` is no longer accepted — March functions are not
curried, and the parser now rejects `f(...)(...)` outright. The three
scaffolding generators build their files through such a writer; they take
(path, content) as one call now.
Until march-language/march#106, reachability pruning dropped every test
module except the entry, so `forge test` compiled one file and passed.
These five files had not been compiled in a long time.

  - test_form: `Form.Wrapper.render` call sites updated to the parameter
    order this branch introduced (they exist after all — my earlier claim
    that nothing calls it came from grepping `Form.render`, and the call
    is spelled `Form.Wrapper.render`); `import Bastion.Test.Conn` and the
    capabilities it propagates.
  - bastion_test: 25 ambiguous `Header` constructors qualified to
    Http.Header; `needs IO.NetListen`.
  - test_gate: LenMin/LenMax/NumMin/NumMax qualified to Gate.* — depot's
    Gate declares the same names.
  - test_html_css: `import Css`.
  - test_islands_dataflow: `needs IO.NetListen`.

Form's components pointed at the WRONG Gate. `Depot.Gate.error_for` does
not exist, so Form.Input/Select/Error failed to link the moment something
pulled them into an executable — invisible while the tests were pruned,
and invisible to `forge build`, which only ever builds a library. The
components' callers build with `Gate.cast` (bastion's own
lib/security/gate.march), and that module has exactly the API in use:
error_for/2 and get_field/2. Repointed all 12 references.

bastion's suite: 81 compile errors -> 0, and 6 runtime failures -> 0.
`forge build` does not fetch deps. On a fresh runner the CAS is empty, so
depot was never cloned and every module it provides reported as unknown —
`Unknown module \`Pool\`. Did you mean \`Plot\`?` from
depot_middleware. Pre-existing; it looked like a depot problem.
@Ch4s3
Ch4s3 merged commit aaa7a7c into main Jul 29, 2026
3 of 4 checks passed
Ch4s3 added a commit that referenced this pull request Jul 29, 2026
Ships #5: the IO.NetListen capability declarations every module importing
HttpServer now needs, the FileError adaptations, the ambiguous-constructor
qualifications, and Form.render's parameter reorder.

Needed as a RELEASE, not just a merge: forgepm resolves bastion from the
registry (`bastion = "0.2.3"`), so its CI still typechecks the published
0.2.3 sources and fails on exactly the errors #5 fixed.
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.

1 participant