Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
eca9970
add compiled workflow policy core
HelgeSverre Jul 31, 2026
1fef554
enforce policies at workflow boundaries
HelgeSverre Jul 31, 2026
8a59e48
document and test workflow policies
HelgeSverre Jul 31, 2026
cc35e71
fix: enforce model policy across LLM dispatch paths
HelgeSverre Jul 31, 2026
9487060
feat(policy): compose policy layers in one scope
HelgeSverre Jul 31, 2026
fe5aa9c
feat(policy): add semantic subjects and content guards
HelgeSverre Jul 31, 2026
95ce9c9
feat(policy): complete guardrails and package compatibility
HelgeSverre Jul 31, 2026
4e24be9
fix(wasm): include policy scanners in web builds
HelgeSverre Jul 31, 2026
21fc6ba
test(web): align arity diagnostic assertion
HelgeSverre Jul 31, 2026
d921e84
docs(workflow): plan durable human approval
HelgeSverre Jul 31, 2026
7cf24ef
feat(workflow): add durable human approval gates
HelgeSverre Jul 31, 2026
55412f8
fix(workflow): harden durable human approval
HelgeSverre Jul 31, 2026
59256b6
fix(workflow): rework the viewer approval panel layout
HelgeSverre Jul 31, 2026
1c596cf
feat(workflow): add interactive approval controls
HelgeSverre Jul 31, 2026
fb67a28
fix(workflow): close approval review gaps
HelgeSverre Aug 1, 2026
8b69a24
feat(policy): add approval evidence profiles
HelgeSverre Aug 1, 2026
b016cdc
chore(wasm): refresh embedded runtime fingerprint
HelgeSverre Aug 1, 2026
b44a373
fix(workflow): preserve durable approval authority
HelgeSverre Aug 1, 2026
59ee304
fix(policy): report each content action accurately
HelgeSverre Aug 1, 2026
4464da1
Merge branch 'feat/issue-50-policy' into feat/issue-50-approval
HelgeSverre Aug 1, 2026
3fc745f
chore(workflow): refresh generated runtime checks
HelgeSverre Aug 1, 2026
23630c6
chore(policy): refresh web runtime lock
HelgeSverre Aug 1, 2026
349cd20
Merge branch 'feat/issue-50-policy' into feat/issue-50-approval
HelgeSverre Aug 1, 2026
0adf202
fix: harden approval static checker, remove CWD dependency, fix tmp c…
HelgeSverre Aug 2, 2026
04c1387
fix: address adversarial review findings
HelgeSverre Aug 2, 2026
d8717d1
Merge branch 'main' into feat/issue-50-approval
HelgeSverre Aug 4, 2026
840b41e
chore(runtime-inventory): refresh match map line numbers
HelgeSverre Aug 4, 2026
900a0d8
fix(policy): fail closed when a deny rule cannot evaluate a subject
HelgeSverre Aug 4, 2026
819a919
fix(workflow-view): escape the phase status before rendering it
HelgeSverre Aug 4, 2026
48c216b
docs(changelog): record workflow policies and durable approval gates
HelgeSverre Aug 4, 2026
ad5ff4f
docs(plans): archive the completed human-approval plan
HelgeSverre Aug 4, 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
1 change: 1 addition & 0 deletions .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ jobs:
publish sema-fmt
publish sema-vm
publish sema-otel
publish sema-policy
publish sema-workflow
publish sema-llm
publish sema-stdlib
Expand Down
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@

### Added

- **Workflow policies — `defpolicy` (#50).** A policy is a compiled, immutable
map that guards a workflow at its boundaries: `:models` (which
provider/model a call may use), `:tools` (per-tool argument constraints over
paths, domains, and commands), `:subjects` (semantic allow/deny rules for the
file, network, command, and external-action a tool actually performs),
`:input` and `:output` (content scanning, with `:redact` and `:block`
actions). Install one with `:policy` in a `defworkflow` or `step` meta map.
Layers compose: any layer may deny, a later layer can only tighten an earlier
one, and the strictest action wins. Every decision is journaled to the run
directory as evidence. `policy/without` is a lexical, audited bypass that
emits a `policy.bypassed` event and never widens the workflow's `:permissions`
sandbox. `sema workflow check` rejects a source file whose policy or approval
use is malformed before the run starts.
- **Durable human approval gates — `approval` (#50).** `(approval :key {…})`
stops a workflow before a sensitive action until a human records a decision.
In `auto` mode on a terminal it prompts; with an approval authority it writes
a request sidecar, exits 3, and waits. A separate trusted process decides with
`sema workflow approve` / `sema workflow reject`, signing with an Ed25519 key
that is never passed to the workflow process; `sema workflow run --resume`
then applies it. Decisions are bound to the run, workflow, code version,
arguments, phase, key, occurrence, and subject digest, so a decision cannot be
replayed onto a different gate or run. `sema workflow approvals` lists pending
requests as text or JSON, and the loopback `sema workflow view` viewer can
record the same signed decision. `sema workflow approval-keygen` creates the
authority.
- **`sema_version_req` in `sema.toml`** — a package can declare the Sema
versions it supports, and `sema pkg` refuses an incompatible install with the
requirement in the error.

- **macOS release binaries are now Developer-ID signed, notarized, and
Sigstore-attested (#109, #107, #133).** Every `*-apple-darwin.tar.xz`
carries a hardened-runtime signature and a GitHub Artifact Attestation
Expand Down
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"crates/sema-docs",
"crates/sema-mcp",
"crates/sema-otel",
"crates/sema-policy",
"crates/sema-workflow",
]
resolver = "2"
Expand Down Expand Up @@ -43,6 +44,7 @@ default-members = [
"crates/sema-docs",
"crates/sema-mcp",
"crates/sema-otel",
"crates/sema-policy",
"crates/sema-workflow",
]

Expand All @@ -68,6 +70,7 @@ sema-notebook = { version = "=1.33.0", path = "crates/sema-notebook" }
sema-docs = { version = "=1.33.0", path = "crates/sema-docs" }
sema-mcp = { version = "=1.33.0", path = "crates/sema-mcp" }
sema-otel = { version = "=1.33.0", path = "crates/sema-otel" }
sema-policy = { version = "=1.33.0", path = "crates/sema-policy" }
sema-workflow = { version = "=1.33.0", path = "crates/sema-workflow" }

tower-lsp = "0.20"
Expand Down Expand Up @@ -96,6 +99,7 @@ rand = "0.10"
uuid = { version = "1", features = ["v4"] }
base64 = "0.22"
sha2 = "0.10"
ring = "0.17"
chrono = "0.4"
csv = "1"
md5 = "0.8"
Expand All @@ -109,6 +113,7 @@ unicode-width = "0.1"
unicode-segmentation = "1"
caseless = "0.2"
glob = "0.3"
globset = "0.4"
hostname = "0.4"
libc = "0.2"
pdf-extract = "0.12"
Expand Down
12 changes: 12 additions & 0 deletions crates/sema-core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ impl Drop for ContextStacksMut<'_> {
pub struct EvalContext {
pub module_cache: RefCell<BTreeMap<PathBuf, BTreeMap<String, Value>>>,
pub embedded_files: RefCell<BTreeMap<PathBuf, Vec<u8>>>,
embedded_files_only: Cell<bool>,
pub current_file: RefCell<Vec<PathBuf>>,
pub module_exports: RefCell<Vec<Option<Vec<String>>>>,
pub module_load_stack: RefCell<Vec<PathBuf>>,
Expand Down Expand Up @@ -235,6 +236,7 @@ impl EvalContext {
EvalContext {
module_cache: RefCell::new(BTreeMap::new()),
embedded_files: RefCell::new(BTreeMap::new()),
embedded_files_only: Cell::new(false),
current_file: RefCell::new(Vec::new()),
module_exports: RefCell::new(Vec::new()),
module_load_stack: RefCell::new(Vec::new()),
Expand Down Expand Up @@ -266,6 +268,7 @@ impl EvalContext {
EvalContext {
module_cache: RefCell::new(BTreeMap::new()),
embedded_files: RefCell::new(BTreeMap::new()),
embedded_files_only: Cell::new(false),
current_file: RefCell::new(Vec::new()),
module_exports: RefCell::new(Vec::new()),
module_load_stack: RefCell::new(Vec::new()),
Expand Down Expand Up @@ -484,6 +487,15 @@ impl EvalContext {
self.embedded_files.borrow_mut().clear();
}

/// When enabled, `import` and `load` may only resolve host-provided embedded files.
pub fn set_embedded_files_only(&self, enabled: bool) {
self.embedded_files_only.set(enabled);
}

pub fn embedded_files_only(&self) -> bool {
self.embedded_files_only.get()
}

pub fn set_module_exports(&self, names: Vec<String>) {
if let Some(state) = self.module_task_state() {
state.set_current_exports(names);
Expand Down
Loading
Loading