@noodleseed/one 0.140.0 → 0.141.0
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.
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +9 -1
- package/node_modules/@noodle-borg/app-package/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/app-package/dist/index.js +1 -0
- package/node_modules/@noodle-borg/{compiler → app-package}/dist/schema-validation.d.ts +2 -1
- package/node_modules/@noodle-borg/app-package/package.json +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-app-sandbox.d.ts +13 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-app-sandbox.js +204 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +22 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +10 -7
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.d.ts +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.js +6 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.d.ts +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.js +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +21 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +39 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.d.ts +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.js +12 -0
- package/node_modules/@noodle-borg/auth/dist/control-plane-token-exchange.d.ts +110 -0
- package/node_modules/@noodle-borg/auth/dist/control-plane-token-exchange.js +217 -0
- package/node_modules/@noodle-borg/auth/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/auth/dist/index.js +1 -0
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +5 -0
- package/node_modules/@noodle-borg/compiler/dist/index.d.ts +1 -2
- package/node_modules/@noodle-borg/compiler/dist/index.js +1 -2
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +16 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +19 -2
- package/node_modules/@noodle-borg/compiler/package.json +0 -2
- package/node_modules/@noodle-borg/compute/dist/code-connector.js +44 -1
- package/node_modules/@noodle-borg/compute/dist/quickjs-engine.js +82 -266
- package/node_modules/@noodle-borg/compute/dist/sandbox-eval.js +276 -0
- package/node_modules/@noodle-borg/compute/dist/worker-pool.js +262 -0
- package/node_modules/@noodle-borg/{service/dist/auth/deploy-gate.d.ts → control-plane/dist/deploy-gates.d.ts} +10 -6
- package/node_modules/@noodle-borg/control-plane/dist/deploy-gates.js +266 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/developer-mcp/dist/capabilities.js +11 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/index.js +1 -1
- package/node_modules/@noodle-borg/protocol/dist/execution-error-mapping.js +13 -1
- package/node_modules/@noodle-borg/protocol/dist/observation.d.ts +2 -1
- package/node_modules/@noodle-borg/protocol/dist/observation.js +2 -1
- package/node_modules/@noodle-borg/runtime/dist/connector/failure-details.d.ts +1 -1
- package/node_modules/@noodle-borg/runtime/dist/connector/failure-details.js +5 -0
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +15 -1
- package/node_modules/@noodle-borg/runtime/dist/connector/types.js +6 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +1 -0
- package/node_modules/@noodle-borg/service/dist/auth/developer-grant-guard.js +2 -1
- package/node_modules/@noodle-borg/service/dist/modules/route-dispatch.js +1 -1
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +15 -3
- package/node_modules/@noodle-borg/service/dist/routes/assistant-clients.js +116 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-dispatch.js +12 -3
- package/node_modules/@noodle-borg/service/dist/routes/assistant-elevation.js +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-stream.js +4 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +8 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +3 -9
- package/node_modules/@noodle-borg/service/dist/routes/assistant-route-http.js +11 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-sandbox.js +12 -47
- package/node_modules/@noodle-borg/service/dist/routes/assistant-session-target.js +34 -8
- package/node_modules/@noodle-borg/service/dist/routes/assistant-transcript.js +27 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +28 -130
- package/node_modules/@noodle-borg/service/dist/routes/config-values.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +8 -2
- package/node_modules/@noodle-borg/service/dist/service.js +15 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +17 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.js +25 -0
- package/node_modules/@noodleseed/assistant/README.md +4 -9
- package/package.json +1 -1
- /package/node_modules/@noodle-borg/{compiler → app-package}/dist/schema-validation.js +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Relay body pinned to `contract/v1/assistant-sandbox-document.html` and the browser client. */
|
|
2
|
+
export declare const ASSISTANT_SANDBOX_DOCUMENT = "<!doctype html><meta charset=\"utf-8\"><style>html,body,iframe{border:0;margin:0;width:100%;height:100%;overflow:hidden}body{background:transparent}</style><body><script>\nlet inner,loaded=false;\nif(self===top)throw new Error('The assistant sandbox relay must be embedded');\nconst standardSandbox='allow-scripts allow-same-origin allow-forms';\nconst rawParentOrigin=new URLSearchParams(location.hash.slice(1)).get('parentOrigin');\nlet expectedParentOrigin;\ntry{const parsed=new URL(rawParentOrigin||'');if(parsed.origin===rawParentOrigin&&/^https?:$/.test(parsed.protocol))expectedParentOrigin=parsed.origin}catch{}\nconst parentTarget=expectedParentOrigin||'*';\nconst ownOrigin=self.origin;\nconst ready={jsonrpc:'2.0',method:'ui/notifications/sandbox-proxy-ready',params:{}};\n// Re-announce until the host acknowledges with the resource: when this document is hosted\n// cross-origin, the first announcement can arrive before the host's load handler is listening.\nconst announce=setInterval(()=>parent.postMessage(ready,parentTarget),120);\nsetTimeout(()=>clearInterval(announce),15000);\naddEventListener('message',(event)=>{\n if(event.source===parent){\n if(expectedParentOrigin&&event.origin!==expectedParentOrigin)return;\n const message=event.data;\n if(message?.method==='ui/notifications/sandbox-resource-ready'){\n clearInterval(announce);\n if(loaded)return;\n loaded=true;\n const requestedSandbox=message.params?.sandbox;\n inner=document.createElement('iframe');\n inner.setAttribute('sandbox',requestedSandbox===standardSandbox?standardSandbox:'allow-scripts');\n inner.setAttribute('referrerpolicy','no-referrer');\n const permissions=message.params?.permissions;\n const allow=[];\n if(permissions?.camera)allow.push('camera');\n if(permissions?.microphone)allow.push('microphone');\n if(permissions?.geolocation)allow.push('geolocation');\n if(permissions?.clipboardWrite)allow.push('clipboard-write');\n if(allow.length)inner.setAttribute('allow',allow.join('; '));\n document.body.append(inner);\n let html=String(message.params?.html||'');\n const csp=message.params?.csp;\n if(csp&&typeof csp==='object'){\n const list=(value,fallback)=>Array.isArray(value)&&value.length?value.join(' '):fallback;\n const resources=Array.isArray(csp.resourceDomains)?csp.resourceDomains:[];\n const suffix=resources.length?' '+resources.join(' '):'';\n const policy=[\"default-src 'none'\",\"script-src 'unsafe-inline'\"+suffix,\"style-src 'unsafe-inline'\"+suffix,'img-src data: blob:'+suffix,'font-src data: blob:'+suffix,'media-src data: blob:'+suffix,'worker-src blob:'+suffix,'connect-src '+list(csp.connectDomains,\"'none'\"),'frame-src '+list(csp.frameDomains,\"'none'\"),\"object-src 'none'\",\"form-action 'none'\",'base-uri '+list(csp.baseUriDomains,\"'none'\")].join('; ');\n const meta='<meta http-equiv=\"Content-Security-Policy\" content=\"'+policy.replace(/&/g,'&').replace(/\"/g,'"')+'\">';\n const doctype=html.match(/^\\s*<!doctype[^>]*>/i);\n html=doctype?html.slice(0,doctype[0].length)+meta+html.slice(doctype[0].length):meta+html;\n }\n try{\n const doc=inner.contentDocument||inner.contentWindow?.document;\n if(!doc)throw new Error('document unavailable');\n doc.open();doc.write(html);doc.close();\n }catch{inner.srcdoc=html}\n return;\n }\n inner?.contentWindow?.postMessage(message,'*');\n } else if(inner && event.source===inner.contentWindow) {\n if(event.origin!==ownOrigin)return;\n parent.postMessage(event.data,parentTarget);\n }\n});\nparent.postMessage(ready,parentTarget);\n</script></body>";
|
|
3
|
+
export interface AssistantSandboxResponsePolicy {
|
|
4
|
+
readonly ok: boolean;
|
|
5
|
+
readonly contentSecurityPolicy: string;
|
|
6
|
+
}
|
|
7
|
+
/** Match a request authority to the configured sandbox origin, normalizing default ports. */
|
|
8
|
+
export declare function isAssistantSandboxHost(requestHost: string | undefined, sandboxBaseUrl: string | undefined): boolean;
|
|
9
|
+
/** Validate an untrusted query and return the CSP response header to apply before navigation. */
|
|
10
|
+
export declare function resolveAssistantSandboxResponsePolicy(requestUrl: string, isolatedOrigin?: boolean): AssistantSandboxResponsePolicy;
|
|
11
|
+
/** Normalize the operator-provided dedicated sandbox origin and keep it off the service origin. */
|
|
12
|
+
export declare function validateAssistantSandboxBaseUrl(value: string, serviceBase: string | undefined): string;
|
|
13
|
+
//# sourceMappingURL=assistant-app-sandbox.d.ts.map
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/** Relay body pinned to `contract/v1/assistant-sandbox-document.html` and the browser client. */
|
|
2
|
+
export const ASSISTANT_SANDBOX_DOCUMENT = `<!doctype html><meta charset="utf-8"><style>html,body,iframe{border:0;margin:0;width:100%;height:100%;overflow:hidden}body{background:transparent}</style><body><script>
|
|
3
|
+
let inner,loaded=false;
|
|
4
|
+
if(self===top)throw new Error('The assistant sandbox relay must be embedded');
|
|
5
|
+
const standardSandbox='allow-scripts allow-same-origin allow-forms';
|
|
6
|
+
const rawParentOrigin=new URLSearchParams(location.hash.slice(1)).get('parentOrigin');
|
|
7
|
+
let expectedParentOrigin;
|
|
8
|
+
try{const parsed=new URL(rawParentOrigin||'');if(parsed.origin===rawParentOrigin&&/^https?:$/.test(parsed.protocol))expectedParentOrigin=parsed.origin}catch{}
|
|
9
|
+
const parentTarget=expectedParentOrigin||'*';
|
|
10
|
+
const ownOrigin=self.origin;
|
|
11
|
+
const ready={jsonrpc:'2.0',method:'ui/notifications/sandbox-proxy-ready',params:{}};
|
|
12
|
+
// Re-announce until the host acknowledges with the resource: when this document is hosted
|
|
13
|
+
// cross-origin, the first announcement can arrive before the host's load handler is listening.
|
|
14
|
+
const announce=setInterval(()=>parent.postMessage(ready,parentTarget),120);
|
|
15
|
+
setTimeout(()=>clearInterval(announce),15000);
|
|
16
|
+
addEventListener('message',(event)=>{
|
|
17
|
+
if(event.source===parent){
|
|
18
|
+
if(expectedParentOrigin&&event.origin!==expectedParentOrigin)return;
|
|
19
|
+
const message=event.data;
|
|
20
|
+
if(message?.method==='ui/notifications/sandbox-resource-ready'){
|
|
21
|
+
clearInterval(announce);
|
|
22
|
+
if(loaded)return;
|
|
23
|
+
loaded=true;
|
|
24
|
+
const requestedSandbox=message.params?.sandbox;
|
|
25
|
+
inner=document.createElement('iframe');
|
|
26
|
+
inner.setAttribute('sandbox',requestedSandbox===standardSandbox?standardSandbox:'allow-scripts');
|
|
27
|
+
inner.setAttribute('referrerpolicy','no-referrer');
|
|
28
|
+
const permissions=message.params?.permissions;
|
|
29
|
+
const allow=[];
|
|
30
|
+
if(permissions?.camera)allow.push('camera');
|
|
31
|
+
if(permissions?.microphone)allow.push('microphone');
|
|
32
|
+
if(permissions?.geolocation)allow.push('geolocation');
|
|
33
|
+
if(permissions?.clipboardWrite)allow.push('clipboard-write');
|
|
34
|
+
if(allow.length)inner.setAttribute('allow',allow.join('; '));
|
|
35
|
+
document.body.append(inner);
|
|
36
|
+
let html=String(message.params?.html||'');
|
|
37
|
+
const csp=message.params?.csp;
|
|
38
|
+
if(csp&&typeof csp==='object'){
|
|
39
|
+
const list=(value,fallback)=>Array.isArray(value)&&value.length?value.join(' '):fallback;
|
|
40
|
+
const resources=Array.isArray(csp.resourceDomains)?csp.resourceDomains:[];
|
|
41
|
+
const suffix=resources.length?' '+resources.join(' '):'';
|
|
42
|
+
const policy=["default-src 'none'","script-src 'unsafe-inline'"+suffix,"style-src 'unsafe-inline'"+suffix,'img-src data: blob:'+suffix,'font-src data: blob:'+suffix,'media-src data: blob:'+suffix,'worker-src blob:'+suffix,'connect-src '+list(csp.connectDomains,"'none'"),'frame-src '+list(csp.frameDomains,"'none'"),"object-src 'none'","form-action 'none'",'base-uri '+list(csp.baseUriDomains,"'none'")].join('; ');
|
|
43
|
+
const meta='<meta http-equiv="Content-Security-Policy" content="'+policy.replace(/&/g,'&').replace(/"/g,'"')+'">';
|
|
44
|
+
const doctype=html.match(/^\\s*<!doctype[^>]*>/i);
|
|
45
|
+
html=doctype?html.slice(0,doctype[0].length)+meta+html.slice(doctype[0].length):meta+html;
|
|
46
|
+
}
|
|
47
|
+
try{
|
|
48
|
+
const doc=inner.contentDocument||inner.contentWindow?.document;
|
|
49
|
+
if(!doc)throw new Error('document unavailable');
|
|
50
|
+
doc.open();doc.write(html);doc.close();
|
|
51
|
+
}catch{inner.srcdoc=html}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
inner?.contentWindow?.postMessage(message,'*');
|
|
55
|
+
} else if(inner && event.source===inner.contentWindow) {
|
|
56
|
+
if(event.origin!==ownOrigin)return;
|
|
57
|
+
parent.postMessage(event.data,parentTarget);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
parent.postMessage(ready,parentTarget);
|
|
61
|
+
</script></body>`;
|
|
62
|
+
const COMPATIBILITY_CSP = "sandbox allow-scripts; default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src https: data: blob:; font-src https: data:; frame-src about:; form-action 'none'; base-uri 'none'; frame-ancestors *";
|
|
63
|
+
const MAX_CSP_QUERY_LENGTH = 8_192;
|
|
64
|
+
const MAX_DOMAINS_PER_DIRECTIVE = 32;
|
|
65
|
+
const CSP_KEYS = ['connectDomains', 'resourceDomains', 'frameDomains', 'baseUriDomains'];
|
|
66
|
+
const CSP_KEY_SET = new Set(CSP_KEYS);
|
|
67
|
+
/** Match a request authority to the configured sandbox origin, normalizing default ports. */
|
|
68
|
+
export function isAssistantSandboxHost(requestHost, sandboxBaseUrl) {
|
|
69
|
+
if (requestHost === undefined || sandboxBaseUrl === undefined)
|
|
70
|
+
return false;
|
|
71
|
+
try {
|
|
72
|
+
const sandboxUrl = new URL(sandboxBaseUrl);
|
|
73
|
+
const requestUrl = new URL(`${sandboxUrl.protocol}//${requestHost}`);
|
|
74
|
+
return requestUrl.href === `${sandboxUrl.origin}/`;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function isRecord(value) {
|
|
81
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
82
|
+
}
|
|
83
|
+
function isLoopback(hostname) {
|
|
84
|
+
return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';
|
|
85
|
+
}
|
|
86
|
+
function normalizeCspDomain(value, connect) {
|
|
87
|
+
if (value.length > 512 || /[;\r\n'"\s]/u.test(value))
|
|
88
|
+
return undefined;
|
|
89
|
+
try {
|
|
90
|
+
const parsed = new URL(value);
|
|
91
|
+
const secure = parsed.protocol === 'https:' || (connect && parsed.protocol === 'wss:');
|
|
92
|
+
const local = isLoopback(parsed.hostname) &&
|
|
93
|
+
(parsed.protocol === 'http:' || (connect && parsed.protocol === 'ws:'));
|
|
94
|
+
if (!secure && !local)
|
|
95
|
+
return undefined;
|
|
96
|
+
if (parsed.username ||
|
|
97
|
+
parsed.password ||
|
|
98
|
+
parsed.pathname !== '/' ||
|
|
99
|
+
parsed.search ||
|
|
100
|
+
parsed.hash) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return parsed.origin;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function parseSandboxCsp(requestUrl) {
|
|
110
|
+
const url = new URL(requestUrl, 'http://sandbox.invalid');
|
|
111
|
+
const values = url.searchParams.getAll('csp');
|
|
112
|
+
if (values.length === 0)
|
|
113
|
+
return { ok: true };
|
|
114
|
+
const serialized = values[0];
|
|
115
|
+
if (values.length !== 1 || serialized === undefined || serialized.length > MAX_CSP_QUERY_LENGTH) {
|
|
116
|
+
return { ok: false };
|
|
117
|
+
}
|
|
118
|
+
let parsed;
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(serialized);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return { ok: false };
|
|
124
|
+
}
|
|
125
|
+
if (!isRecord(parsed) || Object.keys(parsed).some((key) => !CSP_KEY_SET.has(key))) {
|
|
126
|
+
return { ok: false };
|
|
127
|
+
}
|
|
128
|
+
const csp = {};
|
|
129
|
+
for (const key of CSP_KEYS) {
|
|
130
|
+
const raw = parsed[key];
|
|
131
|
+
if (raw === undefined)
|
|
132
|
+
continue;
|
|
133
|
+
if (!Array.isArray(raw) || raw.length > MAX_DOMAINS_PER_DIRECTIVE)
|
|
134
|
+
return { ok: false };
|
|
135
|
+
const normalized = [];
|
|
136
|
+
for (const entry of raw) {
|
|
137
|
+
if (typeof entry !== 'string')
|
|
138
|
+
return { ok: false };
|
|
139
|
+
const domain = normalizeCspDomain(entry, key === 'connectDomains');
|
|
140
|
+
if (!domain)
|
|
141
|
+
return { ok: false };
|
|
142
|
+
normalized.push(domain);
|
|
143
|
+
}
|
|
144
|
+
csp[key] = [...new Set(normalized)];
|
|
145
|
+
}
|
|
146
|
+
return { ok: true, value: csp };
|
|
147
|
+
}
|
|
148
|
+
function sources(values, fallback) {
|
|
149
|
+
return values && values.length > 0 ? values.join(' ') : fallback;
|
|
150
|
+
}
|
|
151
|
+
function sandboxCsp(csp, isolatedOrigin) {
|
|
152
|
+
const resources = csp.resourceDomains ?? [];
|
|
153
|
+
const resourceSuffix = resources.length > 0 ? ` ${resources.join(' ')}` : '';
|
|
154
|
+
return [
|
|
155
|
+
isolatedOrigin
|
|
156
|
+
? 'sandbox allow-scripts allow-same-origin allow-forms'
|
|
157
|
+
: 'sandbox allow-scripts',
|
|
158
|
+
"default-src 'none'",
|
|
159
|
+
`script-src 'self' 'unsafe-inline'${resourceSuffix}`,
|
|
160
|
+
`style-src 'self' 'unsafe-inline'${resourceSuffix}`,
|
|
161
|
+
`img-src 'self' data: blob:${resourceSuffix}`,
|
|
162
|
+
`font-src 'self' data: blob:${resourceSuffix}`,
|
|
163
|
+
`media-src 'self' data: blob:${resourceSuffix}`,
|
|
164
|
+
`worker-src 'self' blob:${resourceSuffix}`,
|
|
165
|
+
`connect-src 'self'${csp.connectDomains && csp.connectDomains.length > 0 ? ` ${csp.connectDomains.join(' ')}` : ''}`,
|
|
166
|
+
`frame-src ${sources(csp.frameDomains, "'none'")}`,
|
|
167
|
+
"object-src 'none'",
|
|
168
|
+
"form-action 'none'",
|
|
169
|
+
`base-uri ${sources(csp.baseUriDomains, "'none'")}`,
|
|
170
|
+
'frame-ancestors *',
|
|
171
|
+
].join('; ');
|
|
172
|
+
}
|
|
173
|
+
/** Validate an untrusted query and return the CSP response header to apply before navigation. */
|
|
174
|
+
export function resolveAssistantSandboxResponsePolicy(requestUrl, isolatedOrigin = false) {
|
|
175
|
+
const parsed = parseSandboxCsp(requestUrl);
|
|
176
|
+
if (!parsed.ok)
|
|
177
|
+
return { ok: false, contentSecurityPolicy: COMPATIBILITY_CSP };
|
|
178
|
+
return {
|
|
179
|
+
ok: true,
|
|
180
|
+
contentSecurityPolicy: parsed.value !== undefined || isolatedOrigin
|
|
181
|
+
? sandboxCsp(parsed.value ?? {}, isolatedOrigin)
|
|
182
|
+
: COMPATIBILITY_CSP,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** Normalize the operator-provided dedicated sandbox origin and keep it off the service origin. */
|
|
186
|
+
export function validateAssistantSandboxBaseUrl(value, serviceBase) {
|
|
187
|
+
let parsed;
|
|
188
|
+
try {
|
|
189
|
+
parsed = new URL(value);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
throw new Error('assistantSandboxBaseUrl must be an exact HTTPS or HTTP loopback origin');
|
|
193
|
+
}
|
|
194
|
+
const loopback = isLoopback(parsed.hostname);
|
|
195
|
+
if (parsed.origin !== value ||
|
|
196
|
+
(parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && loopback))) {
|
|
197
|
+
throw new Error('assistantSandboxBaseUrl must be an exact HTTPS or HTTP loopback origin');
|
|
198
|
+
}
|
|
199
|
+
if (serviceBase !== undefined && new URL(serviceBase).origin === parsed.origin) {
|
|
200
|
+
throw new Error('assistantSandboxBaseUrl must use an origin separate from publicBaseUrl');
|
|
201
|
+
}
|
|
202
|
+
return parsed.origin;
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=assistant-app-sandbox.js.map
|
|
@@ -50,6 +50,14 @@ export interface AssistantSessionRecord {
|
|
|
50
50
|
* turns still belong to the surface that admitted them.
|
|
51
51
|
*/
|
|
52
52
|
readonly publicEmbedId?: string;
|
|
53
|
+
/**
|
|
54
|
+
* The exact authored surface this session is bound to: 'public' is the deployment's one
|
|
55
|
+
* public-audience surface (public or mixed mode), 'authenticated' its authenticated surface. Written
|
|
56
|
+
* at mint from the origin that admitted the session; absent only on records minted before binding
|
|
57
|
+
* existed and on pre-surfaces artifacts, where the projection derives the surface from the pinned
|
|
58
|
+
* deployment and the session's origin instead. Never a deployment-wide union.
|
|
59
|
+
*/
|
|
60
|
+
readonly boundSurface?: 'public' | 'authenticated';
|
|
53
61
|
/**
|
|
54
62
|
* The intercepted tool a successful elevation may re-attempt as the session's first elevated
|
|
55
63
|
* turn. Armed only in the same statement that elevates; consumed (or mooted by the visitor's
|
|
@@ -81,6 +89,13 @@ export interface AssistantTurnConsumption {
|
|
|
81
89
|
export interface AssistantHistoryMessage {
|
|
82
90
|
readonly role: 'user' | 'assistant';
|
|
83
91
|
readonly content: string;
|
|
92
|
+
/**
|
|
93
|
+
* Whether this row ever appeared in the visible panel (`visible`) or is model-facing scaffolding —
|
|
94
|
+
* resolution summaries carrying tool output, `[platform]` resume prompts (`narration`). Only rows
|
|
95
|
+
* explicitly tagged `visible` may be replayed to a browser (ADR 0141/0201 amendments 2026-08-26);
|
|
96
|
+
* an untagged legacy row is treated as narration, fail closed.
|
|
97
|
+
*/
|
|
98
|
+
readonly kind?: 'visible' | 'narration';
|
|
84
99
|
}
|
|
85
100
|
/** Latest prompt messages retained for model continuity; turn admission is tracked separately. */
|
|
86
101
|
export declare const ASSISTANT_HISTORY_MAX_MESSAGES = 40;
|
|
@@ -147,6 +162,12 @@ export interface AssistantStore {
|
|
|
147
162
|
readonly origin: string;
|
|
148
163
|
/** Backend-verified customer routes; absent means unchanged, never cleared. */
|
|
149
164
|
readonly customerRouting?: AssistantSessionRecord['customerRouting'];
|
|
165
|
+
/**
|
|
166
|
+
* The surface that owns the elevation origin: the session lands on that surface's projection —
|
|
167
|
+
* capabilities, instructions, budgets, attribution — in the same statement that elevates
|
|
168
|
+
* (ADR 0201 amendment 2026-08-26). Absent means unchanged, the pre-surfaces artifact case.
|
|
169
|
+
*/
|
|
170
|
+
readonly boundSurface?: AssistantSessionRecord['boundSurface'];
|
|
150
171
|
/** Arms the one-shot resume of the intercepted tool, in the same statement that elevates. */
|
|
151
172
|
readonly pendingResume?: AssistantPendingResume;
|
|
152
173
|
readonly now: Date;
|
|
@@ -209,6 +230,7 @@ export declare class InMemoryAssistantStore implements AssistantStore {
|
|
|
209
230
|
readonly clientId: string;
|
|
210
231
|
readonly origin: string;
|
|
211
232
|
readonly customerRouting?: AssistantSessionRecord['customerRouting'];
|
|
233
|
+
readonly boundSurface?: AssistantSessionRecord['boundSurface'];
|
|
212
234
|
readonly pendingResume?: AssistantPendingResume;
|
|
213
235
|
readonly now: Date;
|
|
214
236
|
}): Promise<AssistantSessionElevation>;
|
|
@@ -102,13 +102,15 @@ export class InMemoryAssistantStore {
|
|
|
102
102
|
return { ok: false, reason: 'already_elevated' };
|
|
103
103
|
}
|
|
104
104
|
const token = mintSecret('nss_');
|
|
105
|
-
// History and publicEmbedId ride along untouched: same conversation, same admitting surface
|
|
106
|
-
// clientId and origin do NOT: a public session's clientId is the embed id and its
|
|
107
|
-
// anonymous mint's page — but the issuer of every post-elevation delegated exchange
|
|
108
|
-
// clientId (ADR 0152), and CORS on every session route follows origin, which after a
|
|
109
|
-
// redirect is wherever the elevating client says the conversation continues. Customer
|
|
110
|
-
// arrives here too: elevation is the first authenticated moment, so it is the only chance
|
|
111
|
-
// routed connector's session ever gets its backend-verified routes.
|
|
105
|
+
// History and publicEmbedId ride along untouched: same conversation, same admitting surface for
|
|
106
|
+
// admission. clientId and origin do NOT: a public session's clientId is the embed id and its
|
|
107
|
+
// origin is the anonymous mint's page — but the issuer of every post-elevation delegated exchange
|
|
108
|
+
// derives from clientId (ADR 0152), and CORS on every session route follows origin, which after a
|
|
109
|
+
// login redirect is wherever the elevating client says the conversation continues. Customer
|
|
110
|
+
// routing arrives here too: elevation is the first authenticated moment, so it is the only chance
|
|
111
|
+
// a routed connector's session ever gets its backend-verified routes. The surface binding follows
|
|
112
|
+
// the landing origin: signing in on the app surface lands the conversation on the app surface's
|
|
113
|
+
// projection (ADR 0201 amendment 2026-08-26); absent means unchanged.
|
|
112
114
|
const elevated = {
|
|
113
115
|
...session,
|
|
114
116
|
tokenHash: digest(token),
|
|
@@ -118,6 +120,7 @@ export class InMemoryAssistantStore {
|
|
|
118
120
|
...(input.customerRouting
|
|
119
121
|
? { customerRouting: Object.freeze({ ...input.customerRouting }) }
|
|
120
122
|
: {}),
|
|
123
|
+
...(input.boundSurface ? { boundSurface: input.boundSurface } : {}),
|
|
121
124
|
...(input.pendingResume ? { pendingResume: { ...input.pendingResume } } : {}),
|
|
122
125
|
};
|
|
123
126
|
this.#sessions.set(session.id, elevated);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AssistantHistoryMessage } from './assistant-store.js';
|
|
2
|
+
/**
|
|
3
|
+
* The bounded visible-transcript projection (ADR 0141/0201 amendments 2026-08-26): what a widget may
|
|
4
|
+
* repaint after a full-page navigation — the sign-in redirect above all — from the session's own
|
|
5
|
+
* short-lived history. Transport-free like every gateway decision; `packages/service` adapts.
|
|
6
|
+
*
|
|
7
|
+
* Only rows explicitly tagged `visible` project. Everything else fails closed out of the transcript:
|
|
8
|
+
* `narration` rows are model-facing scaffolding (resolution summaries carrying tool output,
|
|
9
|
+
* `[platform]` resume prompts the visitor never saw), and an untagged legacy row cannot prove which
|
|
10
|
+
* it was. This is a read-only projection over state that already exists and already expires with the
|
|
11
|
+
* session — it is not a durable transcript, and nothing here may extend a retention window.
|
|
12
|
+
*/
|
|
13
|
+
export interface AssistantTranscriptEntry {
|
|
14
|
+
readonly role: 'user' | 'assistant';
|
|
15
|
+
readonly text: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function visibleTranscript(history: readonly AssistantHistoryMessage[]): readonly AssistantTranscriptEntry[];
|
|
18
|
+
//# sourceMappingURL=assistant-transcript.d.ts.map
|
|
@@ -20,6 +20,7 @@ export interface ElevationPorts {
|
|
|
20
20
|
readonly clientId: string;
|
|
21
21
|
readonly origin: string;
|
|
22
22
|
readonly customerRouting?: AssistantSessionRecord['customerRouting'];
|
|
23
|
+
readonly boundSurface?: AssistantSessionRecord['boundSurface'];
|
|
23
24
|
readonly pendingResume?: AssistantPendingResume;
|
|
24
25
|
readonly now: Date;
|
|
25
26
|
}) => Promise<{
|
|
@@ -47,6 +48,12 @@ export interface ElevationRequest {
|
|
|
47
48
|
readonly origin: string;
|
|
48
49
|
/** Backend-verified customer routes, validated by the route; absent means unchanged. */
|
|
49
50
|
readonly customerRouting?: AssistantSessionRecord['customerRouting'];
|
|
51
|
+
/**
|
|
52
|
+
* The surface that owns the (already validated) elevation origin: the conversation lands on that
|
|
53
|
+
* surface's projection (ADR 0201 amendment 2026-08-26). Absent means unchanged — the pre-surfaces
|
|
54
|
+
* artifact shape, which has no surfaces to land on.
|
|
55
|
+
*/
|
|
56
|
+
readonly boundSurface?: AssistantSessionRecord['boundSurface'];
|
|
50
57
|
/**
|
|
51
58
|
* Arm the one-shot resume of the intercepted tool on success. Only an explicit `true` arms —
|
|
52
59
|
* the route owns the default policy, and probes (the doctor's synthetic elevation) pass nothing.
|
|
@@ -23,6 +23,7 @@ export async function completeElevation(request, ports) {
|
|
|
23
23
|
clientId: request.clientId,
|
|
24
24
|
origin: request.origin,
|
|
25
25
|
...(request.customerRouting ? { customerRouting: request.customerRouting } : {}),
|
|
26
|
+
...(request.boundSurface ? { boundSurface: request.boundSurface } : {}),
|
|
26
27
|
// Armed in the same statement that elevates: the claim happened above, so the intercepted
|
|
27
28
|
// tool is in hand exactly when the session mutation runs.
|
|
28
29
|
...(request.resume === true
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Assistant gateway contracts and in-memory adapters safe for the published CLI runtime. */
|
|
2
2
|
export * from './artifact-projection.js';
|
|
3
|
+
export * from './assistant-app-sandbox.js';
|
|
3
4
|
export * from './assistant-context.js';
|
|
4
5
|
export * from './assistant-customer-issuer.js';
|
|
5
6
|
export * from './assistant-customer-routing.js';
|
|
@@ -9,6 +10,7 @@ export * from './assistant-interactive.js';
|
|
|
9
10
|
export * from './assistant-presentation.js';
|
|
10
11
|
export * from './assistant-sensitive-values.js';
|
|
11
12
|
export * from './assistant-store.js';
|
|
13
|
+
export * from './assistant-transcript.js';
|
|
12
14
|
export * from './elevation.js';
|
|
13
15
|
export * from './elevation-store.js';
|
|
14
16
|
export * from './embed-script.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Assistant gateway contracts and in-memory adapters safe for the published CLI runtime. */
|
|
2
2
|
export * from './artifact-projection.js';
|
|
3
|
+
export * from './assistant-app-sandbox.js';
|
|
3
4
|
export * from './assistant-context.js';
|
|
4
5
|
export * from './assistant-customer-issuer.js';
|
|
5
6
|
export * from './assistant-customer-routing.js';
|
|
@@ -9,6 +10,7 @@ export * from './assistant-interactive.js';
|
|
|
9
10
|
export * from './assistant-presentation.js';
|
|
10
11
|
export * from './assistant-sensitive-values.js';
|
|
11
12
|
export * from './assistant-store.js';
|
|
13
|
+
export * from './assistant-transcript.js';
|
|
12
14
|
export * from './elevation.js';
|
|
13
15
|
export * from './elevation-store.js';
|
|
14
16
|
export * from './embed-script.js';
|
|
@@ -17,5 +17,26 @@ export interface PublicSurface {
|
|
|
17
17
|
readonly capabilities: readonly SurfaceCapabilityRef[];
|
|
18
18
|
readonly instructions?: string;
|
|
19
19
|
}
|
|
20
|
+
export interface AuthenticatedSurface {
|
|
21
|
+
readonly origins: readonly string[];
|
|
22
|
+
/** Absent means the authored intent is the whole server; a declared list narrows it. */
|
|
23
|
+
readonly capabilities?: readonly SurfaceCapabilityRef[];
|
|
24
|
+
readonly instructions?: string;
|
|
25
|
+
}
|
|
26
|
+
/** The deployment's one authenticated surface, read with the same shape discipline as the public one. */
|
|
27
|
+
export declare function authenticatedSurfaceOf(assistant: unknown): AuthenticatedSurface | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Which authored surface owns this origin (ADR 0201: every session binds one exact surface).
|
|
30
|
+
* `pre-surfaces` is the released artifact shape from before surfaces existed — the deployment-wide
|
|
31
|
+
* origin union remains its only contract. `unowned` means surfaces exist and none lists the origin:
|
|
32
|
+
* the caller must refuse, never fall back to the union.
|
|
33
|
+
*/
|
|
34
|
+
export declare function surfaceBindingForOrigin(assistant: unknown, origin: string): {
|
|
35
|
+
readonly kind: 'public' | 'authenticated';
|
|
36
|
+
} | {
|
|
37
|
+
readonly kind: 'unowned';
|
|
38
|
+
} | {
|
|
39
|
+
readonly kind: 'pre-surfaces';
|
|
40
|
+
};
|
|
20
41
|
export declare function publicSurfaceOf(assistant: unknown): PublicSurface | undefined;
|
|
21
42
|
//# sourceMappingURL=public-surface.d.ts.map
|
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
/** The deployment's one authenticated surface, read with the same shape discipline as the public one. */
|
|
2
|
+
export function authenticatedSurfaceOf(assistant) {
|
|
3
|
+
const surfaces = assistant?.surfaces;
|
|
4
|
+
if (!Array.isArray(surfaces))
|
|
5
|
+
return undefined;
|
|
6
|
+
for (const entry of surfaces) {
|
|
7
|
+
const { mode, origins, capabilities, instructions } = entry;
|
|
8
|
+
if (mode !== 'authenticated')
|
|
9
|
+
continue;
|
|
10
|
+
return {
|
|
11
|
+
origins: Array.isArray(origins) ? origins : [],
|
|
12
|
+
// Unlike a public surface, an omitted list is the authored whole-server intent, not a hole:
|
|
13
|
+
// the compiler requires the allowlist only on public-audience surfaces.
|
|
14
|
+
...(Array.isArray(capabilities)
|
|
15
|
+
? { capabilities: capabilities }
|
|
16
|
+
: {}),
|
|
17
|
+
...(typeof instructions === 'string' ? { instructions } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Which authored surface owns this origin (ADR 0201: every session binds one exact surface).
|
|
24
|
+
* `pre-surfaces` is the released artifact shape from before surfaces existed — the deployment-wide
|
|
25
|
+
* origin union remains its only contract. `unowned` means surfaces exist and none lists the origin:
|
|
26
|
+
* the caller must refuse, never fall back to the union.
|
|
27
|
+
*/
|
|
28
|
+
export function surfaceBindingForOrigin(assistant, origin) {
|
|
29
|
+
const surfaces = assistant?.surfaces;
|
|
30
|
+
if (!Array.isArray(surfaces))
|
|
31
|
+
return { kind: 'pre-surfaces' };
|
|
32
|
+
const publicSurface = publicSurfaceOf(assistant);
|
|
33
|
+
if (publicSurface?.origins.includes(origin))
|
|
34
|
+
return { kind: 'public' };
|
|
35
|
+
if (authenticatedSurfaceOf(assistant)?.origins.includes(origin)) {
|
|
36
|
+
return { kind: 'authenticated' };
|
|
37
|
+
}
|
|
38
|
+
return { kind: 'unowned' };
|
|
39
|
+
}
|
|
1
40
|
export function publicSurfaceOf(assistant) {
|
|
2
41
|
const surfaces = assistant?.surfaces;
|
|
3
42
|
if (!Array.isArray(surfaces))
|
|
@@ -19,4 +19,11 @@ export declare function shouldAutoResume(override: boolean | undefined): boolean
|
|
|
19
19
|
* replayed: the model re-derives them from history under the new principal.
|
|
20
20
|
*/
|
|
21
21
|
export declare function resumeTurnMessage(tool: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* The honest fail-closed variant: the elevation landed the conversation on a surface whose
|
|
24
|
+
* projection does not offer the intercepted tool (ADR 0201 amendment 2026-08-26). Silence would
|
|
25
|
+
* read as a hang and a hallucinated attempt would be worse, so the model is told plainly to say
|
|
26
|
+
* the action is not available here and to offer what this surface can do.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resumeUnavailableMessage(tool: string): string;
|
|
22
29
|
//# sourceMappingURL=session-resume.d.ts.map
|
|
@@ -25,4 +25,16 @@ export function resumeTurnMessage(tool) {
|
|
|
25
25
|
`which returned sign_in_required. Call "${tool}" now to complete that request yourself — do ` +
|
|
26
26
|
'not ask them to repeat or re-confirm it — then reply naturally with the outcome.');
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* The honest fail-closed variant: the elevation landed the conversation on a surface whose
|
|
30
|
+
* projection does not offer the intercepted tool (ADR 0201 amendment 2026-08-26). Silence would
|
|
31
|
+
* read as a hang and a hallucinated attempt would be worse, so the model is told plainly to say
|
|
32
|
+
* the action is not available here and to offer what this surface can do.
|
|
33
|
+
*/
|
|
34
|
+
export function resumeUnavailableMessage(tool) {
|
|
35
|
+
return (`[platform] The visitor has just signed in. Their pre-sign-in request needed the "${tool}" ` +
|
|
36
|
+
'tool, but the surface this signed-in conversation continues on does not offer it. Tell them ' +
|
|
37
|
+
'plainly that this action is not available here, and offer what the current tools can do ' +
|
|
38
|
+
'instead. Do not pretend to run it.');
|
|
39
|
+
}
|
|
28
40
|
//# sourceMappingURL=session-resume.js.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type JWTVerifyGetKey } from 'jose';
|
|
2
|
+
/**
|
|
3
|
+
* The inbound half of the delegated token exchange (RFC 8693), serving exactly one first-party
|
|
4
|
+
* caller: the hosted assistant's control-plane connector. We implement the same target-endpoint
|
|
5
|
+
* contract we ask every customer token endpoint to implement (docs/spec/connectors.md, ADR 0152) —
|
|
6
|
+
* verify the platform-signed assertion, identify the user by `(customer_identity.issuer, sub)`,
|
|
7
|
+
* honour the 120s lifetime and single-use `jti`, and return a standard token response.
|
|
8
|
+
*
|
|
9
|
+
* The same issuer and signing key mint control-plane access tokens and exchange assertions, so
|
|
10
|
+
* **audience separation is the entire security boundary**: this exchange accepts only the dedicated
|
|
11
|
+
* exchange URN below and structurally refuses every resource audience (all of which are URLs) —
|
|
12
|
+
* including our own access tokens presented as subject tokens. The minted token is capability-scoped
|
|
13
|
+
* through the developer-grant machinery, so rollback/config-write can never be stored on it and
|
|
14
|
+
* every control-plane route re-checks live org membership per request.
|
|
15
|
+
*
|
|
16
|
+
* This module is the Express-free decision core; the hosted service wraps it in a thin HTTP adapter
|
|
17
|
+
* and supplies every policy port below (ADR 0218).
|
|
18
|
+
*/
|
|
19
|
+
export declare const CONTROL_PLANE_EXCHANGE_AUDIENCE = "urn:noodleseed:control-plane:delegated-exchange";
|
|
20
|
+
export declare const CONTROL_PLANE_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange";
|
|
21
|
+
/** Durable single-use claims for inbound exchange assertion `jti`s; false = replayed. */
|
|
22
|
+
export interface TokenExchangeJtiStore {
|
|
23
|
+
consume(jti: string, expiresAtMs: number, nowMs: number): Promise<boolean>;
|
|
24
|
+
}
|
|
25
|
+
/** Local/test-only; production deployments need durable shared atomic replay storage. */
|
|
26
|
+
export declare class InMemoryTokenExchangeJtiStore implements TokenExchangeJtiStore {
|
|
27
|
+
#private;
|
|
28
|
+
consume(jti: string, expiresAtMs: number, nowMs: number): Promise<boolean>;
|
|
29
|
+
}
|
|
30
|
+
export interface ControlPlaneExchangeConfig {
|
|
31
|
+
/** The dedicated confidential client; deliberately not registered in the OAuth store. */
|
|
32
|
+
readonly clientId: string;
|
|
33
|
+
readonly clientSecret: string;
|
|
34
|
+
/** Exact `org/app/env` tenants whose assertions this exchange serves (the first-party assistant). */
|
|
35
|
+
readonly allowedTenants: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
export interface ControlPlaneExchangeTenantRef {
|
|
38
|
+
readonly org: string;
|
|
39
|
+
readonly app: string;
|
|
40
|
+
readonly env: string;
|
|
41
|
+
}
|
|
42
|
+
/** Structural audit port; the hosted service passes its tenant-scoped audit sink. */
|
|
43
|
+
export interface ControlPlaneExchangeAuditEvent {
|
|
44
|
+
readonly eventType: string;
|
|
45
|
+
readonly org: string;
|
|
46
|
+
readonly app: string;
|
|
47
|
+
readonly env: string;
|
|
48
|
+
readonly decision: 'allow' | 'deny';
|
|
49
|
+
readonly status: number;
|
|
50
|
+
readonly reasonCode?: string;
|
|
51
|
+
readonly actorSubject?: string;
|
|
52
|
+
readonly details?: Readonly<Record<string, string>>;
|
|
53
|
+
}
|
|
54
|
+
export interface ControlPlaneExchangeDeps {
|
|
55
|
+
readonly issuer: string;
|
|
56
|
+
readonly config: ControlPlaneExchangeConfig;
|
|
57
|
+
readonly verifierKey: () => Promise<JWTVerifyGetKey>;
|
|
58
|
+
/** Capability-scoped developer-grant creation/reuse (method-typed so narrower stores satisfy it). */
|
|
59
|
+
readonly grants: {
|
|
60
|
+
getOrCreateActive(input: {
|
|
61
|
+
readonly clientId: string;
|
|
62
|
+
readonly subject: string;
|
|
63
|
+
readonly resource: string;
|
|
64
|
+
readonly capabilities: readonly string[];
|
|
65
|
+
}): Promise<{
|
|
66
|
+
readonly id: string;
|
|
67
|
+
}>;
|
|
68
|
+
};
|
|
69
|
+
/** Durable single-use claim; false = replayed. */
|
|
70
|
+
readonly consumeJti: (jti: string, expiresAtMs: number) => Promise<boolean>;
|
|
71
|
+
/** Resolve an EXISTING platform principal; never creates one. Undefined = refuse. */
|
|
72
|
+
readonly resolveSubject: (subject: string) => Promise<{
|
|
73
|
+
readonly ok: true;
|
|
74
|
+
} | undefined>;
|
|
75
|
+
/** Live (non-revoked) assistant client ids of one tenant; the elevation issuer must name one. */
|
|
76
|
+
readonly listAssistantClientIds: (tenant: ControlPlaneExchangeTenantRef) => Promise<readonly string[]>;
|
|
77
|
+
/** Public-signup-mode workspace provisioning at mint; wired only when signup mode is public. */
|
|
78
|
+
readonly ensureWorkspace?: (identity: {
|
|
79
|
+
readonly subject: string;
|
|
80
|
+
readonly email: string;
|
|
81
|
+
}) => Promise<void>;
|
|
82
|
+
/** The scope ceiling and the developer resource the grant/token bind to. */
|
|
83
|
+
readonly resourcePath: string;
|
|
84
|
+
readonly capabilityCeiling: readonly string[];
|
|
85
|
+
readonly issueAccessToken: (input: {
|
|
86
|
+
readonly subject: string;
|
|
87
|
+
readonly email: string;
|
|
88
|
+
readonly resource: string;
|
|
89
|
+
readonly scope: string;
|
|
90
|
+
readonly developerGrantId: string;
|
|
91
|
+
readonly oauthClientId: string;
|
|
92
|
+
readonly ttlSeconds: number;
|
|
93
|
+
}) => Promise<string>;
|
|
94
|
+
readonly audit?: {
|
|
95
|
+
emit(event: ControlPlaneExchangeAuditEvent): Promise<unknown> | unknown;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export interface ControlPlaneExchangeRequest {
|
|
99
|
+
/** The parsed `application/x-www-form-urlencoded` body. */
|
|
100
|
+
readonly form: Readonly<Record<string, unknown>>;
|
|
101
|
+
readonly authorizationHeader?: string;
|
|
102
|
+
}
|
|
103
|
+
export interface ControlPlaneExchangeDecision {
|
|
104
|
+
readonly status: number;
|
|
105
|
+
readonly body: Readonly<Record<string, unknown>>;
|
|
106
|
+
/** Present exactly when the adapter must set `WWW-Authenticate`. */
|
|
107
|
+
readonly wwwAuthenticate?: string;
|
|
108
|
+
}
|
|
109
|
+
export declare function decideControlPlaneTokenExchange(request: ControlPlaneExchangeRequest, deps: ControlPlaneExchangeDeps): Promise<ControlPlaneExchangeDecision>;
|
|
110
|
+
//# sourceMappingURL=control-plane-token-exchange.d.ts.map
|