Skip to content

Commit 1a7b96a

Browse files
authored
Preserve protected Syntax authority through generated XMD (#776)
* 🔒 Preserve protected component authority through generated wrappers * 🐛 Align admitted protected symbols source provenance
1 parent d56d5ba commit 1a7b96a

14 files changed

Lines changed: 795 additions & 76 deletions

‎architecture.md‎

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3714,7 +3714,7 @@ assembled the run. Above them sits the engine's own, and a name in it means the
37143714
same thing in every execution: whichever host built it, whichever package
37153715
registered what, and whatever the repository holds.
37163716

3717-
One component is in it. `<Syntax />` describes the vocabulary of the site it is
3717+
`<Syntax />` and `<Evaluate>` are in it. `<Syntax />` describes the vocabulary of the site it is
37183718
written at, and a description of a run's vocabulary that anything in the run
37193719
could answer for is a description of nothing — the value of the answer is
37203720
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.
37433743
Every other name in the execution stays the ordinary open import it has always
37443744
been.
37453745

3746-
**Protection is about the answer, not about power.** A protected implementation
3747-
is handed the lexical syntax reference for its site and nothing else: no
3748-
component definitions, no import witness, no invocation capability, no policy
3749-
table, no provider and no registration handle. The body itself is kept in a table
3750-
private to the copy of core that built the implementation and reached only by
3751-
canonical expansion, so an implementation another loaded copy created — which is
3752-
an ordinary arrangement, because a component can be loaded from disk beside its
3753-
own copy — has no body here and no answer to give.
3746+
**Protection is about the answer, not about power.** Canonical expansion hands a
3747+
protected body its lexical syntax reference and the internal operations its
3748+
site owns. `<Evaluate>` receives the captured evaluation profile, its paired
3749+
content projection and an operation that derives a child protected route for
3750+
exact admitted definitions. None is published through a context, component API,
3751+
import answer or public host input.
3752+
3753+
Protected lookup uses exact function identity. The execution's route projects
3754+
an already routed source onto a wrapper without exposing the body; an
3755+
unprotected source projects nothing. Profile sealing projects the first wrapper
3756+
onto the full execution route. `<Evaluate>` derives a fresh child route seeded
3757+
only with its selected sealed definitions, and passes that route and its narrowed
3758+
lexical `SyntaxReference` internally to generated expansion. Generated import
3759+
projects its form/result wrapper only within the child route. Canonical dispatch
3760+
issues the invocation in the routed body's original domain and applies generated
3761+
form checks and read-result collection around either kind of body. Authored
3762+
imports still require their own settled selection frame; a routed function
3763+
cannot repair a missing or multiply selected domain. Paired producers retain
3764+
their lexical site route and operational authority.
3765+
3766+
A trusted provider may therefore delegate and claim canonical self-closing
3767+
`<Syntax>` as a component answer. Its bare form reports the admitted vocabulary;
3768+
its named form renders the enclosing authoring documentation with narrowed
3769+
availability. Verified protected documentation provenance survives sealing even
3770+
when the delegating provider has a different origin. That provenance is
3771+
descriptive data and never a callable lookup key.
3772+
3773+
Routes, projection edges, bodies, domains, callable sets and site operations
3774+
belong to one live execution. Child closure and execution teardown invalidate
3775+
retained operations. A same-name function, structural clone, independent wrapper,
3776+
unadmitted definition or implementation another loaded copy built gains no
3777+
route. Durable records keep their existing structural identities, forms, source,
3778+
profile inputs and results; they contain no callable authority. Continuation
3779+
rebuilds routes from the resuming execution's own verified answers.
37543780

37553781
**The named form is a second question.** Bare `<Syntax />` answers *what may I
37563782
write here*. `<Syntax names={["Elicit"]} />` answers *how do I use this one*, and

‎packages/core/src/components/Evaluate.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import type { Operation } from "effection";
7171
import { getExpansion } from "../expansion.ts";
7272
import { NO_PROFILE, REVOKED } from "../evaluation-profile.ts";
7373
import type { CapturedEntry, CapturedProfile } from "../evaluation-profile.ts";
74-
import { evaluateGeneratedXmd } from "../generated-xmd.ts";
74+
import { evaluateProtectedGeneratedXmd } from "../generated-xmd.ts";
7575
import type {
7676
GeneratedEffectClass,
7777
GeneratedMutation,
@@ -224,8 +224,8 @@ function evaluate(claim: IdentityClaimant): ProtectedBody {
224224
// producer is told about is the vocabulary the fragment is admitted for —
225225
// and reported as availability against the enclosing reference, which keeps
226226
// the authoring documentation the site already had.
227-
const narrowed = narrow(site.syntax, entries);
228-
const source = stated === undefined ? yield* project(site, narrowed) : stated;
227+
const narrowedSyntax = narrow(site.syntax, entries);
228+
const source = stated === undefined ? yield* project(site, narrowedSyntax) : stated;
229229

230230
// Read after the producer has rendered, and exactly once per occurrence: a
231231
// producer may itself commit mutations, and the basis this admission is
@@ -254,9 +254,13 @@ function evaluate(claim: IdentityClaimant): ProtectedBody {
254254
// fragment produced inside another fragment's producer leaves the outer one
255255
// where it was, and a failed one leaves nothing behind.
256256
const leave = yield* profile.enterFragment();
257+
const narrowedBodies = site.narrowProtectedBodies(
258+
entries.admitted.map((entry) => entry.definition.fn),
259+
);
257260
try {
258-
return answer(yield* evaluateGeneratedXmd(request));
261+
return answer(yield* evaluateProtectedGeneratedXmd(request, narrowedBodies, narrowedSyntax));
259262
} finally {
263+
narrowedBodies?.close();
260264
leave();
261265
}
262266
};

‎packages/core/src/components/import-authority.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
*/
2020

2121
import type { ComponentDefinition, FunctionComponentDefinition, SourcePosition } from "../types.ts";
22+
import type { Operation } from "effection";
23+
import type { ComponentInvocation } from "../invocation-identity.ts";
2224
import type {
2325
FormSelections,
2426
InvocationIdentities,
@@ -147,6 +149,12 @@ export interface ExpansionAuthority {
147149
* kept past this execution's teardown reaches a table that is gone.
148150
*/
149151
readonly protectedBodies?: ProtectedBodies;
152+
/** The generated import's form check and result collection, around either body kind. */
153+
readonly invoke?: (
154+
fn: unknown,
155+
invocation: ComponentInvocation,
156+
body: Operation<unknown>,
157+
) => Operation<unknown>;
150158
}
151159

152160
/** Why an answer is not the one canonical execution produced for this name. */

‎packages/core/src/evaluation-profile.ts‎

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ import type {
5454
FragmentFileAccess,
5555
} from "./fragment-capabilities.ts";
5656
import { isFormDispatcher } from "./invocation-identity.ts";
57-
import type { ComponentInvocation } from "./invocation-identity.ts";
57+
import type { ComponentInvocation, ProtectedBodies } from "./invocation-identity.ts";
5858
import type { FetchRequest } from "./fetch-request.ts";
5959
import { normalizeFetchRequest, requestRecord } from "./fetch-request.ts";
6060
import { CORE_REVISION } from "./generated-xmd.ts";
@@ -411,6 +411,8 @@ export type ResolvedAnswers = ReadonlyMap<string, ResolvedAnswer>;
411411
* own operation or a provider's answer, and the two are different grants.
412412
*/
413413
export interface CapturedEntry {
414+
/** Documentation provenance of an exact routed answer; grants no callable authority. */
415+
readonly protectedOrigin?: string;
414416
readonly name: string;
415417
readonly identity: FragmentIdentity;
416418
readonly forms: readonly FragmentForm[];
@@ -553,8 +555,8 @@ export interface PreparedProfile {
553555
readonly live: () => boolean;
554556
/** End them. Registered by canonical execution before any installation runs. */
555557
readonly revoke: () => void;
556-
/** The completed profile, from the answers this execution resolved. */
557-
seal(answers: ResolvedAnswers): Operation<CapturedProfile>;
558+
/** Seal answers, projecting lifetime wrappers through the execution's private operation. */
559+
seal(answers: ResolvedAnswers, project?: ProtectedBodies["project"]): Operation<CapturedProfile>;
558560
}
559561

560562
/**
@@ -605,14 +607,17 @@ export function* prepareEvaluationProfile(
605607
capabilities.revoke();
606608
},
607609
// deno-lint-ignore require-yield
608-
*seal(answers: ResolvedAnswers): Operation<CapturedProfile> {
610+
*seal(
611+
answers: ResolvedAnswers,
612+
project?: ProtectedBodies["project"],
613+
): Operation<CapturedProfile> {
609614
// One sealed implementation per name, built before either table is
610615
// sealed. A name that holds two entries — the self-closing spelling in
611616
// `read` and the paired one in `write` — is one component seen from two
612617
// sides, so both entries carry the same object: two guards over one
613618
// answer would be two lifetimes for one implementation, and which of
614619
// them a fragment reached would depend on which table admitted it.
615-
const sealed = sealAnswers(answered, answers, capabilities);
620+
const sealed = sealAnswers(answered, answers, capabilities, project);
616621
return Object.freeze({
617622
read: sealEntries(read, sealed),
618623
write: sealEntries(write, sealed),
@@ -877,6 +882,7 @@ function spelling(identity: FragmentIdentity): string {
877882

878883
/** One provider-backed name's sealed implementation, shared by every entry. */
879884
interface SealedAnswer {
885+
readonly protectedOrigin?: string;
880886
readonly props: PropsSchema;
881887
readonly definition: FunctionComponentDefinition;
882888
readonly dispatch?: unknown;
@@ -894,6 +900,7 @@ function sealAnswers(
894900
answered: ReadonlyMap<string, FragmentIdentity>,
895901
answers: ResolvedAnswers,
896902
capabilities: CapturedCapabilities,
903+
project: ProtectedBodies["project"] | undefined,
897904
): ReadonlyMap<string, SealedAnswer> {
898905
const sealed = new Map<string, SealedAnswer>();
899906
for (const name of answered.keys()) {
@@ -908,11 +915,14 @@ function sealAnswers(
908915
}
909916
const answer = resolved.definition;
910917
const inner = answer.fn;
918+
const guard = bounded(inner, capabilities);
919+
const protectedOrigin = project?.(inner, guard);
911920
sealed.set(
912921
name,
913922
Object.freeze({
914923
props: detach(answer.props),
915-
definition: Object.freeze({ ...answer, fn: bounded(inner, capabilities) }),
924+
definition: Object.freeze({ ...answer, fn: guard }),
925+
...(protectedOrigin === undefined ? {} : { protectedOrigin }),
916926
// The provider's own dispatcher, when its answer has one. The
917927
// definition above runs behind core's lifetime guard, so what a
918928
// selection would read off it is core's function rather than the
@@ -950,6 +960,7 @@ function sealEntry(entry: PreparedEntry, sealed: ReadonlyMap<string, SealedAnswe
950960
kind: "component-answer" as const,
951961
...described,
952962
definition: answer.definition,
963+
...(answer.protectedOrigin === undefined ? {} : { protectedOrigin: answer.protectedOrigin }),
953964
...(answer.dispatch === undefined ? {} : { dispatch: answer.dispatch }),
954965
});
955966
}

‎packages/core/src/execute.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,6 +2572,7 @@ function* executeDocument(
25722572
declared: declaredImports,
25732573
guarded: identity.protected,
25742574
}),
2575+
identity.protectedBodies.project,
25752576
);
25762577

25772578
const authority: ExpansionAuthority = {

‎packages/core/src/expand.ts‎

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3085,14 +3085,26 @@ function* expandFunctionComponent(
30853085
// the contextual answer below is. Both report it; only this one is
30863086
// beyond reach of the chain that reports it, which is what a component
30873087
// choosing an effect from it needs.
3088-
const issued = issueInvocation(
3089-
expansion.id,
3090-
name,
3091-
selected,
3092-
yield* useScope(),
3093-
!selfClosing,
3094-
dispatcher,
3095-
);
3088+
const frame = yield* useScope();
3089+
// An authored import must still settle its selection frame, including
3090+
// refusing multiple delegations. Only a generated expansion has no such
3091+
// table and issues through its narrowed protected route instead.
3092+
const issued =
3093+
(authority?.identities === undefined
3094+
? authority?.protectedBodies?.issue(
3095+
definition.fn,
3096+
expansion.id,
3097+
name,
3098+
frame,
3099+
!selfClosing,
3100+
dispatcher,
3101+
)
3102+
: undefined) ??
3103+
issueInvocation(expansion.id, name, selected, frame, !selfClosing, dispatcher);
3104+
const dispatchBody = (body: Operation<unknown>) =>
3105+
authority?.invoke === undefined
3106+
? body
3107+
: authority.invoke(definition.fn, issued.invocation, body);
30963108
const projectionState: ProjectionState = {
30973109
invocation,
30983110
projecting: issued.projecting,
@@ -3267,23 +3279,29 @@ function* expandFunctionComponent(
32673279
}
32683280
return renderSegments(outcome.segments);
32693281
});
3282+
let active = true;
32703283
try {
3271-
return yield* guarded(validatedProps, issued.invocation, {
3272-
syntax: authority?.syntax,
3273-
evaluation: authority?.evaluation,
3274-
projectContent: lease?.project,
3275-
});
3284+
return yield* dispatchBody(
3285+
guarded(validatedProps, issued.invocation, {
3286+
syntax: authority?.syntax,
3287+
evaluation: authority?.evaluation,
3288+
projectContent: lease?.project,
3289+
narrowProtectedBodies: (implementations: Iterable<unknown>) =>
3290+
active ? authority?.protectedBodies?.narrow(implementations) : undefined,
3291+
}),
3292+
);
32763293
} finally {
32773294
// Closed in the same breath the issuance is: a projector a body
32783295
// kept authorizes nothing once that body has finished.
32793296
lease?.close();
3297+
active = false;
32803298
issued.close();
32813299
}
32823300
}
32833301
// Ended in the same breath the body is: an issuance a wrapper kept
32843302
// from a finished element authorizes nothing when it is routed here.
32853303
try {
3286-
return yield* definition.fn(validatedProps, issued.invocation);
3304+
return yield* dispatchBody(definition.fn(validatedProps, issued.invocation));
32873305
} finally {
32883306
issued.close();
32893307
}

0 commit comments

Comments
 (0)