Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
303 changes: 303 additions & 0 deletions .Codex/specs/kern-5-r1-5e1-handler-type-kir/spec.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

362 changes: 362 additions & 0 deletions .Codex/specs/kern-5-r2-m3-31c-module-ownership/spec.md

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions docs/kern-5-release-train.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,14 @@ trusted-publishing/provenance configuration is inspected.
gate: policy-bound hashes/oracles/exclusions, dirty-tree and symlink kill
tests, full fitness wall, and final 3/3 Agon review passed on 2026-07-12.
The SHA-named ignored receipt is generated only after commit.
- [x] R1.5e.1 runtime-handler type KIR bridge: one neutral exact handler-type
owner now feeds both runtime admission and canonical semantic KIR records
for `fn.returns` plus direct structured `param.type`. The other 93
type-annotation locations remain explicitly excluded; structural, module,
catalog, constitution, and coverage identities are versioned together.
The Node 22 full fitness wall and terminal `claude,codex,agy` review passed
on 2026-07-17 with zero verified, needs-check, or speculative findings
(`ALPHA-NO-GO`; no public KIR v1 or formatter claim).
- [ ] R2 M3 runtime and handler ownership.
- [x] M3.1 internal transactional runtime envelope: default-off sync/async
normalization with closed portable values, structural diagnostics, failure
Expand Down Expand Up @@ -755,6 +763,52 @@ trusted-publishing/provenance configuration is inspected.
(`review-1784262755790-138qtd-m3-31b2c1-pre-super-final`). Resumable
helper-to-class effects remain M3.31b2c2 and imported/cross-module identity
remains M3.31c.
- [x] M3.31b2c2 resumable helper-to-class effects: a same-root helper and any
transitive wrapper may now enter an owned class constructor, method, or
getter that performs a capability or print effect. Nested resumable helper
arguments execute left-to-right exactly once before the outer cache lookup;
observable body events bypass memoization, while pure helper/class bodies
retain the bounded cache. Only stdout, stderr, and capability events cross
the helper boundary, so helper-local assignment/iteration trace and private
class identity remain contained. Frozen helper/class registries preserve
suspended-run and overlapping-run isolation, and the planner now reports
the admitted same-root path executable. Direct helper effects remain
rejected, and imported/re-exported/cross-module identity remains the sole
M3.31c follow-up. The final `pnpm fitness:kern-5` wall passed with 432/432
cross-target fixtures, 109/109 class fixtures, 233 native cases, 48/48
checker fixtures, 39/39 validator verdicts, and 40 application fixtures on
three legs plus whole-app boot. The required browser wall passed at 152
modules / 1,535,195 raw / 328,258 gzip bytes / 64 ms cold / 127 ms median,
and all 43 convergence mutations were killed. The terminal
`claude,codex,agy` review completed 3/3 with zero verified and zero
needs-check findings; its one speculative classifier concern was disproved
by scalar-first classification and the nested-helper oracle, while three
non-behavioral allocation/traversal/explicit-boundary nits were retained
(`review-1784269895309-ubvwed-m3-31b2c2-resumable-helper-class`).
- [x] M3.31c module-owned helper and class identity: the canonical source
machine now authenticates the complete linker-built module graph, snapshots
every function/class binding once while preserving alias and re-export
identity, and executes reached helpers and class frames in their defining
module scopes. Helper caches, resumability, recursion labels, receiver
dispatch, and metadata snapshots are binding-keyed rather than flattened by
display name. The shared runtime linker now feeds both execution and linked
capability admission; the planner follows exact imported function,
constructor, method, and getter reachability without poisoning an admitted
path with unused private members. `runner-classes-state` is unified/current
and the convergence manifest has no deferred rows. The final Node 22
`pnpm fitness:kern-5` wall passed with 432/432 cross-target fixtures,
109/109 class fixtures, 233 native cases, 48/48 checker fixtures, 39/39
validator verdicts, and 40 application fixtures on three legs plus
whole-app boot. The required browser wall passed at 153 modules / 1,534,548
raw / 328,497 gzip bytes / 75 ms cold / 108 ms browser, and all 68
convergence mutations were killed. Reviews added and fixed binding-identity
resumability plus imported-helper -> private-class -> private-helper budget
reachability with RED public oracles
(`review-1784275324468-ctztgz`, `review-1784277601118-gduk7h`). The final
full-roster review completed on the fixed diff; its two Agy blockers were
stale or contradicted the exact-reachability contract, Codex reported no
findings, and Claude reported no high-confidence blocker
(`review-1784279863324-vvtril`).

1. Correct the support matrix and make `fitness:kern-5` the planned aggregate,
without pretending missing commands already exist.
Expand All @@ -781,6 +835,9 @@ trusted-publishing/provenance configuration is inspected.
9. R1.5d: version diagnostic/location evidence separately, require stable
non-empty diagnostics and expression spans, then generate the clean-SHA
Alpha manifest. Unknown versions and fields fail before effects.
10. R1.5e.1: bridge the exact executable runtime-handler type domain into
semantic structural/module KIR before M4 consumes the seam. Keep the other
93 type-annotation locations excluded and preserve `ALPHA-NO-GO`.

The original broad R1.4-before-ownership order was withdrawn after the R1.3
probe. The selected projection covers seven of 302 source node kinds, while the
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"test:runtime-abi": "pnpm --filter @kernlang/core test --testPathPatterns='runtime-handler-public' && node --test ./scripts/runtime-handler-public-import-closure.test.mjs && node ./scripts/check-runtime-envelope.mjs",
"test:core-runtime-internalization": "pnpm --filter @kernlang/core --filter kern-lang build && node scripts/check-core-runtime-internalization.mjs && node --test scripts/core-runtime-internalization.test.mjs",
"check:source-runner-convergence": "node ./scripts/check-source-runner-convergence.mjs",
"test:source-runner-convergence": "pnpm --filter @kernlang/core test --testPathPatterns='runner-capability-(class-frame|plan)|source-runner-engine|runtime-envelope-effect-machine-(do|each|expression-v1|helper|lambda|non-root|class-(state|method|getter|inheritance|frame|constructor-super|pre-super|super-method|virtual-method|helper))' && node --test ./scripts/source-runner-convergence.test.mjs ./scripts/source-runner-class-helper-convergence.test.mjs && node ./scripts/check-source-runner-convergence.mjs",
"test:source-runner-convergence": "pnpm --filter @kernlang/core test --testPathPatterns='runner-capability-(class-frame|plan)|source-runner-engine|runtime-envelope-effect-machine-(do|each|expression-v1|helper|lambda|non-root|module-ownership|class-)' && node --test ./scripts/source-runner*-convergence.test.mjs && node ./scripts/check-source-runner-convergence.mjs",
"build:kern-alpha-receipt": "node ./scripts/kir-v1/alpha-receipt.mjs",
"test:infra": "pnpm test:prepush && pnpm test:release-policy && pnpm test:kern-5-fitness && pnpm test:kern-semantic-ownership && pnpm test:kern-ir-eligibility && pnpm test:kern-canonical-value && pnpm test:kern-kir-structural-constitution && pnpm test:kern-kir-structural-codec && pnpm test:kern-kir-module-graph && pnpm test:kern-kir-coverage-closure && pnpm test:kern-kir-evidence && pnpm test:kern-alpha-receipt && pnpm test:kern-runtime-envelope && pnpm test:runtime-abi && pnpm test:core-runtime-internalization && pnpm test:source-runner-convergence",
"fitness": "pnpm fitness:kern-5",
Expand Down
26 changes: 24 additions & 2 deletions packages/core/src/ir/semantics/deferred-expression-preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
internalMachineClassGetterForRead,
internalMachineClassMethodForCall,
} from './internal-effect-machine-class-graph.js';
import { classifyInternalMachineClassScalarValue } from './internal-effect-machine-class-value.js';
import {
classifyInternalMachineClassHelperArgument,
classifyInternalMachineClassScalarValue,
} from './internal-effect-machine-class-value.js';
import { assertInternalMachineDoNamespaceAvailable, parseInternalMachineDo } from './internal-effect-machine-do.js';
import { assertDeferredInternalMachineHelperArgument } from './internal-effect-machine-helper-argument-preflight.js';

Check warning on line 17 in packages/core/src/ir/semantics/deferred-expression-preflight.ts

View check run for this annotation

KERN guard / kern-guard

secrets/high-entropy-string

A high-entropy string that looks like a random secret was committed here. If it is a credential, move it to an env var or secret store and rotate it. Matched value: `./in…(54 chars)`.
Raw output
Confidence: 72%
import { internalMachineHelperCallInValue } from './internal-effect-machine-helper-graph.js';
import { evalDecimalExpression, isDecimalExpression, isDecimalValueExpression } from './portable-decimal-evaluator.js';
import { evalPortableValue } from './portable-machine-evaluator.js';
Expand Down Expand Up @@ -353,8 +357,26 @@
);
}
for (const argument of node.args) {
if (argument.kind === 'arrayLit' || argument.kind === 'objectLit') {
const helperDisposition = classifyInternalMachineClassHelperArgument(argument, env);
if (helperDisposition === 'unsupported') {
throw new Error('portable machine: helper argument is outside the resumable domain');
}
if (helperDisposition === 'pure' && (argument.kind === 'arrayLit' || argument.kind === 'objectLit')) {
assertPortableMachineLetShape(argument, env);
} else if (argument.kind === 'arrayLit') {
assertDeferredInternalMachineHelperArgument(
argument,
env,
deferredBindings,
assertDeferredMachineScalarPreflight,
);
} else if (argument.kind === 'objectLit') {
assertDeferredInternalMachineHelperArgument(
argument,
env,
deferredBindings,
assertDeferredMachineScalarPreflight,
);
} else if (expressionRequiresDeferredMachinePreflight(argument, env, deferredBindings)) {
assertDeferredMachineScalarPreflight(argument, env, deferredBindings);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export function initializeInternalMachineClassFields(
evaluate: EvalPortableValue,
): Record<string, unknown> {
const fields = Object.create(null) as Record<string, unknown>;
// Whole-graph preflight admits only closed field expressions before handler
// locals exist, so caller bindings cannot become class initializer inputs.
for (const candidate of internalMachineClassLineageBaseFirst(cls, registry)) {
initializeInternalMachineClassLayerFields(candidate, fields, env, evaluate);
}
Expand Down Expand Up @@ -86,16 +88,18 @@ export function makeInternalMachineClassConstructorEnv(
values: readonly PortableScalar[],
env: SemanticEnv,
registry: ReadonlyMap<string, RunnerClassBinding>,
stackLabel = `${cls.name}.constructor`,
): SemanticEnv {
const params = cls.constructor?.params ?? [];
const scope = cls.module;
return makeEnv({
bindings: new Map(params.map((param, index) => [param, values[index]])),
capabilities: env.capabilities,
capabilityContext: env.capabilityContext,
runnerCallCache: env.runnerCallCache,
runnerCallStack: [...(env.runnerCallStack ?? []), `${cls.name}.constructor`],
runnerClasses: new Map(registry),
runnerFunctions: env.runnerFunctions,
runnerCallStack: [...(env.runnerCallStack ?? []), stackLabel],
runnerClasses: scope?.classes ?? new Map(registry),
runnerFunctions: scope?.functions ?? env.runnerFunctions,
runnerSuperClass: cls.extendsName,
runnerThis: instance,
seed: env.seed,
Expand All @@ -110,15 +114,17 @@ export function makeInternalMachineClassMemberEnv(
values: readonly PortableScalar[],
env: SemanticEnv,
registry: ReadonlyMap<string, RunnerClassBinding>,
stackLabel = `${cls.name}.${member.name}`,
): SemanticEnv {
const scope = cls.module;
return makeEnv({
bindings: new Map(member.params.map((param, index) => [param, values[index]])),
capabilities: env.capabilities,
capabilityContext: env.capabilityContext,
runnerCallCache: env.runnerCallCache,
runnerCallStack: [...(env.runnerCallStack ?? []), `${cls.name}.${member.name}`],
runnerClasses: new Map(registry),
runnerFunctions: env.runnerFunctions,
runnerCallStack: [...(env.runnerCallStack ?? []), stackLabel],
runnerClasses: scope?.classes ?? new Map(registry),
runnerFunctions: scope?.functions ?? env.runnerFunctions,
runnerSuperClass: cls.extendsName,
runnerThis: instance,
seed: env.seed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ function effectiveBaseConstructor(
cls: RunnerClassBinding,
registry: ReadonlyMap<string, RunnerClassBinding>,
): RunnerClassBinding | undefined {
// Cross-module inheritance is rejected before constructor planning; every
// admitted candidate therefore resolves through this one defining registry.
const seen = new Set<string>();
let current: RunnerClassBinding | undefined = cls.extendsName ? registry.get(cls.extendsName) : undefined;
while (current) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ export type InternalMachineClassValueEvaluator = (
state: InternalEffectMachineState,
) => InternalMachineClassValueGenerator;

function classFrameIdentity(
state: InternalEffectMachineState,
cls: NonNullable<ReturnType<typeof internalMachineClassForNew>>,
member: string,
): string {
const identity = state.moduleGraph?.classIdentity.get(cls);
return identity === undefined ? `${cls.name}.${member}` : `module-class:${identity}:${cls.name}.${member}`;
}

function appendEvaluation<T>(target: TraceEvent[], evaluated: InternalMachineClassEvaluatedValue<T>): T {
target.push(...evaluated.events);
return evaluated.value;
Expand Down Expand Up @@ -73,6 +82,8 @@ function* evaluateInternalMachineClassConstructorLayer(
}
}
if (cls.extendsName) {
// Class collection and lineage admission require the complete chain to
// share this defining module, so the registry is stable across layers.
const base = registry.get(cls.extendsName);
if (!base) throw new Error(`machine class: unknown base class "${cls.extendsName}"`);
const baseValues = plan.superArguments.map((argument) =>
Expand Down Expand Up @@ -104,8 +115,8 @@ export function* evaluateInternalMachineClassNewFrame(
evaluate: InternalMachineClassValueEvaluator,
): InternalMachineClassValueGenerator<RunnerClassInstanceValue> {
const candidate = internalMachineClassForNew(value, env);
const registry = state.classRegistry;
const cls = candidate ? registry?.get(candidate.name) : undefined;
const cls = candidate;
const registry = cls?.module?.classes ?? state.classRegistry;
if (!registry || !cls || !state.helperBodyRunner) throw new Error('machine class: construction is unavailable');

const events: TraceEvent[] = [];
Expand Down Expand Up @@ -139,12 +150,21 @@ export function* evaluateInternalMachineClassMethodFrame(
const values: PortableScalar[] = [];
for (const argument of node.args) values.push(appendEvaluation(events, yield* evaluate(argument, env, state)));
const label = `${resolved.cls.name}.${resolved.method.name}`;
if ((env.runnerCallStack ?? []).includes(label)) {
const frameIdentity = classFrameIdentity(state, resolved.cls, resolved.method.name);
if ((env.runnerCallStack ?? []).includes(frameIdentity)) {
// Keep the compatibility runtime's public recursion diagnostic verbatim.
throw new Error(`runner-class: recursive member call "${label}" is unsupported`);
}
const registry = internalMachineClassRegistryForEnv(env);
const methodEnv = makeInternalMachineClassMemberEnv(resolved.cls, resolved.method, receiver, values, env, registry);
const methodEnv = makeInternalMachineClassMemberEnv(
resolved.cls,
resolved.method,
receiver,
values,
env,
registry,
frameIdentity,
);
const restore = bindInternalEffectMachineState(methodEnv, state);
try {
const trace = yield* bodyRunner(resolved.method.body, methodEnv, state);
Expand Down Expand Up @@ -176,7 +196,20 @@ export function* evaluateInternalMachineClassGetterFrame(
if (!receiver || !bodyRunner) throw new Error('machine class: getter receiver is unavailable');

const registry = internalMachineClassRegistryForEnv(env);
const getterEnv = makeInternalMachineClassMemberEnv(resolved.cls, resolved.getter, receiver, [], env, registry);
const label = `${resolved.cls.name}.${resolved.getter.name}`;
const frameIdentity = classFrameIdentity(state, resolved.cls, resolved.getter.name);
if ((env.runnerCallStack ?? []).includes(frameIdentity)) {
throw new Error(`runner-class: recursive member call "${label}" is unsupported`);
}
const getterEnv = makeInternalMachineClassMemberEnv(
resolved.cls,
resolved.getter,
receiver,
[],
env,
registry,
frameIdentity,
);
const restore = bindInternalEffectMachineState(getterEnv, state);
try {
const trace = yield* bodyRunner(resolved.getter.body, getterEnv, state);
Expand Down
Loading
Loading