@kernlang/python 3.5.6 → 3.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/adapters/django.d.ts +49 -0
  2. package/dist/adapters/django.js +151 -0
  3. package/dist/adapters/django.js.map +1 -0
  4. package/dist/adapters/fastapi.d.ts +43 -0
  5. package/dist/adapters/fastapi.js +139 -0
  6. package/dist/adapters/fastapi.js.map +1 -0
  7. package/dist/codegen-body-python.d.ts +17 -5
  8. package/dist/codegen-body-python.js +83 -67
  9. package/dist/codegen-body-python.js.map +1 -1
  10. package/dist/core/expr/helpers.d.ts +5 -0
  11. package/dist/core/expr/helpers.js +62 -0
  12. package/dist/core/expr/helpers.js.map +1 -0
  13. package/dist/core/expr/index.d.ts +9 -0
  14. package/dist/core/expr/index.js +2046 -0
  15. package/dist/core/expr/index.js.map +1 -0
  16. package/dist/core/handlers/index.d.ts +74 -0
  17. package/dist/core/handlers/index.js +462 -0
  18. package/dist/core/handlers/index.js.map +1 -0
  19. package/dist/fastapi-portable.js +3 -2
  20. package/dist/fastapi-portable.js.map +1 -1
  21. package/dist/fastapi-response.d.ts +0 -6
  22. package/dist/fastapi-response.js +2 -2217
  23. package/dist/fastapi-response.js.map +1 -1
  24. package/dist/fastapi-route.js +24 -3
  25. package/dist/fastapi-route.js.map +1 -1
  26. package/dist/fastapi-utils.d.ts +2 -1
  27. package/dist/fastapi-utils.js +2 -58
  28. package/dist/fastapi-utils.js.map +1 -1
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +2 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/ir-semantics/python-leg.js +5 -0
  33. package/dist/ir-semantics/python-leg.js.map +1 -1
  34. package/dist/targets/python.d.ts +27 -0
  35. package/dist/targets/python.js +130 -4
  36. package/dist/targets/python.js.map +1 -1
  37. package/package.json +2 -2
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Django adapter — revised contract (post nero red-team).
3
+ *
4
+ * Wraps `PurePythonHandler` values as Django views + URL conf. Same
5
+ * marshalling-only pattern as the FastAPI adapter — derive/guard/respond
6
+ * live in the pure handler; the adapter ONLY translates Django's
7
+ * `HttpRequest` into a `PureRequest` dict and back.
8
+ *
9
+ * Adapter responsibilities (the ONLY things it does):
10
+ * 1. urls.py: one `path()` per route, mapping the (KERN `:param` → Django
11
+ * `<type:name>`) converted path to the view function.
12
+ * 2. views.py per route: build PureRequest dict from `request.method`,
13
+ * view-fn kwargs (path params), `request.GET` (query), `json.loads(
14
+ * request.body)` (body when content-type is JSON; else raw bytes),
15
+ * `dict(request.headers)`. Call `handle_<fnName>(pure_request)`.
16
+ * Return `JsonResponse(body, status=status, headers=extra_headers or {})`.
17
+ * 3. settings.py: minimal — `DEBUG`, `SECRET_KEY` (env), `INSTALLED_APPS=['django.contrib.contenttypes','django.contrib.auth']`
18
+ * (the bare minimum Django requires), `ROOT_URLCONF`, `DATABASES={}`,
19
+ * `ALLOWED_HOSTS=['*']`.
20
+ * 4. manage.py: standard Django CLI entry point.
21
+ *
22
+ * Acceptance (Phase 3b smoke + Wave 3 end-to-end):
23
+ * - Synthetic PureRequest fixture (hand-built in the smoke, NOT from
24
+ * Django) → pure handler returns expected (status, body).
25
+ * - Django smoke: transpile a real route, generate the Django project,
26
+ * run `python manage.py runserver --noreload <port>`, curl the route,
27
+ * assert response. CRITICAL: use `--noreload` so the SIGTERM hits the
28
+ * real server, not a parent autoreloader (the equivalent of #4's
29
+ * `go run .` orphan-on-SIGTERM hang).
30
+ *
31
+ * Django is installed globally (`django 6.0.5`) on this dev environment; CI
32
+ * will need `pip install django` in the workflow that runs the smoke.
33
+ */
34
+ import type { PurePythonHandler } from '../core/handlers/index.js';
35
+ export interface DjangoAdapterArtifacts {
36
+ /** Python source for urls.py (URL conf with `path('users/', views.handle_get_users)` lines). */
37
+ urlsPy: string;
38
+ /** Python source for views.py (view functions wrapping each PurePythonHandler). */
39
+ viewsPy: string;
40
+ /** Python source for settings.py (minimal Django settings — INSTALLED_APPS, ROOT_URLCONF, DEBUG). */
41
+ settingsPy: string;
42
+ /** Python source for manage.py (Django CLI entry point). */
43
+ managePy: string;
44
+ /** Python source for the pure-handlers module the adapter views import from. Phase 3b's smoke uses a synthetic version; in production the python target writes the real one. */
45
+ pureHandlersPy: string;
46
+ /** Imports the ADAPTER itself needs at the top of views.py. */
47
+ imports: Set<string>;
48
+ }
49
+ export declare function emitDjangoAdapter(handlers: PurePythonHandler[]): DjangoAdapterArtifacts;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Django adapter — revised contract (post nero red-team).
3
+ *
4
+ * Wraps `PurePythonHandler` values as Django views + URL conf. Same
5
+ * marshalling-only pattern as the FastAPI adapter — derive/guard/respond
6
+ * live in the pure handler; the adapter ONLY translates Django's
7
+ * `HttpRequest` into a `PureRequest` dict and back.
8
+ *
9
+ * Adapter responsibilities (the ONLY things it does):
10
+ * 1. urls.py: one `path()` per route, mapping the (KERN `:param` → Django
11
+ * `<type:name>`) converted path to the view function.
12
+ * 2. views.py per route: build PureRequest dict from `request.method`,
13
+ * view-fn kwargs (path params), `request.GET` (query), `json.loads(
14
+ * request.body)` (body when content-type is JSON; else raw bytes),
15
+ * `dict(request.headers)`. Call `handle_<fnName>(pure_request)`.
16
+ * Return `JsonResponse(body, status=status, headers=extra_headers or {})`.
17
+ * 3. settings.py: minimal — `DEBUG`, `SECRET_KEY` (env), `INSTALLED_APPS=['django.contrib.contenttypes','django.contrib.auth']`
18
+ * (the bare minimum Django requires), `ROOT_URLCONF`, `DATABASES={}`,
19
+ * `ALLOWED_HOSTS=['*']`.
20
+ * 4. manage.py: standard Django CLI entry point.
21
+ *
22
+ * Acceptance (Phase 3b smoke + Wave 3 end-to-end):
23
+ * - Synthetic PureRequest fixture (hand-built in the smoke, NOT from
24
+ * Django) → pure handler returns expected (status, body).
25
+ * - Django smoke: transpile a real route, generate the Django project,
26
+ * run `python manage.py runserver --noreload <port>`, curl the route,
27
+ * assert response. CRITICAL: use `--noreload` so the SIGTERM hits the
28
+ * real server, not a parent autoreloader (the equivalent of #4's
29
+ * `go run .` orphan-on-SIGTERM hang).
30
+ *
31
+ * Django is installed globally (`django 6.0.5`) on this dev environment; CI
32
+ * will need `pip install django` in the workflow that runs the smoke.
33
+ */
34
+ export function emitDjangoAdapter(handlers) {
35
+ const urlPaths = handlers.map((h) => {
36
+ const cleanPath = h.path.startsWith('/') ? h.path.slice(1) : h.path;
37
+ const segments = cleanPath.split('/');
38
+ const hasParams = segments.some((s) => s.startsWith(':'));
39
+ const mappedSegments = segments.map((segment) => {
40
+ if (segment.startsWith(':')) {
41
+ const paramName = segment.slice(1);
42
+ const paramType = h.pathParamTypes?.[paramName] || 'str';
43
+ return `<${paramType}:${paramName}>`;
44
+ }
45
+ return segment;
46
+ });
47
+ const djangoPath = mappedSegments.join('/');
48
+ if (hasParams) {
49
+ return ` path("${djangoPath}", views.${h.fnName}_route, name="${h.fnName}"),`;
50
+ }
51
+ else {
52
+ return ` path("${djangoPath}", views.${h.fnName}_route),`;
53
+ }
54
+ });
55
+ const urlsPy = `from django.urls import path
56
+ try:
57
+ from . import views
58
+ except ImportError:
59
+ import views
60
+
61
+ urlpatterns = [
62
+ ${urlPaths.join('\n')}
63
+ ]
64
+ `;
65
+ const pureImportsList = Array.from(new Set(handlers.map((h) => h.fnName)));
66
+ const viewsImports = `import json
67
+ from django.http import JsonResponse, HttpRequest
68
+ from django.views.decorators.csrf import csrf_exempt
69
+ from django.views.decorators.http import require_http_methods
70
+ try:
71
+ from .pure_handlers import ${pureImportsList.join(', ')}
72
+ except ImportError:
73
+ from pure_handlers import ${pureImportsList.join(', ')}`;
74
+ const viewFunctions = handlers
75
+ .map((h) => {
76
+ const method = h.method.toUpperCase();
77
+ const responseHeadersJson = JSON.stringify(h.responseHeaders ?? {});
78
+ return `@csrf_exempt
79
+ @require_http_methods(["${method}"])
80
+ def ${h.fnName}_route(request: HttpRequest, **path_kwargs):
81
+ try:
82
+ body = json.loads(request.body) if request.body else {}
83
+ except json.JSONDecodeError:
84
+ body = {}
85
+ pure_request = {
86
+ "method": request.method,
87
+ "path_params": path_kwargs,
88
+ "query": {k: (request.GET.getlist(k)[0] if len(request.GET.getlist(k)) == 1 else request.GET.getlist(k)) for k in request.GET},
89
+ "body": body,
90
+ "headers": {k.lower(): v for k, v in request.headers.items()},
91
+ "user": getattr(request, "user", None),
92
+ }
93
+ result = ${h.fnName}(pure_request)
94
+ status, body_out, *rest = result if isinstance(result, tuple) else (200, result)
95
+ extra_headers = rest[0] if rest else {}
96
+ merged_headers = {**${responseHeadersJson}, **extra_headers}
97
+ resp = JsonResponse(body_out, status=status, safe=False)
98
+ for k, v in merged_headers.items():
99
+ resp[k] = v
100
+ return resp`;
101
+ })
102
+ .join('\n\n');
103
+ const viewsPy = `${viewsImports}
104
+
105
+ ${viewFunctions}
106
+ `;
107
+ const settingsPy = `import os
108
+ SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "kern-dev-secret-not-for-prod")
109
+ DEBUG = True
110
+ ALLOWED_HOSTS = ["*"]
111
+ INSTALLED_APPS = ["django.contrib.contenttypes", "django.contrib.auth"]
112
+ MIDDLEWARE = []
113
+ ROOT_URLCONF = "urls"
114
+ DATABASES = {}
115
+ USE_TZ = True
116
+ TIME_ZONE = "UTC"
117
+ `;
118
+ const managePy = `#!/usr/bin/env python
119
+ import os, sys
120
+ if __name__ == "__main__":
121
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
122
+ from django.core.management import execute_from_command_line
123
+ execute_from_command_line(sys.argv)
124
+ `;
125
+ const handlerImportsSet = new Set();
126
+ for (const h of handlers) {
127
+ if (h.imports) {
128
+ for (const imp of h.imports) {
129
+ handlerImportsSet.add(imp);
130
+ }
131
+ }
132
+ }
133
+ const pureHandlersImportsStr = Array.from(handlerImportsSet).sort().join('\n');
134
+ const pureHandlersCode = handlers.map((h) => `${h.signature}\n${h.bodyLines.join('\n')}`).join('\n\n');
135
+ const pureHandlersPy = pureHandlersImportsStr ? `${pureHandlersImportsStr}\n\n${pureHandlersCode}` : pureHandlersCode;
136
+ const imports = new Set([
137
+ 'import json',
138
+ 'from django.http import JsonResponse, HttpRequest',
139
+ 'from django.views.decorators.csrf import csrf_exempt',
140
+ 'from django.views.decorators.http import require_http_methods',
141
+ ]);
142
+ return {
143
+ urlsPy,
144
+ viewsPy,
145
+ settingsPy,
146
+ managePy,
147
+ pureHandlersPy,
148
+ imports,
149
+ };
150
+ }
151
+ //# sourceMappingURL=django.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"django.js","sourceRoot":"","sources":["../../src/adapters/django.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * FastAPI adapter — revised contract (post nero red-team).
3
+ *
4
+ * Takes a list of framework-agnostic `PurePythonHandler` values and emits a
5
+ * FastAPI app skin that wraps each handler in a decorated endpoint. The
6
+ * adapter does ONLY marshalling — it builds a `PureRequest` dict from
7
+ * FastAPI's `Request` + path/query/body, calls the pure handler, unpacks
8
+ * the `PureResponse` tuple, and returns a `JSONResponse`. ROUTE LOWERING
9
+ * STAYS IN THE PURE HANDLER (the adapter never re-implements derive/guard/
10
+ * respond — that's the whole point of decoupling).
11
+ *
12
+ * Adapter responsibilities (the ONLY things it does):
13
+ * 1. Wire path params from FastAPI's typed signature into PureRequest.path_params
14
+ * (using `PurePythonHandler.pathParamTypes` for coercion).
15
+ * 2. Wire query params from `request.query_params` into PureRequest.query
16
+ * (using `PurePythonHandler.queryParamTypes` for coercion).
17
+ * 3. Wire body — if `validatesSchema` is set, attach the existing Pydantic
18
+ * model as a FastAPI body param; pass `.model_dump()` into the dict.
19
+ * Else: `await request.json()` if content-type is JSON, else raw bytes.
20
+ * 4. Build the PureRequest dict.
21
+ * 5. Call the pure handler `fnName(pure_request)`.
22
+ * 6. Unpack `(status, body[, headers])` → `JSONResponse(content=body, status_code=status, headers=headers or {})`.
23
+ *
24
+ * Acceptance (Phase 3a smoke + Wave 3 end-to-end):
25
+ * - Synthetic PureRequest fixture (hand-built in the smoke, not from any
26
+ * framework) → pure handler returns expected (status, body).
27
+ * - The existing fastapi conformance suite (194/194 fixtures) passes when
28
+ * routed through `pure-python + this adapter` pipeline (Wave 3).
29
+ *
30
+ * The current monolithic `transpiler-fastapi.ts` stays as the default until
31
+ * Phase 3a + Phase 2 both land and a follow-up flips the wiring. This phase
32
+ * only ADDS the adapter; it does not remove the monolith.
33
+ */
34
+ import type { PurePythonHandler } from '../core/handlers/index.js';
35
+ export interface FastAPIAdapterArtifacts {
36
+ /** Python source for the FastAPI app file (imports, `app = FastAPI()`, decorated endpoints). */
37
+ appPy: string;
38
+ /** Python source for the pure-handlers module (re-emitted as a sibling .py file the adapter imports). Phase 3a's smoke uses a synthetic version of this; in production the python target writes the real one. */
39
+ pureHandlersPy: string;
40
+ /** Imports the ADAPTER itself needs at the top of appPy. */
41
+ imports: Set<string>;
42
+ }
43
+ export declare function emitFastAPIAdapter(handlers: PurePythonHandler[]): FastAPIAdapterArtifacts;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * FastAPI adapter — revised contract (post nero red-team).
3
+ *
4
+ * Takes a list of framework-agnostic `PurePythonHandler` values and emits a
5
+ * FastAPI app skin that wraps each handler in a decorated endpoint. The
6
+ * adapter does ONLY marshalling — it builds a `PureRequest` dict from
7
+ * FastAPI's `Request` + path/query/body, calls the pure handler, unpacks
8
+ * the `PureResponse` tuple, and returns a `JSONResponse`. ROUTE LOWERING
9
+ * STAYS IN THE PURE HANDLER (the adapter never re-implements derive/guard/
10
+ * respond — that's the whole point of decoupling).
11
+ *
12
+ * Adapter responsibilities (the ONLY things it does):
13
+ * 1. Wire path params from FastAPI's typed signature into PureRequest.path_params
14
+ * (using `PurePythonHandler.pathParamTypes` for coercion).
15
+ * 2. Wire query params from `request.query_params` into PureRequest.query
16
+ * (using `PurePythonHandler.queryParamTypes` for coercion).
17
+ * 3. Wire body — if `validatesSchema` is set, attach the existing Pydantic
18
+ * model as a FastAPI body param; pass `.model_dump()` into the dict.
19
+ * Else: `await request.json()` if content-type is JSON, else raw bytes.
20
+ * 4. Build the PureRequest dict.
21
+ * 5. Call the pure handler `fnName(pure_request)`.
22
+ * 6. Unpack `(status, body[, headers])` → `JSONResponse(content=body, status_code=status, headers=headers or {})`.
23
+ *
24
+ * Acceptance (Phase 3a smoke + Wave 3 end-to-end):
25
+ * - Synthetic PureRequest fixture (hand-built in the smoke, not from any
26
+ * framework) → pure handler returns expected (status, body).
27
+ * - The existing fastapi conformance suite (194/194 fixtures) passes when
28
+ * routed through `pure-python + this adapter` pipeline (Wave 3).
29
+ *
30
+ * The current monolithic `transpiler-fastapi.ts` stays as the default until
31
+ * Phase 3a + Phase 2 both land and a follow-up flips the wiring. This phase
32
+ * only ADDS the adapter; it does not remove the monolith.
33
+ */
34
+ export function emitFastAPIAdapter(handlers) {
35
+ const imports = new Set([
36
+ 'from fastapi import FastAPI, Request',
37
+ 'from fastapi.responses import JSONResponse',
38
+ ]);
39
+ const appPyLines = [];
40
+ // Add standard imports for FastAPI
41
+ appPyLines.push('from fastapi import FastAPI, Request');
42
+ appPyLines.push('from fastapi.responses import JSONResponse');
43
+ // Import each pure handler and its validatesSchema model (if any) from the pure_handlers module
44
+ // Use try-except to support both absolute and relative imports across different runtime environments
45
+ for (const handler of handlers) {
46
+ appPyLines.push('try:');
47
+ appPyLines.push(` from .pure_handlers import ${handler.fnName}`);
48
+ appPyLines.push('except ImportError:');
49
+ appPyLines.push(` from pure_handlers import ${handler.fnName}`);
50
+ if (handler.validatesSchema) {
51
+ appPyLines.push('try:');
52
+ appPyLines.push(` from .pure_handlers import ${handler.validatesSchema}`);
53
+ appPyLines.push('except ImportError:');
54
+ appPyLines.push(` from pure_handlers import ${handler.validatesSchema}`);
55
+ }
56
+ }
57
+ appPyLines.push('');
58
+ appPyLines.push('app = FastAPI()');
59
+ appPyLines.push('');
60
+ function mapPythonType(typeStr) {
61
+ if (!typeStr)
62
+ return 'str';
63
+ const t = typeStr.trim().toLowerCase();
64
+ if (t === 'string')
65
+ return 'str';
66
+ if (t === 'number')
67
+ return 'float';
68
+ if (t === 'boolean')
69
+ return 'bool';
70
+ if (t === 'integer')
71
+ return 'int';
72
+ return typeStr;
73
+ }
74
+ for (const handler of handlers) {
75
+ // Convert path param style from KERN (:param) to FastAPI ({param})
76
+ const pathWithFastapiBraces = handler.path.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');
77
+ // Extract path param names
78
+ const paramMatches = handler.path.match(/:([a-zA-Z0-9_]+)/g) || [];
79
+ const paramNames = paramMatches.map((m) => m.slice(1));
80
+ // Map path params to typed parameters
81
+ const typedPathParams = paramNames.map((name) => {
82
+ const type = handler.pathParamTypes[name] || 'str';
83
+ return `${name}: ${mapPythonType(type)}`;
84
+ });
85
+ const bodyPydanticParamOrNone = handler.validatesSchema ? `body_pydantic_param: ${handler.validatesSchema}` : '';
86
+ const routeArgs = [];
87
+ for (const p of typedPathParams) {
88
+ routeArgs.push(p);
89
+ }
90
+ if (bodyPydanticParamOrNone) {
91
+ routeArgs.push(bodyPydanticParamOrNone);
92
+ }
93
+ routeArgs.push('request: Request');
94
+ const routeArgsStr = routeArgs.join(', ');
95
+ // Construct path params dict
96
+ const pathParamsEntries = paramNames.map((name) => `"${name}": ${name}`).join(', ');
97
+ const pathParamsStr = `{${pathParamsEntries}}`;
98
+ const responseHeadersLiteral = JSON.stringify(handler.responseHeaders);
99
+ const methodLower = handler.method.toLowerCase();
100
+ const methodUpper = handler.method.toUpperCase();
101
+ appPyLines.push(`@app.${methodLower}("${pathWithFastapiBraces}")`);
102
+ appPyLines.push(`async def ${handler.fnName}_route(${routeArgsStr}):`);
103
+ if (!handler.validatesSchema) {
104
+ appPyLines.push(' body_pydantic_param = None');
105
+ }
106
+ appPyLines.push(' pure_request = {');
107
+ appPyLines.push(` "method": "${methodUpper}",`);
108
+ appPyLines.push(` "path_params": ${pathParamsStr},`);
109
+ appPyLines.push(' "query": {k: (request.query_params.getlist(k)[0] if len(request.query_params.getlist(k)) == 1 else request.query_params.getlist(k)) for k in request.query_params.keys()},');
110
+ appPyLines.push(' "body": (body_pydantic_param.model_dump() if body_pydantic_param is not None else (await request.json() if "application/json" in request.headers.get("content-type", "") else (await request.body()))),');
111
+ appPyLines.push(' "headers": {k.lower(): v for k, v in request.headers.items()},');
112
+ appPyLines.push(' "user": getattr(request.state, "user", None),');
113
+ appPyLines.push(' }');
114
+ appPyLines.push(` result = ${handler.fnName}(pure_request)`);
115
+ appPyLines.push(' status, body, *rest = result if isinstance(result, tuple) else (200, result)');
116
+ appPyLines.push(' extra_headers = rest[0] if rest else {}');
117
+ appPyLines.push(` merged_headers = {**${responseHeadersLiteral}, **extra_headers}`);
118
+ appPyLines.push(' return JSONResponse(content=body, status_code=status, headers=merged_headers or None)');
119
+ appPyLines.push('');
120
+ }
121
+ // Concatenate all handler code to build pureHandlersPy, including their combined imports
122
+ const allPureImports = new Set();
123
+ for (const handler of handlers) {
124
+ if (handler.imports) {
125
+ for (const imp of handler.imports) {
126
+ allPureImports.add(imp);
127
+ }
128
+ }
129
+ }
130
+ const sortedPureImports = Array.from(allPureImports).sort();
131
+ const pureImportsStr = sortedPureImports.length > 0 ? `${sortedPureImports.join('\n')}\n\n` : '';
132
+ const pureHandlersPy = `${pureImportsStr + handlers.map((h) => `${h.signature}\n${h.bodyLines.join('\n')}`).join('\n\n')}\n`;
133
+ return {
134
+ appPy: appPyLines.join('\n'),
135
+ pureHandlersPy,
136
+ imports,
137
+ };
138
+ }
139
+ //# sourceMappingURL=fastapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastapi.js","sourceRoot":"","sources":["../../src/adapters/fastapi.ts"],"names":[],"mappings":"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"}
@@ -73,6 +73,16 @@ export interface BodyEmitOptions {
73
73
  forIterNext?: boolean;
74
74
  letAssign?: boolean;
75
75
  };
76
+ /** Outer-scope names the body INHERITS — typically function parameters and
77
+ * module-level globals the wrapper has bound. Pre-populated as the
78
+ * outermost `localScopes` map so an inner-block `let` that shadows ANY of
79
+ * these triggers the block-scope rename (closes nero red-team Challenge 2
80
+ * for param shadows). Each name is recorded as 'const' since the body's
81
+ * own re-declarations of these names go through `let` (a fresh
82
+ * declaration) rather than `assign`, so this annotation only governs
83
+ * shadow-detection — it never blocks legitimate inner reassignment of an
84
+ * unrelated inner binding. */
85
+ outerBindings?: string[];
76
86
  }
77
87
  /** Slice 3e — public return shape. `code` is the joined body text;
78
88
  * `imports` is the per-handler set of import identifiers
@@ -109,6 +119,13 @@ interface BodyEmitContext {
109
119
  regexScopes: Array<Map<string, Extract<ValueIR, {
110
120
  kind: 'regexLit';
111
121
  }> | null>>;
122
+ /** Per-scope `userName -> emittedName` map. Populated when an inner-block
123
+ * `let` shadows an outer binding so TS block-scope (`let x=1; if(c){let x=2}
124
+ * return x` → 1) survives Python's flat function-scope (would otherwise
125
+ * leak 2). Parallel to `localScopes`; pushed/popped together. The outermost
126
+ * scope never renames (function-body lets stay user-facing). Resolved via
127
+ * `resolveLocalRename`; consulted in ident emission. */
128
+ renameStack: Array<Map<string, string>>;
112
129
  propagateStyle: 'value' | 'http-exception';
113
130
  usedPropagation: boolean;
114
131
  /** PR-3b differential-harness opt-in (see BodyEmitOptions.traceHooks). */
@@ -145,7 +162,6 @@ interface BodyEmitContext {
145
162
  * async iterable; sync data is wrapped at iteration entry).
146
163
  *
147
164
  * Both helpers are pure functions on the input; no captures, no globals. */
148
- export declare const KERN_PAIR_HELPERS_PY: string;
149
165
  /** KERN-canonical interpolation formatter for `fmt` / template literals.
150
166
  * Python `f"{v}"` uses `str()`, which gives `True`/`False`/`None` — diverging
151
167
  * from KERN's canonical lowercase `true`/`false`/`null` that TS template
@@ -157,7 +173,6 @@ export declare const KERN_PAIR_HELPERS_PY: string;
157
173
  * `True` → `"True"`. Co-located with the codegen so the production emitter and
158
174
  * the differential harness use byte-identical defs; emitted at module scope
159
175
  * via `BodyEmitResult.helpers` whenever an interpolation is wrapped. */
160
- export declare const KERN_FMT_HELPER_PY: string;
161
176
  /** Emit the body of a native KERN handler as Python source. Returns the
162
177
  * joined body text. Each top-level line is unindented; nested `if`-bodies
163
178
  * carry one level of 4-space indent per level of nesting.
@@ -195,9 +210,6 @@ export declare function emitNativeKernBodyPythonWithImports(handlerNode: IRNode,
195
210
  * `emitPyExprCtx` which threads the live ctx (and therefore the live
196
211
  * imports set) end-to-end. */
197
212
  export declare function emitPyExpression(node: ValueIR, options?: BodyEmitOptions): string;
198
- export declare const KERN_I32_HELPER_PY: string;
199
- export declare const KERN_TMOD_HELPER_PY: string;
200
- export declare const KERN_JS_HELPER_PY: string;
201
213
  export declare function lowerBitwiseAndModuloAST(node: ValueIR): ValueIR;
202
214
  export declare function registerHelpers(node: ValueIR, ctx: BodyEmitContext): void;
203
215
  export {};