@@ -54,7 +54,7 @@ import type {
5454 FragmentFileAccess ,
5555} from "./fragment-capabilities.ts" ;
5656import { isFormDispatcher } from "./invocation-identity.ts" ;
57- import type { ComponentInvocation } from "./invocation-identity.ts" ;
57+ import type { ComponentInvocation , ProtectedBodies } from "./invocation-identity.ts" ;
5858import type { FetchRequest } from "./fetch-request.ts" ;
5959import { normalizeFetchRequest , requestRecord } from "./fetch-request.ts" ;
6060import { 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 */
413413export 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. */
879884interface 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 }
0 commit comments