diff --git a/architecture.md b/architecture.md
index 99f35f838..724660c4a 100644
--- a/architecture.md
+++ b/architecture.md
@@ -3714,7 +3714,7 @@ assembled the run. Above them sits the engine's own, and a name in it means the
same thing in every execution: whichever host built it, whichever package
registered what, and whatever the repository holds.
-One component is in it. `` describes the vocabulary of the site it is
+`` and `` are in it. `` describes the vocabulary of the site it is
written at, and a description of a run's vocabulary that anything in the run
could answer for is a description of nothing — the value of the answer is
exactly that nobody but core produced it.
@@ -3743,14 +3743,40 @@ and verified where the component is invoked, and core's own copy is what runs.
Every other name in the execution stays the ordinary open import it has always
been.
-**Protection is about the answer, not about power.** A protected implementation
-is handed the lexical syntax reference for its site and nothing else: no
-component definitions, no import witness, no invocation capability, no policy
-table, no provider and no registration handle. The body itself is kept in a table
-private to the copy of core that built the implementation and reached only by
-canonical expansion, so an implementation another loaded copy created — which is
-an ordinary arrangement, because a component can be loaded from disk beside its
-own copy — has no body here and no answer to give.
+**Protection is about the answer, not about power.** Canonical expansion hands a
+protected body its lexical syntax reference and the internal operations its
+site owns. `` receives the captured evaluation profile, its paired
+content projection and an operation that derives a child protected route for
+exact admitted definitions. None is published through a context, component API,
+import answer or public host input.
+
+Protected lookup uses exact function identity. The execution's route projects
+an already routed source onto a wrapper without exposing the body; an
+unprotected source projects nothing. Profile sealing projects the first wrapper
+onto the full execution route. `` derives a fresh child route seeded
+only with its selected sealed definitions, and passes that route and its narrowed
+lexical `SyntaxReference` internally to generated expansion. Generated import
+projects its form/result wrapper only within the child route. Canonical dispatch
+issues the invocation in the routed body's original domain and applies generated
+form checks and read-result collection around either kind of body. Authored
+imports still require their own settled selection frame; a routed function
+cannot repair a missing or multiply selected domain. Paired producers retain
+their lexical site route and operational authority.
+
+A trusted provider may therefore delegate and claim canonical self-closing
+`` as a component answer. Its bare form reports the admitted vocabulary;
+its named form renders the enclosing authoring documentation with narrowed
+availability. Verified protected documentation provenance survives sealing even
+when the delegating provider has a different origin. That provenance is
+descriptive data and never a callable lookup key.
+
+Routes, projection edges, bodies, domains, callable sets and site operations
+belong to one live execution. Child closure and execution teardown invalidate
+retained operations. A same-name function, structural clone, independent wrapper,
+unadmitted definition or implementation another loaded copy built gains no
+route. Durable records keep their existing structural identities, forms, source,
+profile inputs and results; they contain no callable authority. Continuation
+rebuilds routes from the resuming execution's own verified answers.
**The named form is a second question.** Bare `` answers *what may I
write here*. `` answers *how do I use this one*, and
diff --git a/packages/core/src/components/Evaluate.ts b/packages/core/src/components/Evaluate.ts
index 438bf0858..f2c9df85b 100644
--- a/packages/core/src/components/Evaluate.ts
+++ b/packages/core/src/components/Evaluate.ts
@@ -71,7 +71,7 @@ import type { Operation } from "effection";
import { getExpansion } from "../expansion.ts";
import { NO_PROFILE, REVOKED } from "../evaluation-profile.ts";
import type { CapturedEntry, CapturedProfile } from "../evaluation-profile.ts";
-import { evaluateGeneratedXmd } from "../generated-xmd.ts";
+import { evaluateProtectedGeneratedXmd } from "../generated-xmd.ts";
import type {
GeneratedEffectClass,
GeneratedMutation,
@@ -224,8 +224,8 @@ function evaluate(claim: IdentityClaimant): ProtectedBody {
// producer is told about is the vocabulary the fragment is admitted for —
// and reported as availability against the enclosing reference, which keeps
// the authoring documentation the site already had.
- const narrowed = narrow(site.syntax, entries);
- const source = stated === undefined ? yield* project(site, narrowed) : stated;
+ const narrowedSyntax = narrow(site.syntax, entries);
+ const source = stated === undefined ? yield* project(site, narrowedSyntax) : stated;
// Read after the producer has rendered, and exactly once per occurrence: a
// producer may itself commit mutations, and the basis this admission is
@@ -254,9 +254,13 @@ function evaluate(claim: IdentityClaimant): ProtectedBody {
// fragment produced inside another fragment's producer leaves the outer one
// where it was, and a failed one leaves nothing behind.
const leave = yield* profile.enterFragment();
+ const narrowedBodies = site.narrowProtectedBodies(
+ entries.admitted.map((entry) => entry.definition.fn),
+ );
try {
- return answer(yield* evaluateGeneratedXmd(request));
+ return answer(yield* evaluateProtectedGeneratedXmd(request, narrowedBodies, narrowedSyntax));
} finally {
+ narrowedBodies?.close();
leave();
}
};
diff --git a/packages/core/src/components/import-authority.ts b/packages/core/src/components/import-authority.ts
index a1d98837e..d6e70849c 100644
--- a/packages/core/src/components/import-authority.ts
+++ b/packages/core/src/components/import-authority.ts
@@ -19,6 +19,8 @@
*/
import type { ComponentDefinition, FunctionComponentDefinition, SourcePosition } from "../types.ts";
+import type { Operation } from "effection";
+import type { ComponentInvocation } from "../invocation-identity.ts";
import type {
FormSelections,
InvocationIdentities,
@@ -147,6 +149,12 @@ export interface ExpansionAuthority {
* kept past this execution's teardown reaches a table that is gone.
*/
readonly protectedBodies?: ProtectedBodies;
+ /** The generated import's form check and result collection, around either body kind. */
+ readonly invoke?: (
+ fn: unknown,
+ invocation: ComponentInvocation,
+ body: Operation,
+ ) => Operation;
}
/** Why an answer is not the one canonical execution produced for this name. */
diff --git a/packages/core/src/evaluation-profile.ts b/packages/core/src/evaluation-profile.ts
index 7e45a1486..ff8eb027a 100644
--- a/packages/core/src/evaluation-profile.ts
+++ b/packages/core/src/evaluation-profile.ts
@@ -54,7 +54,7 @@ import type {
FragmentFileAccess,
} from "./fragment-capabilities.ts";
import { isFormDispatcher } from "./invocation-identity.ts";
-import type { ComponentInvocation } from "./invocation-identity.ts";
+import type { ComponentInvocation, ProtectedBodies } from "./invocation-identity.ts";
import type { FetchRequest } from "./fetch-request.ts";
import { normalizeFetchRequest, requestRecord } from "./fetch-request.ts";
import { CORE_REVISION } from "./generated-xmd.ts";
@@ -411,6 +411,8 @@ export type ResolvedAnswers = ReadonlyMap;
* own operation or a provider's answer, and the two are different grants.
*/
export interface CapturedEntry {
+ /** Documentation provenance of an exact routed answer; grants no callable authority. */
+ readonly protectedOrigin?: string;
readonly name: string;
readonly identity: FragmentIdentity;
readonly forms: readonly FragmentForm[];
@@ -553,8 +555,8 @@ export interface PreparedProfile {
readonly live: () => boolean;
/** End them. Registered by canonical execution before any installation runs. */
readonly revoke: () => void;
- /** The completed profile, from the answers this execution resolved. */
- seal(answers: ResolvedAnswers): Operation;
+ /** Seal answers, projecting lifetime wrappers through the execution's private operation. */
+ seal(answers: ResolvedAnswers, project?: ProtectedBodies["project"]): Operation;
}
/**
@@ -605,14 +607,17 @@ export function* prepareEvaluationProfile(
capabilities.revoke();
},
// deno-lint-ignore require-yield
- *seal(answers: ResolvedAnswers): Operation {
+ *seal(
+ answers: ResolvedAnswers,
+ project?: ProtectedBodies["project"],
+ ): Operation {
// One sealed implementation per name, built before either table is
// sealed. A name that holds two entries — the self-closing spelling in
// `read` and the paired one in `write` — is one component seen from two
// sides, so both entries carry the same object: two guards over one
// answer would be two lifetimes for one implementation, and which of
// them a fragment reached would depend on which table admitted it.
- const sealed = sealAnswers(answered, answers, capabilities);
+ const sealed = sealAnswers(answered, answers, capabilities, project);
return Object.freeze({
read: sealEntries(read, sealed),
write: sealEntries(write, sealed),
@@ -877,6 +882,7 @@ function spelling(identity: FragmentIdentity): string {
/** One provider-backed name's sealed implementation, shared by every entry. */
interface SealedAnswer {
+ readonly protectedOrigin?: string;
readonly props: PropsSchema;
readonly definition: FunctionComponentDefinition;
readonly dispatch?: unknown;
@@ -894,6 +900,7 @@ function sealAnswers(
answered: ReadonlyMap,
answers: ResolvedAnswers,
capabilities: CapturedCapabilities,
+ project: ProtectedBodies["project"] | undefined,
): ReadonlyMap {
const sealed = new Map();
for (const name of answered.keys()) {
@@ -908,11 +915,14 @@ function sealAnswers(
}
const answer = resolved.definition;
const inner = answer.fn;
+ const guard = bounded(inner, capabilities);
+ const protectedOrigin = project?.(inner, guard);
sealed.set(
name,
Object.freeze({
props: detach(answer.props),
- definition: Object.freeze({ ...answer, fn: bounded(inner, capabilities) }),
+ definition: Object.freeze({ ...answer, fn: guard }),
+ ...(protectedOrigin === undefined ? {} : { protectedOrigin }),
// The provider's own dispatcher, when its answer has one. The
// definition above runs behind core's lifetime guard, so what a
// selection would read off it is core's function rather than the
@@ -950,6 +960,7 @@ function sealEntry(entry: PreparedEntry, sealed: ReadonlyMap) =>
+ authority?.invoke === undefined
+ ? body
+ : authority.invoke(definition.fn, issued.invocation, body);
const projectionState: ProjectionState = {
invocation,
projecting: issued.projecting,
@@ -3267,23 +3279,29 @@ function* expandFunctionComponent(
}
return renderSegments(outcome.segments);
});
+ let active = true;
try {
- return yield* guarded(validatedProps, issued.invocation, {
- syntax: authority?.syntax,
- evaluation: authority?.evaluation,
- projectContent: lease?.project,
- });
+ return yield* dispatchBody(
+ guarded(validatedProps, issued.invocation, {
+ syntax: authority?.syntax,
+ evaluation: authority?.evaluation,
+ projectContent: lease?.project,
+ narrowProtectedBodies: (implementations: Iterable) =>
+ active ? authority?.protectedBodies?.narrow(implementations) : undefined,
+ }),
+ );
} finally {
// Closed in the same breath the issuance is: a projector a body
// kept authorizes nothing once that body has finished.
lease?.close();
+ active = false;
issued.close();
}
}
// Ended in the same breath the body is: an issuance a wrapper kept
// from a finished element authorizes nothing when it is routed here.
try {
- return yield* definition.fn(validatedProps, issued.invocation);
+ return yield* dispatchBody(definition.fn(validatedProps, issued.invocation));
} finally {
issued.close();
}
diff --git a/packages/core/src/generated-xmd.ts b/packages/core/src/generated-xmd.ts
index f3d442cf3..bc05979ca 100644
--- a/packages/core/src/generated-xmd.ts
+++ b/packages/core/src/generated-xmd.ts
@@ -129,8 +129,9 @@ import { scanSegments } from "./scanner.ts";
import { sourceDescription } from "./source-position.ts";
import { RESERVED_STRUCTURAL } from "./structural.ts";
import { installFormSelections, invocationForm } from "./invocation-identity.ts";
-import type { FormSelections } from "./invocation-identity.ts";
+import type { FormSelections, ProtectedBodies } from "./invocation-identity.ts";
import type { ComponentInvocation } from "./invocation-identity.ts";
+import type { SyntaxReference } from "./syntax-reference.ts";
import type {
FunctionComponentDefinition,
Json,
@@ -887,6 +888,8 @@ class GeneratedImportAuthority implements ImportAuthority {
readonly #forms = installFormSelections();
/** The form authority under each admitted name's wrapper. */
readonly #dispatchers = new Map();
+ readonly #protectedBodies: ProtectedBodies | undefined;
+ readonly #invocations = new WeakMap