Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c7ddeab
docs: track full shared boundary contracts implementation
marsninja Sep 7, 2026
d8176e7
fix(client): fence cached reads across writes and identity changes
marsninja Sep 7, 2026
5640f6a
feat: carry boundary contracts into native bindings and endpoint runt…
marsninja Sep 7, 2026
de45c52
feat(native): check host implementations and generate registration ad…
marsninja Sep 7, 2026
844c072
feat(compiler): persist boundary contracts and qualify runtime endpoi…
marsninja Sep 7, 2026
91f8278
feat: enforce opaque native access and document contract APIs
marsninja Sep 7, 2026
6315060
fix: preserve boundary finalization and fence walker writes
marsninja Sep 7, 2026
eb11934
fix: retain lifetime dependencies and explicit effect assumptions
marsninja Sep 7, 2026
77c2420
fix: attach contracts to inline server calls
marsninja Sep 7, 2026
57484df
fix: keep native type imports out of client placement
marsninja Sep 7, 2026
6234919
fix: validate native scalar argument ranges
marsninja Sep 7, 2026
bdbb6ca
docs: record application integration findings
marsninja Sep 7, 2026
80268c7
fix: preserve native crossings during client placement
marsninja Sep 7, 2026
9d02744
fix: preserve native shared borrow permissions
marsninja Sep 7, 2026
46c9228
fix: use AST unparse helper and expose selfhost diagnostics
marsninja Sep 7, 2026
0d979a4
fix: defer effect unparsing until a declaration is found
marsninja Sep 7, 2026
e591284
fix: type browser host objects and signup response conversions
marsninja Sep 7, 2026
23067a2
fix: resolve host and opaque handle scopes from inferred types
marsninja Sep 7, 2026
ec94e76
docs: record contract integration fixes and pending acceptance
marsninja Sep 7, 2026
44562d5
fix: derive native browser aliases from the runtime registry
marsninja Sep 7, 2026
b146e36
fix: emit valid host callbacks and avoid duplicate runtime globals
marsninja Sep 7, 2026
6c7b8b1
Preserve primitive BigInt range checks in Wasm adapters
marsninja Sep 7, 2026
6b1d033
Record successful browser integration flows
marsninja Sep 7, 2026
4aef60a
Retain boundary contracts in cached client artifacts for deployment a…
marsninja Sep 7, 2026
fd39508
Update scope status and record final audit verification work
marsninja Sep 7, 2026
83a8962
Finalize implementation scope and record audit verification waiver
marsninja Sep 7, 2026
a9b0403
docs: replace implementation progress file with migration release note
marsninja Sep 7, 2026
bb1a646
fix: make endpoint identities portable and migrate contract validation
marsninja Sep 7, 2026
1e9efa7
style: normalize spacing after WebGL host lint fixes
marsninja Sep 7, 2026
ad7d8e4
fix: align sealed cache format and use runtime typing reflection
marsninja Sep 7, 2026
86fafc4
Fix shared boundary integration across packaged runtimes and CI
marsninja Sep 7, 2026
b39fc79
Derive Wasm integer bounds from the fixed-width ABI shape
marsninja Sep 7, 2026
5f62879
Compile runtime dependency graphs and keep Wasm analysis in the bound…
marsninja Sep 7, 2026
107d681
Reuse the validated runtime source path when staging client tests
marsninja Sep 7, 2026
911df46
Check JSX factory bindings independently of generated source headers
marsninja Sep 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions jac/examples/day_planner/auth/components/AuthForm.jac
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def:pub AuthForm(onAuth: Callable[[], None]) -> JsxElement {
}
loading = True;
result = await jacSignup(username, password);
if result["success"] {
if result.success {
# /user/register creates the account but does not return a
# session token; sign in immediately to establish one.
logged_in = await jacLogin(username, password);
Expand All @@ -53,7 +53,7 @@ def:pub AuthForm(onAuth: Callable[[], None]) -> JsxElement {
}
} else {
loading = False;
error = str(result["error"]) if result["error"] else "Signup failed";
error = str(result.error) if result.error else "Signup failed";
}
}

Expand Down
4 changes: 2 additions & 2 deletions jac/examples/day_planner/walkers/components/AuthForm.jac
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def:pub AuthForm(onAuth: Callable[[], None]) -> JsxElement {
}
loading = True;
result = await jacSignup(username, password);
if result["success"] {
if result.success {
# /user/register creates the account but does not return a
# session token; sign in immediately to establish one.
logged_in = await jacLogin(username, password);
Expand All @@ -53,7 +53,7 @@ def:pub AuthForm(onAuth: Callable[[], None]) -> JsxElement {
}
} else {
loading = False;
error = str(result["error"]) if result["error"] else "Signup failed";
error = str(result.error) if result.error else "Signup failed";
}
}

Expand Down
37 changes: 18 additions & 19 deletions jac/examples/jaclang_org/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,25 +251,24 @@ and the browser host reaches it with a plain import:
import from .arena { init }
```

That one line in `core/site/game/webgl_host.jac` is the whole wiring. Because the host
is client code and the target is native-anchored, the import IS the cl→na
edge: the client build compiles the module to `/static/arena.wasm`, binds
`init` to a generated stub that lazily instantiates the wasm on first call
(via `@jac/wasm_host`), and compiles to nothing on the server -- the same
import in a pure server module would still mean the ctypes crossing. Because arena declares app FFI, the host registers its WebGL
implementations first with `set_na_env("arena", sh, {"env": ...})`; an
FFI-free native module would need no ceremony at all.

Its memory story is the point: `[gc] default = "none"` builds it headerless --
no reference counting, no collector, static drops only -- and the build audits
the emitted IR for `__rc_*` machinery, so a wasm that re-entered the RC world
fails to build rather than shipping. The ownership checker's source-level
zero-RC contract (`[memory]`, E140x hard errors) ships disarmed until a
release carries jaseci-labs/jac#7732 -- the 0.34.x checker misfires E1401 on
arena's raylib extern decls; jac.toml says exactly when and how to re-arm
it. Entity pools are index arenas (parallel
scalar lists, the `own_rbtree` idiom) inside one `own Game` the browser holds
as an opaque handle; every update pass borrows it `&mut` down the call tree.
The compiler generates typed Wasm calls from the native declarations, including
scalar conversions and opaque ownership handles. The application imports `init`,
`frame`, the score/health accessors, and `shutdown` normally. It supplies Jac's
reusable `@jac/webgl` host with `bind_na_host(init, host)`; host methods are checked
against the native import declarations. Module instantiation and host-import
registration belong to `@jac/wasm_host`.

Production client bundles include `/static/boundaries.json`. This audit records
qualified endpoint identities, placements, callers, value shapes, native ownership
contracts, host requirements, and effect assumptions. The compiler retains these
records in cached client artifacts so they survive release of its syntax trees.
Unknown effects disable endpoint caching; explicit effect declarations are
reported as assumptions, not inferred guarantees.

The game's `[memory]` enforcement remains declared in `jac.toml`. Entity pools
are index arenas inside an owned `Game`; update passes borrow it mutably, and
`shutdown` consumes its browser handle.

The same source also builds headlessly:

```bash
Expand Down
2 changes: 1 addition & 1 deletion jac/examples/jaclang_org/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ signed-in user.
| `commands/score.jac` | The offline scorer: `parse_repo_ref`, `score_repo` (the exact pipeline `core/scoring_service.jac` runs), `as_dict`, `render`, `run_score` |
| `commands/docs.jac` | `run_docs`: bridges to `docs_sync_tick` / `docs_status` (owned by `web`) |
| `commands/feed.jac` | `run_feed`, `run_post`: bridges to the `social_graph` walkers; `FeedLine` is the CLI's own view of a reported tweet |
| `commands/common.jac` | Exit codes, `explain_bridge_error` (one place that turns the `BridgeError` family into a code and a hint), and the `str_field` / `int_field` / `list_field` readers that accept a rehydrated object or a wire dict alike |
| `commands/common.jac` | Exit codes and `explain_bridge_error`; endpoint consumers use declared record fields |

## Tests

Expand Down
22 changes: 0 additions & 22 deletions jac/examples/jaclang_org/cli/commands/common.jac
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,3 @@ def explain_bridge_error(e: BridgeError) -> int {
warn("jacorg: " + where + " failed" + _suffix(e.detail));
return EXIT_FAILURE;
}

def field_of(raw: object, name: str) -> object {
if isinstance(raw, dict) {
return raw.get(name);
}
return getattr(raw, name, None);
}

def str_field(raw: object, name: str) -> str {
v = field_of(raw, name);
return "" if v is None else str(v);
}

def int_field(raw: object, name: str) -> int {
v = field_of(raw, name);
return v if isinstance(v, int) else 0;
}

def list_field(raw: object, name: str) -> list[object] {
v = field_of(raw, name);
return list(v) if isinstance(v, list) else [];
}
27 changes: 0 additions & 27 deletions jac/examples/jaclang_org/cli/commands/common.test.jac
Original file line number Diff line number Diff line change
@@ -1,33 +1,6 @@
import io;
import from contextlib { redirect_stderr }

obj Probe {
has label: str = "v1",
pages: int = 3,
tags: list[str] = ["a", "b"];
}

test "field helpers read objects and dicts alike" {
p = Probe();
d = {"label": "v1", "pages": 3, "tags": ["a", "b"]};
assert str_field(p, "label") == "v1";
assert str_field(d, "label") == "v1";
assert int_field(p, "pages") == 3;
assert int_field(d, "pages") == 3;
assert list_field(p, "tags") == ["a", "b"];
assert list_field(d, "tags") == ["a", "b"];
}

test "field helpers default when a field is missing or mistyped" {
d = {"pages": "three", "tags": "nope"};
assert str_field(d, "label") == "";
assert int_field(d, "pages") == 0;
assert list_field(d, "tags") == [];
assert str_field(None, "label") == "";
assert int_field(42, "pages") == 0;
assert list_field("text", "tags") == [];
}

test "bridge failures map to distinct exit codes and hints" {
buf = io.StringIO();
with redirect_stderr(buf) {
Expand Down
10 changes: 5 additions & 5 deletions jac/examples/jaclang_org/cli/commands/docs.jac
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import from core.docs.graph { DocsStatus, docs_status }
import from core.docs.sync { docs_sync_tick }
import from jaclang.server.bridge { BridgeError }
import from .common { EXIT_OK, explain_bridge_error, int_field, str_field }
import from .common { EXIT_OK, explain_bridge_error }

def render_status(st: DocsStatus) -> str {
if not st.ready {
Expand All @@ -13,13 +13,13 @@ def render_status(st: DocsStatus) -> str {
for v in st.versions {
lines.append(
" "
+ str_field(v, "label")
+ v.label
+ " jac "
+ str_field(v, "jac_version")
+ v.jac_version
+ " "
+ str(int_field(v, "pages"))
+ str(v.pages)
+ " pages fetched "
+ str_field(v, "fetched_at")
+ v.fetched_at
);
}
return "\n".join(lines);
Expand Down
38 changes: 17 additions & 21 deletions jac/examples/jaclang_org/cli/commands/feed.jac
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import from core.social_graph { create_tweet, load_feed }
import from jaclang.server.bridge { BridgeError }
import from .common {
EXIT_OK,
EXIT_USAGE,
explain_bridge_error,
list_field,
str_field,
warn
}
import from .common { EXIT_OK, EXIT_USAGE, explain_bridge_error, warn }

obj FeedLine {
has author: str = "",
Expand All @@ -17,15 +10,6 @@ obj FeedLine {
comments: int = 0;
}

def feed_line(raw: object) -> FeedLine {
return FeedLine(
author=str_field(raw, "author_username"),
content=str_field(raw, "content"),
created_at=str_field(raw, "created_at"),
likes=len(list_field(raw, "likes")),
comments=len(list_field(raw, "comments"))
);
}

def render_line(line: FeedLine) -> str {
stamp = line.created_at[:19].replace("T", " ");
Expand All @@ -48,7 +32,17 @@ async def run_feed(limit: int, query: str) -> int {
return EXIT_OK;
}
for raw in rows[:limit] {
print(render_line(feed_line(raw)));
print(
render_line(
FeedLine(
author=raw.author_username,
content=raw.content,
created_at=raw.created_at,
likes=len(raw.likes),
comments=len(raw.comments)
)
)
);
}
if len(rows) > limit {
print("... " + str(len(rows) - limit) + " more; raise --limit to see them");
Expand All @@ -67,10 +61,12 @@ async def run_post(text: str) -> int {
}
try {
made = await create_tweet(content=body);
posted = list_field(made, "reports");
posted = made.reports;
if posted {
line = feed_line(posted[0]);
print("posted as @" + (line.author or "you") + ": " + line.content);
tweet = posted[0];
print(
"posted as @" + (tweet.author_username or "you") + ": " + tweet.content
);
} else {
print(
"posted nothing: the site found no profile for this identity "
Expand Down
3 changes: 3 additions & 0 deletions jac/examples/jaclang_org/core/github.jac
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import from jaclang.data.contracts { JsonEndpoint }
import from core.github_contracts { GithubRepository, GithubCommit }

glob API_ROOT: str = "https://api.github.com/repos/",
CODELOAD_ROOT: str = "https://codeload.github.com/",
CHUNK_BYTES: int = 262144;
Expand Down
25 changes: 25 additions & 0 deletions jac/examples/jaclang_org/core/github_contracts.jac
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
obj GithubUser {
has id: int,
login: str,
avatar_url: str = "";
}

obj GithubRepository {
has full_name: str,
name: str,
owner: GithubUser,
private: bool,
description: str | None = None,
html_url: str = "",
default_branch: str = "main",
stargazers_count: int = 0,
forks_count: int = 0,
size: int = 0,
topics: list[str] = [],
pushed_at: str | None = None,
created_at: str = "";
}

obj GithubCommit {
has sha: str;
}
72 changes: 30 additions & 42 deletions jac/examples/jaclang_org/core/impl/github.impl.jac
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import re;
import io;
import os;
import json;
import tarfile;
import time;
import urllib.error;
Expand Down Expand Up @@ -57,56 +56,45 @@ impl parse_repo_url(url: str) -> tuple[str, str] | None {
return (m.group(1) as str, m.group(2) as str);
}

impl repo_meta(repo: str, agent: str) -> RepoMeta | None {
data = fetch(API_ROOT + repo, agent, accept_json=True);
def _api_bytes(url: str, agent: str) -> bytes {
data = fetch(url, agent, accept_json=True);
if data is None {
return None;
}
try {
raw = json.loads(data);
} except Exception {
return None;
raise RuntimeError("GitHub is unavailable or rate limited");
}
if not isinstance(raw, dict)
or raw.get("full_name") is None
or raw.get("private", False) {
return data;
}

impl repo_meta(repo: str, agent: str) -> RepoMeta | None {
raw = JsonEndpoint[GithubRepository](GithubRepository, API_ROOT + repo).read(
lambda (url: str) -> bytes { return _api_bytes(url, agent); }
);
if raw.private {
return None;
}
login = "";
avatar = "";
owner = raw.get("owner");
if isinstance(owner, dict) {
login = str(owner.get("login") or "");
avatar = str(owner.get("avatar_url") or "");
}
return RepoMeta(
full_name=str(raw.get("full_name") or ""),
owner=login,
name=str(raw.get("name") or ""),
description=str(raw.get("description") or "")[:220],
avatar_url=avatar,
html_url=str(raw.get("html_url") or ""),
default_branch=str(raw.get("default_branch") or "main"),
stars=int(raw.get("stargazers_count") or 0),
forks=int(raw.get("forks_count") or 0),
size_kb=int(raw.get("size") or 1),
topics=[str(t) for t in (raw.get("topics") or [])][:6],
pushed_at=str(raw.get("pushed_at") or ""),
created_at=str(raw.get("created_at") or "")
full_name=raw.full_name,
owner=raw.owner.login,
name=raw.name,
description=(raw.description or "")[:220],
avatar_url=raw.owner.avatar_url,
html_url=raw.html_url,
default_branch=raw.default_branch,
stars=raw.stargazers_count,
forks=raw.forks_count,
size_kb=raw.size,
topics=raw.topics[:6],
pushed_at=raw.pushed_at or "",
created_at=raw.created_at
);
}

impl head_sha(repo: str, ref: str, agent: str) -> str {
data = fetch(API_ROOT + repo + "/commits/" + ref, agent, accept_json=True);
if data is None {
return "";
}
try {
raw = json.loads(data);
} except Exception {
return "";
}
return str(raw.get("sha") or "") if isinstance(raw, dict) else "";
result = JsonEndpoint[GithubCommit](
GithubCommit, API_ROOT + repo + "/commits/" + ref
).read(
lambda (url: str) -> bytes { return _api_bytes(url, agent); }
);
return result.sha;
}

impl download_tarball(
Expand Down
Loading
Loading