let a construct refuse, not only observe
@authenticate (6 uses), @authorize (3), @rate_limit (3) and @validate (2)
parsed, attached, and compiled to nothing. Fourteen applications that read as
protection and emitted no instruction — a function decorated @authenticate
compiled byte-identically to an undecorated one.
The missing capability was not authentication. It was that a construct could
observe a boundary but never refuse one. injects_at_entry discards the target's
result; there was no form in which a construct could say no.
@decorator("guards_at_entry", "my_auth")
fn authenticate() {}
@authenticate
@authorize
fn handler() -> String { ... }
emits, at entry:
{ el_val_t __g = my_auth(EL_STR("handler"), EL_STR("authenticate")); if (__g) return __g; }
{ el_val_t __g = my_roles(EL_STR("handler"), EL_STR("authorize")); if (__g) return __g; }
Guards precede injections because a refused call must not report a crossing,
and every guard runs where the topmost injecting construct wins — refusal is
not a role, so it does not follow the role convention.
The compiler still knows nothing about auth. The program points the construct
at its own function, which is where that decision belongs.
Verified: existing @manager/@accessor output byte-identical, compiler
self-hosts byte-identically, guards stack in declaration order and emit before
the beat. 94/94 native compiler tests pass.
This commit is contained in:
@@ -805,3 +805,40 @@ test "builtin-constructs-still-inject" {
|
||||
assert str_contains(out, "EL_STR(\"manager\")"), "seeded manager still injects"
|
||||
assert str_contains(out, "EL_STR(\"accessor\")"), "seeded accessor still injects"
|
||||
}
|
||||
|
||||
// ── Declared constructs: guards ──────────────────────────────────────────────
|
||||
//
|
||||
// A guard is an injection that may refuse. Non-zero return short-circuits the
|
||||
// decorated fn. This is what @authenticate/@authorize/@rate_limit/@validate
|
||||
// needed and never had — fourteen applications that read as protection and
|
||||
// emitted no instruction.
|
||||
|
||||
test "declared-guard-emits-refusable-check" {
|
||||
let src: String = "@decorator(\"guards_at_entry\", \"my_auth\")\nfn authenticate() {}\n@authenticate\nfn handler() -> Int { return 7 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "my_auth(EL_STR(\"handler\")"), "the guard is called at entry"
|
||||
assert str_contains(out, "if (__g) return __g;"), "a non-zero guard result short-circuits the fn"
|
||||
}
|
||||
|
||||
test "declared-guards-stack-in-order" {
|
||||
let src: String = "@decorator(\"guards_at_entry\", \"my_auth\")\nfn authenticate() {}\n@decorator(\"guards_at_entry\", \"my_roles\")\nfn authorize() {}\n@authenticate\n@authorize\nfn handler() -> Int { return 7 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert str_contains(out, "my_auth("), "first guard runs"
|
||||
assert str_contains(out, "my_roles("), "second guard runs — every guard applies, not just the topmost"
|
||||
}
|
||||
|
||||
test "guard-precedes-injection" {
|
||||
// A refused call must not report a boundary crossing.
|
||||
let src: String = "@decorator(\"guards_at_entry\", \"my_auth\")\nfn authenticate() {}\n@authenticate\n@manager\nfn handler() -> Int { return 7 }"
|
||||
let out: String = compile_capture(src)
|
||||
let g: Int = str_index_of(out, "my_auth(")
|
||||
let b: Int = str_index_of(out, "engram_boundary_beat(EL_STR(\"handler\")")
|
||||
assert g < b, "the guard is emitted before the beat"
|
||||
assert g >= 0, "guard present"
|
||||
}
|
||||
|
||||
test "undeclared-guard-emits-nothing" {
|
||||
let src: String = "@not_a_declared_guard\nfn handler() -> Int { return 7 }"
|
||||
let out: String = compile_capture(src)
|
||||
assert !str_contains(out, "if (__g)"), "an undeclared construct guards nothing"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user